tags:

views:

45

answers:

3

Hi. 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
+1  A: 

You will have to make b be a class of your own where you define all the methods you want to do what you want.

Although you can, actually, do something like this:

class A
  attr_reader :b
  def initialize
      @b = ""
      augment_b!
  end

  def augment_b!
      class << @b
        alias_method :ltlt, :<< 
        def << args
          self.ltlt args
          print "success"
        end
      end
  end
  def b=param
    @b = param.to_s
    augment_b!
    print "success"
  end
end

I don't recommend it though.

Let's see if I can explain this to you.

the b= method is called when you assign to the b property. but when you call a.b << you are accessing b and then calling << on that object, and thus only ever interfacing with your A class when accessing b. What I did was redefining that << method on b.

ormuriauga
a.b << 'd' => `SystemStackError: stack level too deep`
fl00r
yeah, sorry. fixed it now. hadn't tested it
ormuriauga
looks too hacky :) isn't in Ruby more simple way for inspecting instance variables accessing and changing?
fl00r
yes it is too hacky. Like I said, I don't recommend it. If you give the object away you can't really detect changes in any simple way I know of. what are you trying to achieve?
ormuriauga
A: 

Your method b wasn't called on the goodbye line.

The zero-parameter accessor method you defined was called, and the returned string was passed to the << method of class String.

You have defined two methods with similar names: one is b and the other is b=.

In the first method call you really called the b= method and then in the second you called the plain b. Try this:

a.b = "hello world!"
success=> "hello world!"
a.b = '' << " and goodbye!"
success=> " and goodbye!"
DigitalRoss
but it asigned to `b` somehow. what method is invoked for assigning? setter I suppose - so in my setter I call `print`
fl00r
Ok, I've added an update. It's interesting to note the creative parsing of method names with embedded spaces.
DigitalRoss
As I mentioned earlier — it is quite strange if I can't control my instance variable state and changing via methods difficult from `=`
fl00r
for me simplier to do this: `a.b = 'hi'; a.b = a.b+" bye"` — but it is not cool :) and my variable is not String but Array
fl00r
all `attr_Accessor :b` does is this:def b; @b; end` and `def b=(_b); @b = _b; end`. If you want something else, you have to do something else :)
ormuriauga
+4  A: 

Here is the tricky part that you're missing: The variable @b does 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 @thing to 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_set method that can set instance variables. But there wouldn't be any way to write that method yourself without using the = operator and eval().

Chuck
thanx, It is very interesting information
fl00r
As well as `instance_variable_set`, there's also `instance_eval`. But that's black magic you don't need to use.
Andrew Grimm