chaining ruby enumerator functions in a clean way - ruby

I just finished a course on ruby where the instructor takes a list of movies, groups them, then calls map, sort, and reverse. It works fine, but I don't find the syntax to be very readable and I'm trying to figure out if what I have in mind is valid. I come from a c# background.
#we can reformat our code to make it shorter
#note that a lot of people don't like calling functions on the
#end of function blocks. (I don't like the look, either)
count_by_month = movies.group_by do |movie|
movie.release_date.strftime("%B")
end.map do |month, list|
[month, list.size]
end.sort_by(&:last).reverse
What I am wondering is if I can do something like
#my question: can I do this?
count_by_month = movies.group_by(&:release_date.strftime("%B"))
.map(&:first, &:last.size)
.sort_by(&:last)
.reverse
#based on what I've seen online, I could maybe do something like
count_by_month = movies.groupBy({m -> m.release_date.strftime("%B")})
.map{|month, list| [month, list.size]}
.sort_by(&:last)
.reverse

As a number of people in the comments suggest, this is really a matter of style; that being said, I have to agree with the comments within the code and say that you want to avoid method chaining at the end of a do..end.
If you're going to split methods by line, use a do..end. {} and do...end are synonymous, as you know, but the braces are more often used (in my experience) for single-line pieces of code, and as 'mu is too short' pointed out, if you're set on using them, you may want to look into lambdas. But I'd stick to do..end in this case.
A general style rule I was taught that I follow is to split up chains if what is being worked with changes class in a way that might not be intuitive. ex: fizz = "buzz".split.reverse breaks up a string into an array, but it's clear what the code is doing.
In the example you provided, there's a lot going on that's a bit hard to follow; I like that you wrote out the group_by using hash notation in the last example because it's clear what the group_by is sorting by there and what the output is - I'd put it in a [well named] variable of its own.
grouped_by_month = movies.groupBy({m -> m.release_date.strftime("%B")})
count_by_month = grouped_by_month.map{|month, list| [month, list.size]}.sort_by(&:last).reverse
This splits up the code into one line that sets up the grouping hash and another line that manipulates it.
Again, this is style, so everyone has their own quirks; this is simply how I'd edit this based off a quick glance. You seem to be getting into Ruby quite well overall! Sometimes I just like the look of a chain of methods on one line, even if its against best practices (and I'm doing Project Euler or some other project of my own). I'd suggest looking at large projects on Github (ex: rails) to get a feel for how those far more experienced than myself write clean code. Good luck!

Related

Stuck with rdoc: Any way to add indentation to call-seq?

I know I should use YARD. Not an option in this environment.
So I'm using rdoc and call-seq is generally been great, but I want to add indentation for a call-seq that is always used in a block/yield style, something like:
call-seq:
someFunc {
example1(..)
example2(..)
}
But call-seq: removes any indentation and it becomes:
someFunc {
example1(..)
example2(..)
}
Which is sad. I know I could use a separate code block as an example, but this is really part of the general API for how this should be called, and it would be nice if I could use call-seq (instead of the ugly inverted colors of a code block).
I am guessing this isn't possible with the limitations of rdoc, but I thought I'd ask. Any thoughts?
I think you are confused about what the purpose of the calling sequence is, but the name unfortunately is really confusing. Normally, I would assume that it was designed by a non-native speaker, but if I remember my history correctly, RDoc was designed by David Thomas and Andy Hunt for their book Programming Ruby in 2000.
Anyway, calling sequence is not for documenting a sequence of calls, as one might surmise, i.e. it is not for documenting multiple lines of code.
It is for documenting what we could all in other languages multiple overloads of the method. Note that the syntax that is used for each overload is not even legal Ruby syntax (because of the →), so treating it as executable code that is to be indented does not make much sense:
inject(initial, sym) → obj
inject(sym) → obj
inject(initial) { |memo, obj| block } → obj
inject { |memo, obj| block } → obj
What this tells us is that the Enumerable#inject method has four different "overloads", 2×2 combinations: with or without initial value and specifying the binary operation either as a block or as a Symbol.
Hence why it is not possible to format code inside the calling sequence.

Is there a better way to assign a Ruby hash while avoiding RuboCop's ABC Size warnings?

