Ruby style tips: Avoid eval, and keep things clear and orderly - ruby

I'm confused on this one. I have a couple of viable solutions, but I don't like either of them. The problem at hand is that I am trying to generate a CSV in a Rails application. In my application specifically, I have a lot of values, around 30. Many of the values I would like displayed are also on associations, thus making the lines even longer... They look something like this (don't bother to read, just wanted you to have an idea of what i was talking about):
[piece.client.organization, piece.client.category, piece.client.name , piece.campaign.name, piece.name, piece_url(piece.id), piece.campaign.election_date, piece.campaign.win_loss, piece.final_date, piece.local_photos, piece.killed, piece.format_list, piece.artist_list, piece.partner_list, piece.account_executive_list, piece.out_of_stock, piece.total_intake, piece.campaign.candidate_tags, piece.client.spec_list, piece.campaign.mail_poll]
Except that they're even longer and more unwieldy. They work, but they make me feel bad inside. This is when I had the idea that I would just put them in a two-dimensional array, it instantly made the data look much more readable:
[["Client", piece.client.organization],
["Category", piece.client.category],
["Client Name", piece.client.name] ,
...
["Campaign Name", piece.campaign.name],
["Piece Name", piece.name]]
That's great, now I can just loop over it to create my CSV rows... However, it will blow up based on where I need to define it as my "piece" is undefined. So then, I thought... what if I just wrap the second arguments in quotes and call eval on them later on when I need them? Then I looked it up, and people seem to say to use eval only to save lives...
Can anyone think of a simpler way to keep all of my data paired with column names, but maybe not use eval? Or maybe suggest that this would be a good use case for eval?

You can usually avoid eval by using blocks instead. For example, re-define your structure in terms of method calls:
columns = [
[ "Client", lambda { |piece| piece.client.organization } ],
[ "Category", lambda { |piece| piece.client.category } ],
# ...
]
Then when iterating over your block, do something like this:
pieces.each do |piece|
spec.each do |label, proc|
value = proc.call(piece)
# ... Do whatever you need here
end
end
Defining blocks (Proc internally) can help define methods for doing things while deferring variable binding to some point in the future.
eval has a reputation for being dangerous because it can execute anything. It's best to avoid it unless there really is no other way.

Related

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.

Ruby: how to pass object to a block and return a calculated value depending on the obj?

I would like to do something similar:
seconds=Time.parse("0:26:29.489").magic{|z| z.hour+z.min+z.sec+z.nsec.fdiv(1_000_000)}
to convert a timestamp into seconds (with fractions too), instead of writing:
d=Time.parse("0:26:29.489")
seconds=d.hour+d.min+d.sec+d.nsec.fdiv(1_000_000)
to spare a temporary "d" variable. But what should I use for "magic" if any?
Ruby has tap, but that won't help you here. What you want is something that would be called pipe, but sadly it's not there. At least not without a gem that monkey patches Object. Though I think it should be.
You can create a lambda and immediately call it, which will avoid the intermediate variable (or at least contain it within the lambda's block scope, as in your magic example), but I'm not sure you gain much, and would probably stick with what you have. The lambda approach would look like this:
# will return the value for "seconds"
->(d) { d.hour+d.min+d.sec+d.nsec.fdiv(1_000_000) }.(Time.parse("0:26:29.489"))

chaining ruby enumerator functions in a clean way

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!

"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.)

Why is "#{String}" a common idiom in Ruby

A Ruby dev I know asked this; my answer is below... Are there other, better reasons?
Why do so many Ruby programmers do
"#{string}"
rather than
string
since the second form is simpler and more efficient?
Is this a common idiom for Ruby developers? I don't see it that much.
Smaller changes when you later need to do more than simply get the value of the string, but also prepend/append to it at the point of use seems to be the best motivation I can find for that idiom.
There is only one case where this is a recommended idiom :
fname = 'john'
lname = 'doe'
name = "#{fname} #{lname}"
The code above is more efficient than :
name = fname + ' ' + lname
or
name = [fname, lname].join(' ')
What's the broader context of some of the usages? The only thing I can come up with beyond what's already been mentioned is as a loose attempt at type safety; that is, you may receive anything as an argument, and this could ensure that whatever you pass in walks like a duck..or, well, a string (though string.to_s would arguably be clearer).
In general though, this is probably a code smell that someone along the way thought was Best Practices.
I use this kind of code, so that I can pass nil as string and it still will work on a string, rather than seeing some exceptions flying:
def short(string = nil)
"#{string}"[0..7]
end
And it's easier/faster to append some debug code, if it's already in quotes.
So in short: It's more convenient.
Interesting answers, everyone. I'm the developer who asked the original question. To give some more context, I see this occasionally at my current job, and also sometimes in sample code on the Rails list, with variables that are known in advance to contain strings. I could sort of understand it as a substitute for to_s, but I don't think that's what's going on here; I think people just forget that you don't need the interpolation syntax if you're just passing a string variable.
If anyone tried to tell me this was a best practice, I'd run away at top speed.
maybe it is easy way to convert any to string? Because it is the same as call to_s method. But it is quite strange way :).
a = [1,2,3]
"#{a}"
#=> "123"
a.to_s
#=> "123"
I could image this being useful in cases where the object being interpolated is not always a String, as the interpolation implicitly calls #to_s:
"#{'bla'}" => "bla"
"#{%r([a-z])}" => "(?-mix:[a-z])"
"#{{:bla => :blub}}" => "blablub"
May make sense when logging something, where you don't care so much about the output format, but never want an error because of a wrong argument type.

Resources