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.)
source to share
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))}
source to share