I have a method that builds a laptop's attributes, but only if the attributes are present within a row that is given to the method:
def build_laptop_attributes desk_id, row, laptop
attributes = {}
attributes[:desk_number] = room_id if laptop && desk_id
attributes[:status] = row[:state].downcase if row[:state]
attributes[:ip_address] = row[:ip_address] if row[:ip_address]
attributes[:model] = row[:model] if row[:model]
attributes
end
Currently, RuboCop is saying that the Metric/AbcSize is too high, and I was wondering if there is an obvious and clean way to assign these attributes?
Style Guides Provide "Best Practices"; Evaluate and Tune When Needed
First of all, RuboCop is advisory. Just because RuboCop complains about something doesn't mean it's wrong in some absolute sense; it just means you ought to expend a little more skull sweat (as you're doing) to see if what you're doing makes sense.
Secondly, you haven't provided a self-contained, executable example. That makes it impossible for SO readers to reliably refactor it, since it can't currently be tested without sample inputs and expected outputs not provided in your original post. You'll need those things yourself to evaluate and refactor your own code, too.
Finally, the ABC Metric looks at assignments, branches, and conditionals. You have five assignments, four conditionals, and what looks liks a method call. Is that a lot? If you haven't tuned Rubocop, the answer is "RuboCop thinks so." Whether or not you agree is up to you and your team.
If you want to try feeding Rubocop, you can do a couple of things that might help reduce the metric:
Refactor the volume and complexity of your assignments. Some possible examples include:
Replace your postfix if-statements with safe navigators (&.) to guard against calling methods on nil.
Extract some of your branching logic and conditionals to methods that "do the right thing", potentially reducing your current method to a single assignment with four method calls. For example:
attributes = { desk_number: location, status: laptop_status, ... }
Replace all your multiple assignments with a deconstructing assignment (although Rubocop often complains about those, too).
Revisit whether you have the right data structure in the first place. Maybe you really just want an OpenStruct, or some other data object.
Your current code seems readable, so is the juice really worth the squeeze? If you decide that RuboCop is misguided in this particular case, and your code works and passes muster in your internal code reviews, then you can tune the metric's sensitivity in your project's .rubocop.yml or disable that particular metric for just that section of your source code.
After reading #Todd A. Jacobs answer, you may want (or not) to write something like this:
def build_laptop_attributes desk_id, row, laptop
desk_number = room_id if laptop && desk_id
{
desk_number: desk_number,
status: row[:state]&.downcase,
ip_address: = row[:ip_address],
model: row[:model]
}.compact
end
This reduces has the advantage of reducing the number of calls to []=, as well as factorizing many ifs in a single compact.
In my opinion, it is more readable because it is more concise and because the emphasis is completely on the correspondence between your keys and values.
Alternative version to reduce the amount of conditionals (assuming you are checking for nil / initialized values):
def build_laptop_attributes desk_id, row, laptop
attributes = {}
attributes[:desk_number] = room_id if laptop && desk_id
attributes[:status] = row[:state]&.downcase
attributes[:ip_address] = row[:ip_address]
attributes[:model] = row[:model]
attributes.compact
end
There is an additional .compact as a cost of removing assignments checks.

Good style for splitting lengthy expressions over lines

If the following is not the best style, what is for the equivalent expression?
if (some_really_long_expression__________ && \
some_other_really_long_expression)
The line continuation feels ugly. But I'm having a hard time finding a better alternative.
The parser doesn't need the backslashes in cases where the continuation is unambiguous. For example, using Ruby 2.0:
if true &&
true &&
true
puts true
end
#=> true
The following are some more-or-less random thoughts about the question of line length from someone who just plays with Ruby. Nor have I had any training as a software engineer, so consider yourself forewarned.
I find the problem of long lines is often more the number of characters than the number of operations. The former can be reduced by (drum-roll) shortening variable names and method names. The question, of course, is whether the application of a verbosity filter (aka babbling, prattling or jabbering filter) will make the code harder to comprehend. How often have you seen something fairly close to the following (without \)?
total_cuteness_rating = cats_dogs_and_pigs.map {|animal| \
cuteness_calculation(animal)}.reduce {|cuteness_accumulator, \
cuteness_per_animal| cuteness_accumulator + cuteness_per_animal}
Compare that with:
tot_cuteness = pets.map {|a| cuteness(a)}.reduce(&:+)
Firstly, I see no benefit of long names for local variables within a block (and rarely for local variables in a method). Here, isn't it perfectly obvious what a refers to in the calculation of tot_cuteness? How good a memory do you need to remember what a is when it is confined to a single line of code?
Secondly, whenever possible use the short form for enumerables followed by a block (e.g, reduce(&:+)). This allows us to comprehend what's going on in microseconds, here as soon as our eyes latch onto the +. Same, for .to_i, _s or _f. True, reduce {|tot, e| tot + e} isn't much longer, but we're forcing the reader's brain to decode two variables as well as the operator, when + is really all it needs.
Another way to shorten lines is to avoid long chains of operations. That comes at a cost, however. As far as I'm concerned, the longer the chain, the better. It reduces the need for temporary variables, reduces the number of lines of code and--possibly of greatest importance--allows us to read across a line, as most humans are accustomed, rather than down the page. The above line of code reads, "To calculate total cuteness, calculate each pet's cuteness rating, then sum those ratings". How could it be more clear?
When chains are particularly long, they can be written over multiple lines without using the line-continuaton character \:
array.each {|e| blah, blah, ..., blah
.map {|a| blah, blah, ..., blah
.reduce {|i| blah, blah, ..., blah }
}
}
That's no less clear than separate statements. I think this is frequently done in Rails.
What about the use of abbreviations? Which of the following names is most clear?
number_of_dogs
number_dogs
nbr_dogs
n_dogs
I would argue the first three are equally clear, and the last no less clear if the writer consistently prefixes variable names with n_ when that means "number of". Same for tot_, and so on. Enough.
One approach is to encapsulate those expressions inside meaningful methods. And you might be able to break it into multiple methods that you can later reuse.
Other then that is hard to suggest anything with the little information you gave. You might be able to get rid of the if statement using command objects or something like that but I can't tell if it makes sense on your code because you didn't show it.
Ismael answer works really well in Ruby (there may be other languages too) for 2 reasons:
Ruby has very low overhead to creating methods due to lack of type
definition
It allows you to decouple such logic for reuse or future adaptability and testing
Another option I'll toss out is create logic equations and store the result in a variable e.g.
# this are short logic equations testing x but you can apply same for longer expressions
number_gt_5 = x > 5
number_lt_20 = x < 20
number_eq_11 = x == 11
if (number_gt_5 && number_lt_20 && !number_eq_11)
# do some stuff
end

Why are else statements discouraged in Ruby?

I was looking for a Ruby code quality tool the other day, and I came across the pelusa gem, which looks interesting. One of the things it checks for is the number of else statements used in a given Ruby file.
My question is, why are these bad? I understand that if/else statements often add a great deal of complexity (and I get that the goal is to reduce code complexity) but how can a method that checks two cases be written without an else?
To recap, I have two questions:
1) Is there a reason other than reducing code complexity that else statements could be avoided?
2) Here's a sample method from the app I'm working on that uses an else statement. How would you write this without one? The only option I could think of would be a ternary statement, but there's enough logic in here that I think a ternary statement would actually be more complex and harder to read.
def deliver_email_verification_instructions
if Rails.env.test? || Rails.env.development?
deliver_email_verification_instructions!
else
delay.deliver_email_verification_instructions!
end
end
If you wrote this with a ternary operator, it would be:
def deliver_email_verification_instructions
(Rails.env.test? || Rails.env.development?) ? deliver_email_verification_instructions! : delay.deliver_email_verification_instructions!
end
Is that right? If so, isn't that way harder to read? Doesn't an else statement help break this up? Is there another, better, else-less way to write this that I'm not thinking of?
I guess I'm looking for stylistic considerations here.
Let me begin by saying that there isn't really anything wrong with your code, and generally you should be aware that whatever a code quality tool tells you might be complete nonsense, because it lacks the context to evaluate what you are actually doing.
But back to the code. If there was a class that had exactly one method where the snippet
if Rails.env.test? || Rails.env.development?
# Do stuff
else
# Do other stuff
end
occurred, that would be completely fine (there are always different approaches to a given thing, but you need not worry about that, even if programmers will hate you for not arguing with them about it :D).
Now comes the tricky part. People are lazy as hell, and thusly code snippets like the one above are easy targets for copy/paste coding (this is why people will argue that one should avoid them in the first place, because if you expand a class later you are more likely to just copy and paste stuff than to actually refactor it).
Let's look at your code snippet as an example. I'm basically proposing the same thing as #Mik_Die, however his example is equally prone to be copy/pasted as yours. Therefore, would should be done (IMO) is this:
class Foo
def initialize
#target = (Rails.env.test? || Rails.env.development?) ? self : delay
end
def deliver_email_verification_instructions
#target.deliver_email_verification_instructions!
end
end
This might not be applicable to your app as is, but I hope you get the idea, which is: Don't repeat yourself. Ever. Every time you repeat yourself, not only are you making your code less maintainable, but as a result also more prone to errors in the future, because one or even 99/100 occurrences of whatever you've copied and pasted might be changed, but the one remaining occurrence is what causes the #disasterOfEpicProportions in the end :)
Another point that I've forgotten was brought up by #RayToal (thanks :), which is that if/else constructs are often used in combination with boolean input parameters, resulting in constructs such as this one (actual code from a project I have to maintain):
class String
def uc(only_first=false)
if only_first
capitalize
else
upcase
end
end
end
Let us ignore the obvious method naming and monkey patching issues here, and focus on the if/else construct, which is used to give the uc method two different behaviors depending on the parameter only_first. Such code is a violation of the Single Responsibility Principle, because your method is doing more than one thing, which is why you should've written two methods in the first place.
def deliver_email_verification_instructions
subj = (Rails.env.test? || Rails.env.development?) ? self : delay
subj.deliver_email_verification_instructions!
end

