Refactoring some Ruby Code - ruby

I need some help refactoring some ruby code. Im not keeping it DRY at all.
if potatoes
if item.type != nil
if item.has_stuff == false && (item.something_else).to_f >= (comparing).to_f
# RUN JOB A
else
# RUN JOB B
end
else
# RUN JOB A
end
else
# RUN JOB B
end
I just created random names for things.

if potatoes && (item.type.nil? || (item.has_stuff == false && (item.something_else).to_f >= (comparing).to_f))
# JOB A
else
# JOB B
end
But for such complicated logic, it might be better to pull part of that into a method
def item.has_some_property?(comparing)
has_stuff == false && something_else.to_f >= comparing.to_f
end
if potatoes && (item.type.nil? || item.has_some_property?(comparing))
# JOB A
else
# JOB B
end

if !potatoes
# Job B
elsif item.type.nil?
# Job A
elsif item.has_stuff != false
# Job B
elsif item.something_else.to_f >= comparing.to_f
# Job A
else
# Job B
end

If you associate the jobs to external methods (defined elsewhere) you can keep it dry by using ternary operators, and using a different perspective, considering that the conditions are complementary... to keep the code more readable you can write the conditions on different lines, if you wish, or you should use a method to perform the checks (i.e. item.has_stuff == false && (item.something_else).to_f >= (comparing).to_f) if meaningful in your code...
A quick example:
def is_empty_and_something_less_than_something?(comparing)
item.has_stuff == false && (item.something_else).to_f >= (comparing).to_f
end
job_a_conditions= potatoes && (item.type.nil? || item.is_empty_and_something_less_than_comparing?(comparing))
job_a_conditions ? job_a : job_b
The same conditions can be associated to a method, if re-used in your code.

Related

&& operator not stopping excution with nil

