Changing module level variables in anonymous array in Ruby

I am learning Ruby and think I was smart in the following piece of code:

[@start,@end].map!{ |time| time += operation == :add ? amount : -(amount) }

      

where @start, @end are two module level variables, the operation can be one of the following: add or: sub, and amount is a floating point amount to customize both @start and @end.

Provided this only saves me a line of code, but why doesn't this approach work, and how can I get something like this that does?

(My expected output for @ start / @ end will be modified accordingly, however the unit tests show they remain the same.)

+2


source to share


3 answers


In Ruby, it is important to remember the distinction between variables and the objects they store. Simply setting a variable will never change the object referenced by that variable. When you do a += b

, it will simply be shortened for a = a + b

. This way you are assigning a new value to the variable a without changing the object that was there before, or changing any other references to that object. So changing the variable time

doesn't change @start

.

To assign an instance variable, you need to assign that instance variable. Here's a way to do what you were looking for:

operation = :+
amount = 12
@start, @end = [@start, @end].map {|time| time.send(operation, amount)}

      



You will notice that we are not running into this business :add

and :sub

either - we can simply pass in the actual name of the message we want to send (I used + in this case, but it could be anything).

If you had a large, dynamically generated list of ivars that you wanted to install, this is a little more complicated. The only difference is to get and set the ivars by name.

ivars = [:@start, :@end, :@something_else]
operation = :+
amount = 12
ivars.each {|ivar| instance_variable_set(ivar, instance_variable_get(ivar).send(operation, amount))}

      

+5


source


The operator +=

changes the value time

, but returns the old value time

, so the correct code is:

@start,@end = [@start,@end].map!{ |time| time + (operation == :add ? amount : -amount) }

      



EDIT Updated the code to actually change @start

and @end

.

+3


source


The operation of adding in a block does not change the time, it returns a new value. Therefore, the elements in the array do not change, they are replaced.

+2


source







All Articles