"In Ruby there's more than one way of doing the same thing" - what does this mean?

Feel free to delete this topic if it's discussed or quite obvious. I hail from C# background and I'm planning to learn Ruby. Everything I read about it seems quite intriguing. But I'm confused over this basic philosophy of Ruby that "there's more than one way to do one thing". Can someone provide 2 or 3 simple arithmetic or string examples to make this point clear, like if its about the syntaxes or logics etc.
Thanks
"More than one way of doing something" means having the choice of doing something the way you want it. That way you can use various programming styles, no matter what background you're coming from.
Iteration using for vs. blocks
You can iterate over an array of things like so. This is pretty basic, and if you're from a Java background, this feels kind of natural.
for something in an_array
print something
end
A more Ruby-like way would be the following:
an_array.each do |something|
print something
end
The first is a rather well known way of doing things. The second one is using blocks, a very powerful concept that you'll find in many Ruby idioms. Basically, the array knows how to iterate over its contents, so you can modify this and add something like:
an_array.each_with_index do |something, index|
print "At #{index}, there is #{something}"
end
You could have done it like this too, but now you see that the above one looks easier:
index = 0
for something in an_array
print "At #{index}, there is #{something}"
index += 1
end
Passing arguments as usual or using Hashes
Normally, you would pass arguments like so:
def foo(arg1, arg2, arg3)
print "I have three arguments, which are #{arg1}, #{arg2} and #{arg3}"
end
foo("very", "easy", "classic")
=> "I have three arguments, which are very easy and classic"
However, you may also use a Hash to do that:
def foo(args)
print "I have multiple arguments, they are #{args[:arg1]}, #{args[:arg2]} and #{args[:arg3]}"
end
foo :arg1 => "in a", :arg2 => "hash", :arg3 => "cool"
=> "I have three arguments, which are in a hash and cool"
The second form is one used excessively by Ruby on Rails. The nice thing is that you now have named parameters. When you are passing them, you will more easily remember what they are used for.
It means a lot of confusion, style wars, and bugs due to subtle differences, all in the name of freedom of choice.
A somewhat trivial example is the use of alias/alias_method (also note that there are two similar ways for almost the same thing, e. g. alias versus alias_method).
Consider that you are working in a project and you forgot which API to use.
What was the name of the method again?
Well, you can just remain within the domain logic of your program at hand, and continue to work with it the way you want to; then you are going to simply add an alias in the main entry point of your other program.
People can use by default .collect or they can use .map, it makes little difference what you personally would use (I use .map since it is shorter).
The use of aliases helped me because after some months, I often can not remember how to use something. Yes, I could look it up, but why would I have to bother anyway? I can just use an alias instead. (Note that I do try to remain as simple as possible with aliases and APIs.)

Resources