The class below defies my understanding that nil && 'foo' should return nil and not execute 'foo'
no matter what I tried, with or without parenthesis, #user.another_boolean always returns undefined method another_boolean for nil nilclass. I thought if #user is nil it should stop evaluating there and return nil.
class MyClass
def initialize(user, variable = nil)
#user = user
#variable = variable || user.try(:variable)
end
def result
#result ||= !!(#user &&
#variable &&
#variable.a_boolean ||
#user.another_boolean? ||
#user.a_third_boolean? && instance_method_retuning_a_boolean)
end
end
I also tried to look for the documentation of the && operator inside the ruby documentation but could only find a reference to and which shouldn't be the same thing given their precedence difference.
Any help much appreciated.
Ruby version: 2.2.5
Edit:
#user and #variable are rails model
Rails version: 4.2
It is standard practice in software for && to have a higher precedence than ||.
So the following are all logically equivalent:
b && a || c
a && b || c
c || b && a
c || a && b
Now, your code is a little longer:
#user &&
#variable &&
#variable.a_boolean ||
#user.another_boolean? ||
#user.a_third_boolean? && instance_method_retuning_a_boolean
But again we can group the && operators together to show what it's equivalent to:
(#user && #variable && #variable.a_boolean) ||
(#user.another_boolean?) ||
(#user.a_third_boolean? && instance_method_retuning_a_boolean)
Therefore if #user && #variable && #variable.a_boolean == false, then #user.another_boolean? will be evaluated.
I'm not clear what it is you're trying to achieve - so I don't know if the above logic is correct, or how one might "fix" it, but there's your explanation for why the method is being called.
Your expression has a form of:
aaa &&
bbb &&
bbb.foo ||
aaa.bar ||
aaa.baz && something
it may be reformatted as:
aaa && bbb && bbb.foo
||
aaa.bar
||
aaa.baz && something
It's the same, just whitespaces are laid out differently.
As you can see here, not all terms are protected by the initial aaa&&bbb test.
Most probably you meant this:
#result ||= !!( (
#user &&
#variable
)
&&
(
#variable.a_boolean ||
#user.another_boolean? ||
#user.a_third_boolean?
)
&& instance_method_retuning_a_boolean
)
I've added way too many parentheses than needed, but this way you exactly see what's going on.
Hi Yann and welcome to Stackoverflow. Let me give you some examples that may help you understand the reason for your observation.
You correctly stated that:
nil && true
=> nil
but if you chain additional operators without explicitly use brackets then the following happens:
nil && true || true
=> true
This is because the && operator has higher precedence so you could write the same thing like this, and then its clear why the expression does not stop after the first nil:
(nil && true) || true
I found this article pretty helpful: https://womanonrails.com/operator-precedence-ruby.
So for your case if we would put the brackets as it is now we would have the following:
(#user && #variable && #variable.a_boolean) ||
#user.another_boolean? ||
(#user.a_third_boolean? && instance_method_retuning_a_boolean)
This means that even if the first part of the expression results in false, the #user.another_boolean? still gets evaluated.
So what you probably want is putting brackets explicitly:
(#user && #variable) &&
(#variable.a_boolean || #user.another_boolean? || #user.a_third_boolean?) &&
instance_method_retuning_a_boolean
So now you have the first part, which will check if both #user and #variable are not nil. If any of those is nil, the second part will not be evaluated anymore.
Hope this brings some clarity.
You can probably avoid an overly complex boolean expression by adding a guard clause (or two) that separates the prerequisite conditions from the actual result:
def result
return unless #user
return unless #variable
#result ||= #variable.a_boolean ||
#user.another_boolean? ||
#user.a_third_boolean? && instance_method_retuning_a_boolean
end
I'm not sure if this returns the expected result, but you get the idea.

`detect` and `find` return `nil` whereas `find_all` and `select` return a result

With the code below, the byebug trips:
cspg_instance = #game_instances.find do |instance|
instance_end_time = TimeOperation.new(:+, instance.start_time, instance.duration).result
if (event.end_time > instance.start_time && event.end_time <= instance_end_time && instance.events.first.period == event.period)
byebug
end
end
This returns the correct result: (edit: this WAS returning the correct result and now returns nil)
cspg_instance = #game_instances.find do |instance|
instance_end_time = TimeOperation.new(:+, instance.start_time, instance.duration).result
if (event.end_time > instance.start_time && event.end_time <= instance_end_time && instance.events.first.period == event.period)
ok = true
end
ok
end
Yet, it returns nil, with only the 'if' condition (without the if statement); whereas find_all and select both return a value.
cspg_instance = #game_instances.find do |instance|
instance_end_time = TimeOperation.new(:+, instance.start_time, instance.duration).result
(event.end_time > instance.start_time && event.end_time <= instance_end_time && instance.events.first.period == event.period)
end
I wonder if anyone has any idea what to make of this; perhaps a ruby or installation failure?
There are two different issues:
1) Why the version with byebug doesn't work
This is the important piece from the docs of find: Returns the first for which block is not false.
Now let's have a look at your cases:
# just writing "true" here, with no if statement will deliver a result
If you just write true at the end of the block then that true is returned and therefore find finds this entry.
if (event.end_time > instance.start_time && event.end_time <= instance_end_time && instance.events.first.period == event.period)
ok = true
end
ok
This case is similar: If the if condition is true you assign true to the ok variable. Because you call ok again in the last line of the block the block returns true and find finds this element.
if (event.end_time > instance.start_time && event.end_time <= instance_end_time && instance.events.first.period == event.period)
byebug
end
But this example in your code is different. Here you open bundle if the if condition is true. This makes the bundler call the last method call in the find block. Calling bundler doesn't return true therefore the whole block doesn't return true and find doesn't pick these entry.
The solution is to return true in the last line of the find block. Because you have already the condition in your code, you could use that directly without assigning true to a variable first.
– for example like this:
cspg_instance = #game_instances.find do |instance|
instance_end_time = TimeOperation.new(:+, instance.start_time, instance.duration).result
event.end_time > instance.start_time && event.end_time <= instance_end_time && instance.events.first.period == event.period
end
2) Why does find_all and select work, but find does not?
In the comments, you clarified that #game_instances is actually not an Array, but an ActiveRecord::Relation. ActiveRecord::Relation#find works totally different than the method on an Array. Simplified find on such a relation expects an id of a record and returns that record within the scope given by the relation. Calling to_a on the relation loads all records into memory an allows you to use Array#find.
From a performance point of view it would make sense to translate the condition into a SQL condition and only load the one record that matches from the database instead of loading all records and find the correct one in your application.

Combining multiple 'elsif' statements

I try to programm snakes in Ruby. In order to get myself more familiar with Ruby. I define the position of every part of the snake through saving its X and Y value in two 1D arrays one for a X value and one for a Y value.
$x = [2,...]
$y = [2,...]
(What I forgot to tell is that the head of the Snake moves through user input while the rest just inherits its position from the part before like this.)
def length(f)
if $length >= f
$y[f] = $y[f-1]
$x[f] = $x[f-1]
end
end
In order to get a field for the Snake to move around I programmed this.
for a in (1..20)
for b in (1..20)
print " X "
end
puts" "
end
Which gives me a 20*20 field.
I then tried to display every part of the snake like on the field like this.(While also drawing a boarder around the field.)
for a in (1..20)
for b in (1..20)
if a == 1 || a == 20
if b == 1 || b == 20
print " + "
else
print " - "
end
elsif b == 1 || b == 20
print " | "
elsif a == $x[0] && b == $y[0]
body
elsif a == $x[1] && b == $y[1]
body
elsif a == $x[2] && b == $y[2]
body
elsif a == $x[3] && b == $y[3]
body
elsif a == $x[4] && b == $y[4]
body
else
print " "
end
end
puts""
end
This works but if the user is really good/ has a lot of spare time I need to make allot of elsif for every one represents a part of the snake if the snake should have as a limit a length of 100 I would need to make 100 elsif statements.(The body is just:
def body
print " # ".green
end
)
I tried fixing it with a for loop like this:
for c in (1..100)
if a == $x[c] && b == $y[c]
body
end
end
and this
loop do
$x.size.times do |index|
if $x[index] == a && $y[index] == b
body
end
end
break
end
But sadly this didn't gave the desired outcome for this interfered with the ifthat draws the boarders of the field.
Is there a way to combine these multiple elsif statements?
Every help would be highly appreciated. ( Sorry for being to vague in the first draft.)
Recommended Refactorings
NB: You included no sample data in your original post, so your mileage with answers will vary.
You have a number of issues, not just one. Besides not being DRY, your code is also not very testable because it's not broken out into discrete operations. There are a number of things you can (and probably should) do:
Break your "body" stuff into discrete methods.
Use Array or Enumerator methods to simplify the data.
Use dynamic methods to loop over your arrays, rather than fixed ranges or for-loops.
Use case/when statements inside your loop to handle multiple conditionals for the same variable.
In short, you need to refactor your code to be more modular, and to leverage the language to iterate over your data objects rather than using one conditional per element as you're currently doing.
Simplify Your Data Set and Handle Procedurally
As an example, consider the following:
def handle_matched_values array
end
def handle_mismatched_values array
end
paired_array = a.zip b
matched_pairs = paired_array.select { |subarray| subarray[0] == subarray[1] }
unmatched_pairs = paired_array.reject { |subarray| subarray[0] == subarray[1] }
matched_pairs.each { |pair| handle_matched_values pair }
matched_pairs.each { |pair| handle_mismatched_values pair }
In this example, you may not even need an if statement. Instead, you could use Array#select or Array#reject to find indices that match whatever criteria you want, and then call the relevant handler for the results. This has the advantage of being very procedural, and makes it quite clear what data set and handler are being paired. It's also quite readable, which is extremely important.
Dynamic Looping and Case Statements
If you truly need to handle your data within a single loop, use a case statement to clean up your conditions. For example:
# Extract methods to handle each case.
def do_something_with data; end
def do_something_else_with data; end
def handle_borders data; end
# Construct your data any way you want.
paired_array = a.zip b
# Loop over your data to evaluate each pair of values.
paired_array.each do |pair|
case pair
when a == b
do_something_with pair
when a == paired_array.first || paired_array.last
handle_borders pair
else
do_something_else_with pair
end
end
There are certainly plenty of other ways to work pairwise with a large data set. The goal is to give you a basic structure for refactoring your code. The rest is up to you!
I would start with something like this:
(1..20).each do |a|
(1..20).each do |b|
if [1, 20].include?(a)
print([1, 20].include?(b) ? ' + ' : ' - ')
elsif (1..100).any? { |i| a == $x[i] && b == $y[i] }
body
else
print(' ')
end
puts('')
end
end
I suppose this would work as a solution even if it is not that advanced?
loop do
$x.size.times do |index|
if $x[index] == a && $y[index] == b
body
end
end
break
end

Ruby Multiple AND Evaluations For Integer Value

I have, on a few occasions, found myself needing to write a rather verbose if statement in some ruby scripts.
The statement would look something like this:
if long_var_name == 0 && very_different_name == 0 && other_third_var == 0 && additional_variable == 0 && name_five == 0 && longest_variable_name_six == 0
# (possibly even more conditions)
# do stuff here...
end
It seems like there has to be a more elegant way to do this.
The problem is, if and and aren't exactly easy to research with google, as basic english words. So I've come up empty-handed.
Does anyone know a way to shorten this kind of situation?
It can become a nightmare to read when you have even more of them.
Sometimes variable renaming isn't an option.
The names are sometimes vastly different.
Note: I found a clever solution for similar situations with OR:
Ruby Multiple OR Evaluations For String Value
If you have an array and are specifically testing for zero, you can do:
vars1 = [0,0,3,0,0]
vars2 = [0,0,0,0,0]
vars1.all?(&:zero?) # false
vars2.all?(&:zero?) # true
EDIT: Based on OP's added conditions of having different names for the values:
if [long_var_name_1,long_var_name_2,long_var_name_3].all?(&:zero?)
# do stuff here...
end
In your specific case, I would write
if [a, b, c, d, e, f].all? { |var| var == 0 }
There's noting wrong about chaining and conditions IMHO.
Have you thought of breaking it up into logic expressions? Basically break it up into smaller bits of logical groupings and its easier to read e.g.
grp_1_zero = var_1 == 0 && var_2 == 0 && var_3 == 0
grp_2_zero = var_a == 0 && var_b == 0 && var_c == 0
grp_3_zero = var_z == 0 && var_x == 0 && var_y == 0
if grp_1_zero && grp_2_zero && grp_3_zero
#code here
end
Another, for array a:
a == Array.new(a.size,0)
You can use Enumerable#all? and the Symbol#to_proc utilisation of the Fixnum#zero? method.
foo = 0
bar = 0
baz = 0
[foo, bar, baz].all? &:zero?
# => true
one = 1
two = 2
[foo, one, two].any? &:zero?
#=> true
Note that you can also provide any anonymous function for the test.
owns_peanuts = ->(person){ person.inventory.contains :peanuts }
[bill, david, mike].any? &owns_peanuts

proper formatting in long fragments of code

if i have to use unless/if and blocks, code might get really long. This doesnt look nice at all, and my question is how to handle this kind of situation, what is the proper formatting, eg in this hipothetical case?
if some_array.each.all? {|argument| argument.method_one == #some_value} && another_array.method_two == #completely_different_value
#
#some stuff here
#
end
You can divide it in several lines. I think this format is more easier to read
result1 = some_array.each.all? { |argument| argument.method_one == #some_value }
result2 = another_array.method_two == #completely_different_value
if result1 && result2
#
#some stuff here
#
end
There are plenty ways to accomplish your task.
The most common way is to use backslashes as you do in shell prompt:
if some_array.each.all? { |argument| \
argument.method_one == #some_value \
} \
&& another_array.method_two == #completely_different_value \
puts a
end
Also you may silently break the line at dots (dot must be placed at the end of the line in Ruby < 1.9 or at the beginning of the next line as well in Ruby 1.9+.)
"foo bar baz".
reverse.
split.
map(&:capitalize).
join ' '
# ⇒ "Zab Rab Oof"
I recommend extracting the parts into variables
condition1 = some_array.each.all? do |argument|
argument.method_one == #some_value
end
condition2 = another_array.method_two == #completely_different_value
if condition1 && condition2
#
# some stuff here
#
end
or making the conditions into methods
def condition1?(arr)
arr.some_array.each.all? do |argument|
argument.method_one == #some_value
end
end
def condition2?(arr)
arr.method_two == #completely_different_value
end
if condition1?(some_array) && condition2?(another_array)
#
# some stuff here
#
end
extracting into methods has the advantage that your code generally becomes easier to test.

Resources