Ruby Code Beautification, Split Long Instructions on Multiple Lines

Ruby code beautification, split long instructions on multiple lines

Do it like this:

Promotion.joins(:category).
where(["lft>=? and rgt<=?", c.lft, c.rgt]).
joins(:shops).
where(:promotions_per_shops => { :shop_id => shops_id }).
count('id', :distinct => true)

Breaking up long strings on multiple lines in Ruby without stripping newlines

Three years later, there is now a solution in Ruby 2.3: The squiggly heredoc.

class Subscription
def warning_message
<<~HEREDOC
Subscription expiring soon!
Your free trial will expire in #{days_until_expiration} days.
Please update your billing information.
HEREDOC
end
end

Blog post link: https://infinum.co/the-capsized-eight/articles/multiline-strings-ruby-2-3-0-the-squiggly-heredoc

The indentation of the least-indented line will be
removed from each line of the content.

Ruby: Can I write multi-line string with no concatenation?

There are pieces to this answer that helped me get what I needed (easy multi-line concatenation WITHOUT extra whitespace), but since none of the actual answers had it, I'm compiling them here:

str = 'this is a multi-line string'\
' using implicit concatenation'\
' to prevent spare \n\'s'

=> "this is a multi-line string using implicit concatenation to eliminate spare
\\n's"

As a bonus, here's a version using funny HEREDOC syntax (via this link):

p <<END_SQL.gsub(/\s+/, " ").strip
SELECT * FROM users
ORDER BY users.id DESC
END_SQL
# >> "SELECT * FROM users ORDER BY users.id DESC"

The latter would mostly be for situations that required more flexibility in the processing. I personally don't like it, it puts the processing in a weird place w.r.t. the string (i.e., in front of it, but using instance methods that usually come afterward), but it's there. Note that if you are indenting the last END_SQL identifier (which is common, since this is probably inside a function or module), you will need to use the hyphenated syntax (that is, p <<-END_SQL instead of p <<END_SQL). Otherwise, the indenting whitespace causes the identifier to be interpreted as a continuation of the string.

This doesn't save much typing, but it looks nicer than using + signs, to me.

Also (I say in an edit, several years later), if you're using Ruby 2.3+, the operator <<~ is also available, which removes extra indentation from the final string. You should be able to remove the .gsub invocation, in that case (although it might depend on both the starting indentation and your final needs).

EDIT: Adding one more:

p %{
SELECT * FROM users
ORDER BY users.id DESC
}.gsub(/\s+/, " ").strip
# >> "SELECT * FROM users ORDER BY users.id DESC"

ruby Split lines using selective tags that appear multiple times in file in any order

You can use Ruby's flip-flop operator here.

Code

def dissect(str)
arr = str.lines.map(&:strip)
grp, ungrp = [], []
arr.each { |line| line=='<public>'..line=='</public>' ? (grp << line) : ungrp << line }
[grp.slice_when { |s,t| s == '</public>' && t == '<public>' }.
map { |a| a[1..-2] },
ungrp]
end

The last statement of the method, which constructs the array returned by the method, could be replaced with the following.

b = grp.count('<public>').times.with_object([]) do |_,a|
ndx = grp.index('</public>')
a << grp[1..ndx-1]
grp = grp[ndx+1..-1] if ndx < grp.size-1
end
[b, ungrp]

Example

str =<<-EOS
this is test line 1
this is testing purpose
<public>
am inside of public
doing lot of stuffs and printing result here
</public>
let's stick another line here
<public>
am inside of another public
doing another set of stuffs and printing here
</public>
and another line here
EOS

grouped, ungrouped = dissect(str)
#=> [
# [ ["am inside of public",
# "doing lot of stuffs and printing result here"],
# ["am inside of another public",
# "doing another set of stuffs and printing here"]
# ],
# [
# "this is test line 1",
# "this is testing purpose",
# "let's stick another line here",
# "and another line here"]
# ]
# ]
grouped
#=> [ ["am inside of public",
# "doing lot of stuffs and printing result here"],
# ["am inside of another public",
# "doing another set of stuffs and printing here"]
# ]
ungrouped
#=> ["this is test line 1",
# "this is testing purpose",
# "let's stick another line here",
# "and another line here"]

