I’ve got class A
class A
attr_reader :b
def b=param
@b = param
print "success"
end
end
>> a = A.new
>> a.b = "hello world!"
#> "success"
#> "hello world!"
>> a.b << " and goodbye!"
#> "helo world! and goodbye!"
Where is my “success” ? 🙂
I want to print ‘success’ EVERY TIME my variable changed.
I can’t just write
def b<<param
@b << param
print "success"
end
Here is the tricky part that you’re missing: The variable
@bdoes not change in your example. It still contains the same string object you initially set it to. It’s the string itself that is changing. This distinction is extremely important, and if you don’t grasp it, you’ll find your program plagued by a thousand subtle bugs. Here it is:Objects and variables are two independent things. Variables are like slots, and objects are the things you put in them. Only the
=operator puts a new object into a slot*; everything else sends messages to the object in the slot. When you write@thing = "hello", that puts the String object"hello"into the slot@thing. When you write@thing << " world", you aren’t setting@thingto contain a new object; you’re leaving the same object in there, but adding" world"onto the end of the string that the object represents. This also means that any other slots holding the same object will also find their strings changed!If you want to get around this, you’ll have to use a proxy object (to receive the
<<message) like ormuriauga described instead of storing the string directly. Ruby’s Delegator might be useful for that. Though I would advise considering whether you really need this, because it does complicate your design and there is often a better way to do it.* OK, that’s a little bit hand-wavy. There’s also the special
instance_variable_setmethod that can set instance variables. But there wouldn’t be any way to write that method yourself without using the=operator andeval().