Using conditionals in one line in rails - ruby

Is this a valid way to impose a condition on a statement? I was having trouble using multiple &&'s or ||'s when using it in one line the other day.
#sales_opportunities << auction unless auction.company == current_user || !(auction.bids & current_user.bids).empty? || !auction.condition.include?(part.condition)

Some thoughts:
You can certainly put multiple conditions into one line after "unless"
If you do so, you may need to confirm they return the proper results, even with edge cases (make sure that the || or && has the proper scope, you may want parens to be explicit)
Nonetheless, this is pretty hard to read. I would suggest pulling those conditionals out and making each into its own method. This will make it more readable, and if you need those conditions elsewhere, it will make your code more DRY.

What #CaptainChaos and #PascalBetz said. At the very minimum, extract parts of that condition to local vars with descriptive names. Something like this:
insider_user = auction.company == current_user
user_placed_bids = (auction.bids & current_user.bids).present?
part_matches = auction.condition.include?(part.condition)
#sales_opportunities << auction unless insider_user || user_placed_bids || !part_matches
Now this looks much more manageable, heh?

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.

What's the purpose of using the suffix "if" statement in Ruby?

I know Ruby supports a suffix if like:
number = -42 if opposite
but what's the purpose of this? Why would it be used in place of the prefix if statement?
The suffix-style if and unless can also be good for "guard clauses", in the form of:
return if ...
return unless ...
Here's an example:
# suffix-style
def save
return false if invalid?
# go for it
true
end
Versus:
# indented style
def save
if valid?
# go for it
true
else
false
end
end
In the second example, the entire implementation of the method has to be shifted over by one indent due to the valid? check, and we need an extra else clause. With the suffix style, the invalid? check is considered an edge case that we handle and then bail out, and the rest of the method doesn't need an indent or an else clause.
This is sometimes called a "guard clause" and is recommended by the Ruby Style Guide.
It can make the code easier to read in some cases. I find this to be true especially in the case of unless, where you have some action you usually want to perform:
number = -42 unless some_unusual_circumstance_holds
Once you have it for unless, for symmetry it makes sense to support it for if as well.
number = -42 if opposite
is the same as
if opposite
number = -42
end
Some people prefer the one-liner for readability reasons. Imagine a line like:
process_payment if order_fulfilled?
Doesn't that read nice?
Postfix style does not have the else section. It is useful when you only want to do something with one of the two cases divided by the condition and don't want to mess with the other case.
It's the same as prefix but shorter. The only reason is to save vertical space in the text editor.

2 Conditions in if statement

I am trying to detect if the email address is not one of two domains but I am having some trouble with the ruby syntax. I currently have this:
if ( !email_address.end_with?("#domain1.com") or !email_address.end_with?("#domain2.com"))
#Do Something
end
Is this the right syntax for the conditions?
Rather than an or here, you want a logical && (and) because you are trying to find strings which match neither.
if ( !email_address.end_with?("#domain1.com") && !email_address.end_with?("#domain2.com"))
#Do Something
end
By using or, if either condition is true, the whole condition will still be false.
Note that I am using && instead of and, since it has a higher precedence. Details are well outlined here
From the comments:
You can build an equivalent condition using unless with the logical or ||
unless email_address.end_with?("#domain1.com") || email_address.end_with?("#domain2.com")
This may be a bit easier to read since both sides of the || don't have to be negated with !.
If more domains are added, then the repetitive email_address.end_with? is getting boring real fast. Alternative:
if ["#domain1.com", "#domain2.com"].none?{|domain| email_address.end_with?(domain)}
#do something
end
I forgot end_with? takes multiple arguments:
unless email_address.end_with?("#domain1.com", "#domain2.com")
#do something
end
How about:
(!email_address[/#domain[12]\.com\z/])

Preferred way to write true AND false conditional in Ruby

I apologize if this question is answered somewhere but I'm not positive I'm phrasing it right for Google, and I haven't seen it in any style guides.
Since Ruby has multiple ways to show negativity in a conditional, what is the preferred way to write a conditional that is checking that one part is true and one part is false? Example:
if array && !array.include?('Bob')
#do stuff!
But you could also say:
if array
#do stuff! unless array.include?('Bob')
or:
if array && not array.include?('Bob')
#do stuff
or:
if !array.nil? && !array.include?('Bob')
or a wacky double unless:
unless array.nil?
#do stuff unless array.include?('Bob')
And several others. Any idea which is considered the most Rubyish? Sources to back your opinion up?
As far as documented guidelines, the only thing that i can think of is the Google guide that admonishes "don't use and and or; always use && and || instead.".
Other than that, it somewhat depends on the context. If all you have is code to be executed if one condition is true and the other false, then I would definitely put them in a single if with && !:
if array && !array.include?('Bob')
#do stuff!
On the other hand, you might have additonal code that gets executed if the first condition is true even if the second one is also true; in that case, the nested unless or if makes sense:
if array
do stuff! unless array.include? 'Bob'
do other stuff anyway
end
Does not improve readability, but still interesting.
If you use Rails you can use ActiveSupport try extension like this:
if array.try(:include?, 'Bob')
# Do stuff

Is it better to use || or include? when checking a variable against multiple values?

Which way of writing this condition is better?
1)
(self.expense_gl_dist_code.dist_type == GlDistCode::PERCENTAGE || self.expense_gl_dist_code.dist_type == GlDistCode::MIXED)
2)
["GlDistCode::PERCENTAGE","GlDistCode::MIXED"].include?(self.expense_gl_dist_code.dist_type)
I find the second clearer for two reasons:
1) In the second version the elements being checked for are all next to each other, separated by commas. In the first version there's always self.expense_gl_dist_code.dist_type ==, so it's less easy to scan them all at once.
2) In the second version it's immediately obvious that all elements are checked for the same condition, while in the first version it could say something like
dist_type == GlDistCode::PERCENTAGE || dist_type == GlDistCode::MIXED || dist_type != GlDistCode::WHATEVER
and you might not notice right away.
The first way is much clearer and therefore to be preferred to the slightly obfuscated second option.
If you are just comparing two elements, I'd say either is fine.
I'm more inclined to the second version, because you can then include all the elements you want to validate against into a single variable, and name it. For example
ALLOWED_TYPES = [GldDistCode::PERCENTAGE, GlDistCode::MIXED]
then
if ALLOWED_TYPES.include?(dist_type)
is more legible IMHO.
BTW, you're using strings ("GldDistCode::PERCENTAGE") instead of the actual value which you intended.

Resources