Ok maybe this is simple but…
given this:
arr = ("a".."z").to_a
arr
=> ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]
..and that I’m trying to change all “arr” values to “bad”
why isn’t this working ?
arr.each { |v| v = "bad" }
arr
=> ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]
Answers suggested that “v” is a local variable to the block (a “copy” of the array value) and I fully understand that (and never puzzled me before) but then
.. why it is working if array elements are objects ?
class Person
def initialize
@age = 0
end
attr_accessor :age
end
kid = Person.new
man = Person.new
arr = [kid, man]
arr.each { |p| p.age = 50 }
arr[0]
=> #<Person:0xf98298 @age=50>
isn’t here “p” still local to the block here?
but then it really affects the objects, how come ?
I’ll expand upon @pst’s comment:
Because
eachiterates through the array and puts each item into the block you’ve given as a local variablev, asvis not a reference to the arrayarr.eachdoes not give back an array, for that you would usemap(see @benjaminbenben’s answer). Therefore assigning it does not “work”.Here you put each item in
arrinto the local variablev, but you’ve also referred to the array itself in the block, hence you are able to assign to the array and use the local variablevto find an index that corresponds to the contents ofv(but you may find this wouldn’t work as you expect when the items are not all unique).Here, again you’ve filled the local variable
pwith each item/object inarr, but then you’ve accessed each item via a method, so you are able to change that item – you are not changing the array. It’s different because the reference is to the contents of the local variable, which you’ve mixed up with being a reference to the array. They are separate things.In response to the comment below:
It’s all about who’s referring to whom when.
Try this:
vreferred to 2 different objects there.vis not a fixed thing, it’s a name. At first it refers to#<Person:0x000001008de248 @age=0>, then it refers to#<Person:0x00000100877e80 @age=0>.Now try this:
They are all objects but nothing was updated or “worked”. Why? Because when the block is first entered,
vrefers to the item in the array that was yielded (given). So on first iterationvis#<Person:0x00000100877e80 @age=0>.But, we then assign
"bad"tov. We are not assigning"bad"to the first index of the array because we aren’t referencing the array at all.arris the reference to the array. Putarrinside the block and you can alter it:Why then does
arr.each { |p| p.age = 50 }update the items in the array? Becauseprefers to the objects that also happen to be in the array. On first iterationprefers to the object also known askid, andkidhas anage=method and you stick50in it.kidis also the first item in the array, but you’re talking aboutkidnot the array. You could do this:At first,
preferred to the object that also happened to be in the array (that’s where it was yielded from), but thenpwas made to refer to"bad".eachiterates over the array and yields a value on each iteration. You only get the value not the array. If you want to update an array you either do:or
as
mapreturns an array filled with the return value of the block.map!will update the reference you called it on with an array filled with the return value of the block. Generally, it’s a bad idea to update an object when iterating over it anyway. I find it’s always better to think of it as creating a new array, and then you can use the!methods as a shortcut.