I've been bitten a couple of times by forgetting that x = y
in Ruby makes x refer to the same object as y; I'm too used to languages where it means, in Ruby terms, x = y.dup
. Forgetting this, I inadvertently change y
when I think it's safe on the right side of the assignment.
I can see that it would make sense to avoid simple x = y
assignments without a special reason, but the same thing can be lurking in other places such as
name = (person.last_name.blank? ? 'unknown' : person.last_name)
where a later name << title
would actually be changing person.last_name and not just name.
If this has happened to you, too, how have you learned to avoid it? Are there certain red flags or patterns to look for? Do you look with suspicion at each assignment you make? Do you use .dup
a lot? I don't know if Ruby's usage will ever become second nature to me, so any useful tips would be welcome.
This may sound unorthodox in a (essentially imperative) language like Ruby, but my advice is: avoid collateral damages by not updating objects at all (except when strictly necessary); create new ones instead. You pay a bit of performance but you'll get code which is clearer, more compact, more modular and easier to debug.
http://en.wikipedia.org/wiki/Functional_programming
So, in your example, just create a new string with a new name:
complete_name = name + title
Just an addition to tokland's answer:
Functional approach insists on immutability - i.e. not altering existing objects, but creating another whenever you want to change the original one. This is somewhat against the object-orientated paradigm that Ruby also brings (objects keep their state internally, which can be altered by calling methods on it), so you have to balance a bit between the two approaches (on the other hand, we benefit by having multiple paradigms easily accessible in a single language).
So, three things to remember for now:
- Learn what assignment in Ruby is: nothing but naming an object. So, when you say
y=x
, you are only saying "we give another name y
to whatever was named x
".
name << title
mutates object called name
.
name += title
takes objects named name
and title
, concatenates them into another object, and assigns that new object name name
. It doesn't mutate anything.
I also came across such a situation and it resulted in a bug, which I took half a day to figure out. I essentially did something like this
hash = {....}
filename = object.file_name
hash.each |k, v| {file_name.gsub!(k, v) if file_name.include? k}
This code was inside a loop and in the loop, I expected the variable file_name
to be again set to original value. But the object.file_name was changed, as I was performing file_name.gsub!
. There are 2 ways to solve this. Either replace the .gsub!
call with file_name = file_name.gsub
or do file_name = object.file_name.dup
. I opted for the second option.
I think we should be careful with methods having !
and <<
, as they change the original object on which they are acting, especially after assignments like this.
A method should not modify a variable (e.g. by using the shift operator) unless its definition says it will modify it.
So: never modify an object in a method that didn't either (a) create it or (b) document that it modifies it.