Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Proper Ruby style for multi-line method chaining containing blocks

The style guide is unclear on this case (other than just giving the opinion that it's weird and it is). I have a chain of hash/array functions to execute on a complex response object. I need to do a few sorts and selections and maps. Currently I just have a long chain of method calls but some of the blocks used for sorting and selecting are multiple lines and some are not.

Should I mix block definition types? Use only braces or only do..end blocks? Should I break it into multiple steps?

Also, if I refactor my blocks and turn them into methods, where would it be appropriate to put them? They don't really make sense in the context of the containing class as instance or class methods.

I looked around and could find no consensus on what is correct style but I am looking for a method that will pass the Ruby Style Guide (so most likely refactoring my blocks into methods, but where do I put them?)

Here is a bit of a contrived example:

def example1 (arr)
  arr.sort do |a, b|
    a_pieces = a.value.split ' '
    b_pieces = b.value.split ' '
    (a_pieces[1] + a_pieces[5]) <=> (b_pieces[1] + b_pieces[5])
  end
  .last[:some_value]
  .select do |a|
    a == 'something'
  end

end


def example2 (arr)
  arr.sort { |a, b|
    a_pieces = a.value.split ' '
    b_pieces = b.value.split ' '
    (a_pieces[1] + a_pieces[5]) <=> (b_pieces[1] + b_pieces[5])
  }
  .last[:some_value]
  .select { |a| a == 'something' }
end
like image 501
Daniel Williams Avatar asked Mar 27 '14 20:03

Daniel Williams


2 Answers

I would use do ... end blocks for multi-line and curly braces { ... } for one-line code. I never use do ... end for chaining methods, except when preceding for a multi-line block. I also chain methods in the same line as longer as the code is readable and the line is no longer than 80 chars.

So your example I will code it as below:

def example1 (arr)
  arr.sort do |a, b|
    a_pieces = a.value.split ' '
    b_pieces = b.value.split ' '
    (a_pieces[1] + a_pieces[5]) <=> (b_pieces[1] + b_pieces[5])
  end.last[:some_value].select { |a| a == 'something' }
end

Although I probably will split it in two methods (actually three with the example1 one):

def sort_array(arr)
  arr.sort do |a, b|
    a_pieces = a.value.split ' '
    b_pieces = b.value.split ' '
    (a_pieces[1] + a_pieces[5]) <=> (b_pieces[1] + b_pieces[5])
  end
end

def select_item(arr)
  arr.last[:some_value].select { |a| a == 'something' }
end

def example1(arr)
  select_item(sort_array(arr))
end
like image 142
Rafa Paez Avatar answered Sep 20 '22 15:09

Rafa Paez


I'd do it something like this:

def example1(arr)

  arr.sort_by { |a|
    a_pieces = a.value.split
    (a_pieces[1] + a_pieces[5])
  }.last[:some_value]
   .select { |a| a == 'something'}

end

sort will be much slower than sort_by if there is anything beyond a simple <=> comparison of simple objects.

Stylistically, use {...} instead of do...end if you're returning a value, especially in a chain of methods. The "style guides" suggest using do...end when you're dealing with multiple lines in a block, but end.some_method is fugly and confusing. Using {...} reminds us we are passing only values and cleans up the fugliness.

Remember, they're style guides, not laws, and are really common-coding practice guidelines to help us write code readable by everyone. Consistency is important, as is readability.

Also, a bare split does the same things as split ' ' or split(' ').

like image 21
the Tin Man Avatar answered Sep 20 '22 15:09

the Tin Man