Why does variable not change value in an '.each' where an assignment is happening?

I have this line, where the input variables in the array can come in any form — string, integer, fixnum, float (ps, I know that sales_price as a currency should really be BigDecimal or the like on instantiation, this is just to illustrate my question):

[quantity, sales_price].each { |multiplicand| multiplicand = BigDecimal.new(multiplicand.to_s) if multiplicand.class != BigDecimal}

So if (rdb:1) quantity = 50 (rdb:1) quantity.class Fixnum (rdb:1) sales_price = 50 (rdb:1) sales_price.class Float

And I call this: (rdb:1) [quantity, sales_price].each { |multiplicand| multiplicand = BigDecimal.new(multiplicand.to_s) if multiplicand.class != BigDecimal} [50, 1.99]

Why don’t the variables change? (rdb:1) quantity.class Fixnum (rdb:1) sales_price.class Float (rdb:1)

Because if I do this: [quantity, sales_price].map { |multiplicand| multiplicand = BigDecimal.new(multiplicand.to_s) if multiplicand.class != BigDecimal}

I do get: [#BigDecimal:3b4f4f8,‘0.5E2’,4(8), #BigDecimal:3b4f46c,‘0.199E1’,8(8)]

… so I know that the logic is working.

Shouldn’t an operation in an each change the input? — I thought the input should be by ref.

[snip]

All each does is iterate over a collection, yielding to the block. when you do multiplicand = blah, you are just setting the local variable multiplicand to point at some new object - you're not mutating the actual object.

Fred

I have this line, where the input variables in the array can come in any

form — string, integer, fixnum, float (ps, I know that sales_price as a

currency should really be BigDecimal or the like on instantiation, this is

just to illustrate my question):

[quantity, sales_price].each { |multiplicand| multiplicand =

BigDecimal.new(multiplicand.to_s) if multiplicand.class != BigDecimal}

[snip]

All each does is iterate over a collection, yielding to the block.

when you do multiplicand = blah, you are just setting the local

variable multiplicand to point at some new object - you’re not

mutating the actual object.

I see, I guess I got confused as I use ActiveRecord so much and within a each block, by setting values and calling save it of course saves the object. Is there a way to achieve what I am presenting, though, passing a batch of variables and having them operated on and changed?

This situation: (rdb:1) v1 = 1 1 (rdb:1) v2 = 2 2 (rdb:1) v3 = 3 3 (rdb:1) arr = [v1,v2,v3] [1, 2, 3] (rdb:1) arr = arr.map{|i| i = i + 1} [2, 3, 4]

However what I would want is to see the variable values changed but they are not:

(rdb:1) v1 1 (rdb:1) v2 2

Excuse my ingenuity, but I am trying to understand… if I have an array of variables it seems that when the array is created, the elements which were variables are substituted by values, and there is no reference:

(rdb:1) arr = [v1,v2,v3] [1, 2, 3] (rdb:1) v1 1 (rdb:1) arr[0]=5 5 (rdb:1) v1 1

This must sound like kindergarten but apparently I have been working albeit successfully under some very false premises.

I see, I guess I got confused as I use ActiveRecord so much and within a each block, by setting values and calling save it of course saves the object. Is there a way to achieve what I am presenting, though, passing a batch of variables and having them operated on and changed?

that is very different. doing some_object.foo = 'bar' is calling a method on an object that changes its state, whereas foo = bar just points a local variable at something new. (and in addition Fixnums/BigDecimal are immutable - there is no method you can call on a number to change it into another nmber)

Excuse my ingenuity, but I am trying to understand... if I have an array of variables it seems that when the array is created, the elements which were variables are substituted by values, and there is no reference:

(rdb:1) arr = [v1,v2,v3] [1, 2, 3]

Without resorting to dirty tricks, you can't change the values of local variables other than by actually doing v1 = 2. arr does NOT contain [v1,v2,v3]. It contains 3 objects, and the local variables v1,v2,v3 just happen to also be referencing those same objects.

Fred

On Tue, Feb 8, 2011 at 9:06 AM, Frederick Cheung <frederick.che...@gmail.com

I see, I guess I got confused as I use ActiveRecord so much and within a

each block, by setting values and calling save it of course saves the

object. Is there a way to achieve what I am presenting, though, passing a

batch of variables and having them operated on and changed?

that is very different. doing some_object.foo = ‘bar’ is calling a

method on an object that changes its state, whereas

foo = bar just points a local variable at something new. (and in

addition Fixnums/BigDecimal are immutable - there is no method you can

call on a number to change it into another nmber)

I think I am starting to see, maybe. Hopefully not being too annoying here.

It seems rather strange to me that if you have an object, i.e. a string that it should behave differently than my Account object… the string#=() method is still a property, right? – no different really than account#name=(), so seems it would be logical that if I can do account.name = ‘x’ within an iterator, that doing string=‘y’ should also work, but as we see it does not (lets put the Fixnums and BigDecimal aside as I understand as a property they are re-created rather than modified).

arr.each {|account| account.name = ‘x’}

arr.each {|string| string=(‘y’)}

… they seem to me to be the same thing, unless objects are really not the same.

But with another experiment I see something interesting — if I instantiate a string formally as an object, then I do get the by reference relationship in an array:

ruby-1.9.2-p136 :098 > str = String.new(‘David’)

=> “David” ruby-1.9.2-p136 :099 > arr = [str] => [“David”] ruby-1.9.2-p136 :100 > arr.each {|s| str = ‘new val’} => [“David”] ruby-1.9.2-p136 :101 > str

=> “new val”

But if I just do it as a normal assignment, I do not:

ruby-1.9.2-p136 :106 > string = ‘David’ => “David” ruby-1.9.2-p136 :107 > arr = [string]

=> [“David”] ruby-1.9.2-p136 :108 > arr.each {|s| s = ‘new val’} => [“David”] ruby-1.9.2-p136 :109 > string => “David”

I thought that doing

str = ‘David’

was just a shortcut to doing

str = String.new(‘David’)

But apparently the behavior at least in the context of an array is different. Wow!

Phil

On Tue, Feb 8, 2011 at 9:06 AM, Frederick Cheung <frederick.che...@gmail.com

I see, I guess I got confused as I use ActiveRecord so much and within a

each block, by setting values and calling save it of course saves the

object. Is there a way to achieve what I am presenting, though, passing a

batch of variables and having them operated on and changed?

that is very different. doing some_object.foo = ‘bar’ is calling a

method on an object that changes its state, whereas

foo = bar just points a local variable at something new. (and in

addition Fixnums/BigDecimal are immutable - there is no method you can

call on a number to change it into another nmber)

I think I am starting to see, maybe. Hopefully not being too annoying here.

It seems rather strange to me that if you have an object, i.e. a string that it should behave differently than my Account object… the string#=() method is still a property, right? – no different really than account#name=(), so seems it would be logical that if I can do account.name = ‘x’ within an iterator, that doing string=‘y’ should also work, but as we see it does not (lets put the Fixnums and BigDecimal aside as I understand as a property they are re-created rather than modified).

arr.each {|account| account.name = ‘x’}

arr.each {|string| string=(‘y’)}

… they seem to me to be the same thing, unless objects are really not the same.

But with another experiment I see something interesting — if I instantiate a string formally as an object, then I do get the by reference relationship in an array:

ruby-1.9.2-p136 :098 > str = String.new(‘David’)

=> “David” ruby-1.9.2-p136 :099 > arr = [str] => [“David”] ruby-1.9.2-p136 :100 > arr.each {|s| str = ‘new val’} => [“David”] ruby-1.9.2-p136 :101 > str

=> “new val”

But if I just do it as a normal assignment, I do not:

ruby-1.9.2-p136 :106 > string = ‘David’ => “David” ruby-1.9.2-p136 :107 > arr = [string]

=> [“David”] ruby-1.9.2-p136 :108 > arr.each {|s| s = ‘new val’} => [“David”] ruby-1.9.2-p136 :109 > string => “David”

In the first case, you explicitly changed the value of the local variable str… it didn’t change the array contents. In the second case you only changed the value inside the scope of the block that was enumerating through. Whether you created the string with String.new or not won’t make a difference.

Ruby does pass object by reference, but when you do s = “whatever” in the block, you’re creating a new local variable that only lives as long as the scope of the block.

If you want to modify an array of strings, for example, you could do:

arr = [“John”, “Doe”]

=> [“John”,“Doe”]

arr.each{|s| s.replace(“blah”) }

=> [“blah”,“blah”]

s.replace actually modifies the object, instead of creating a new one.

Phil

On Tue, Feb 8, 2011 at 9:06 AM, Frederick Cheung <frederick.che...@gmail.com

I see, I guess I got confused as I use ActiveRecord so much and within a

each block, by setting values and calling save it of course saves the

object. Is there a way to achieve what I am presenting, though, passing a

batch of variables and having them operated on and changed?

that is very different. doing some_object.foo = ‘bar’ is calling a

method on an object that changes its state, whereas

foo = bar just points a local variable at something new. (and in

addition Fixnums/BigDecimal are immutable - there is no method you can

call on a number to change it into another nmber)

I think I am starting to see, maybe. Hopefully not being too annoying here.

It seems rather strange to me that if you have an object, i.e. a string that it should behave differently than my Account object… the string#=() method is still a property, right? – no different really than account#name=(), so seems it would be logical that if I can do account.name = ‘x’ within an iterator, that doing string=‘y’ should also work, but as we see it does not (lets put the Fixnums and BigDecimal aside as I understand as a property they are re-created rather than modified).

arr.each {|account| account.name = ‘x’}

arr.each {|string| string=(‘y’)}

… they seem to me to be the same thing, unless objects are really not the same.

But with another experiment I see something interesting — if I instantiate a string formally as an object, then I do get the by reference relationship in an array:

ruby-1.9.2-p136 :098 > str = String.new(‘David’)

=> “David” ruby-1.9.2-p136 :099 > arr = [str] => [“David”] ruby-1.9.2-p136 :100 > arr.each {|s| str = ‘new val’} => [“David”] ruby-1.9.2-p136 :101 > str

=> “new val”

But if I just do it as a normal assignment, I do not:

ruby-1.9.2-p136 :106 > string = ‘David’ => “David” ruby-1.9.2-p136 :107 > arr = [string]

=> [“David”] ruby-1.9.2-p136 :108 > arr.each {|s| s = ‘new val’} => [“David”] ruby-1.9.2-p136 :109 > string => “David”

In the first case, you explicitly changed the value of the local variable str… it didn’t change the array contents. In the second case you only changed the value inside the scope of the block that was enumerating through. Whether you created the string with String.new or not won’t make a difference.

Oh, right, I see my errror

Ruby does pass object by reference, but when you do s = “whatever” in the block, you’re creating a new local variable that only lives as long as the scope of the block.

If you want to modify an array of strings, for example, you could do:

arr = [“John”, “Doe”]

=> [“John”,“Doe”]

arr.each{|s| s.replace(“blah”) }

=> [“blah”,“blah”]

s.replace actually modifies the object, instead of creating a new one.

Perfect, now I think I’ve got it, thanks!

If you want to modify an array of strings, for example, you could do: arr = ["John", "Doe"] => ["John","Doe"] arr.each{|s| s.replace("blah") } => ["blah","blah"] s.replace actually modifies the object, instead of creating a new one.

Also, depending on your use-case, consider the enumerable methods ".inject", ".collect", et al:

  >> x = [1,2,3]   => [1, 2, 3]   >> x.collect { |y| y*y }   => [1, 4, 9]   >>

If you want to modify an array of strings, for example, you could do:

arr = [“John”, “Doe”]

=> [“John”,“Doe”]

arr.each{|s| s.replace(“blah”) }

=> [“blah”,“blah”]

s.replace actually modifies the object, instead of creating a new one.

Also, depending on your use-case, consider the enumerable methods

“.inject”, “.collect”, et al:

Thanks… #inject is new to me and looks very cool. From what I understand collect and map are the same function, is that your understanding too?

That's what the documentation says...