Explanation

For the example above the steps are as follows.

arr = str.lines.map(&:strip)
#=> ["this is test line 1", "this is testing purpose", "<public>",
# "am inside of public", "doing lot of stuffs and printing result here",
# "</public>", "let's stick another line here", "<public>",
# "am inside of another public", "doing another set of stuffs and printing here",
# "</public>", "and another line here"]

ungrp, grp = [], []
arr.each { |line| line=='<public>'..line=='</public>' ? (grp << line) : ungrp << line }

The flip-flop returns false until line=='<public>' is true. It then returns true and continues to return true until after line=='</public>' is true. It then returns false until it once again encounters a line for which line=='<public>' is true, and so on.

ungrp
#=> <returns the value of 'ungrouped' in the example>
grp
#=> ["<public>",
# "am inside of public",
# "doing lot of stuffs and printing result here",
# "</public>",
# "<public>",
# "am inside of another public",
# "doing another set of stuffs and printing here",
# "</public>"]
enum = grp.slice_when { |s,t| s == '</public>' && t == '<public>' }
#=> #<Enumerator: #<Enumerator::Generator:0x00000

See Enumerable#slice_when, which made its debut in Ruby v2.2.

We can see the elements that will be generated by this enumerator by converting it to an array.

enum.to_a
#=> [["<public>", "am inside of public",
# "doing lot of stuffs and printing result here", "</public>"],
# ["<public>", "am inside of another public",
# "doing another set of stuffs and printing here", "</public>"]]

Lastly,

enum.map { |a| a[1..-2] }
#=> <returns the array 'grouped' in the example>

Beautify excessively long conditionals

Yes, you can write it as multiple lines, and that is pretty common when you have a large number of conditions.

if a && b &&
c && d &&
e && f
do_something
else
"Well, with all those conditions, it wasn't likely anyway, was it?"
end

(Personally, I tend to combine long grouped conditions into variables so that the condition in the if-expression is short and it's easy to follow the logic. You can use the same multi-line syntax with that.)

Split Ruby regex over multiple lines

You need to use the /x modifier, which enables free-spacing mode.

In your case:

"bar" =~ /(foo|
bar)/x

Ruby indented multiline strings

Personally, I think that Ruby's indented heredocs are useless and they should work more like Bash indented heredocs and also strip whitespace inside the string …

Anyway, there are a couple of libraries that try to deal with this situation. There is a wide array of libraries that try to fix this problem:

  • Martin Aumont's Unindent library which is also part of the Facets library
  • Facets also provides String#margin
  • Sunlight Labs' Unindentable library
  • Samuel Dana's Indentation library

What is the best way to refactor a long string line in ruby?

You can try this

str = "#{envelope_quantity} - envelope #{Budget::util_name(envelope_size)} "\
"#{Budget::util_name(envelope_paper)} #{Budget::util_name(envelope_color)} "\
"#{Budget::util_name(envelope_grammage)} #{Budget::util_name(envelope_model)} "\
"#{Budget::util_name(envelope_print)}"

this way you will be able to confine the string within max line length and also its slightly more readable than using join

Multiline strings with no indent

In the RubyTapas Episode 249, Avdi Grimm describes a technique to strip leading whitespace from a multi-line string:

def unindent(s)
s.gsub(/^#{s.scan(/^[ \t]+(?=\S)/).min}/, '')
end

It is behavior compatible to other existing solutions to this problem, e.g. String#strip_heredoc in ActiveSupport / Rails or the standalone unindent gem.

You can use this method with a heredoc which is special syntax in ruby (and many other languages) to write multi-line strings.

module Something
def unindent(s)
s.gsub(/^#{s.scan(/^[ \t]+(?=\S)/).min}/, '')
end

def welcome
unindent(<<-TEXT)
Hello

This is an example. This multiline string works
- even with deeper nestings...
All is OK here :)
TEXT
end
end


Related Topics



Leave a reply



Submit