Forum: Ruby foo= methods don't get called inside instance scope without

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
Alder G. (Guest)
on 2006-05-20 22:26
(Received via mailing list)
irb(main):001:0> class Bar; def foo=(val); puts "Bar#foo= called!"; end;
end
irb(main):002:0> Bar.new.instance_eval { foo = 1 }          #=> no
output
irb(main):003:0> Bar.new.instance_eval { foo= 1 }           #=> no
output
irb(main):004:0> Bar.new.instance_eval { self.foo = 1 }   #=> Bar#foo=
called!

It seems like the only way to call Bar#foo= inside and instance of Bar
is by prefixing self explicitly. Am I right about this?
Robert K. (Guest)
on 2006-05-20 22:30
(Received via mailing list)
2006/5/20, Alder G. <removed_email_address@domain.invalid>:
> irb(main):001:0> class Bar; def foo=(val); puts "Bar#foo= called!"; end; end
> irb(main):002:0> Bar.new.instance_eval { foo = 1 }          #=> no output
> irb(main):003:0> Bar.new.instance_eval { foo= 1 }           #=> no output
> irb(main):004:0> Bar.new.instance_eval { self.foo = 1 }   #=> Bar#foo= called!
>
> It seems like the only way to call Bar#foo= inside and instance of Bar
> is by prefixing self explicitly. Am I right about this?

Yes.  foo= is a local variable assignment. See
http://ruby-doc.org/docs/ProgrammingRuby/html/tut_...
(you'll have to scroll down a bit).

Kind regards

robert
Alder G. (Guest)
on 2006-05-20 22:42
(Received via mailing list)
On 5/20/06, Robert K. <removed_email_address@domain.invalid> wrote:
> http://ruby-doc.org/docs/ProgrammingRuby/html/tut_...
> (you'll have to scroll down a bit).
>
> Kind regards
>
> robert
>
> --
> Have a look: http://www.flickr.com/photos/fussel-foto/
>
>

Yup. Seems like assignments to local variable take foo precedence over
calling foo=. Kind of a shame, since I have to use self.foo= whenever
I want to call foo=. A bit ugly. Which is why I wanted to make sure
there's no alternative.
(Guest)
on 2006-05-21 12:06
(Received via mailing list)
I'm not sure of your specific situation, but i've found this to help in
these circumstances:

class Module
  def dsl_accessor(*symbols)
    symbols.each { |sym|
      class_eval %{
        def #{sym}(*val)
          if val.empty?
            @#{sym}
          else
            @#{sym} = val.size == 1 ? val[0] : val
          end
        end
      }
    }
  end
end

Bar.dsl_accessor :foo
b = Bar.new.
b.instance_eval { foo 1 }          #=> 1
b.instance_eval { foo }             #=> 1

See http://www.artima.com/rubycs/articles/ruby_as_dsl3.html for more.
Alder G. (Guest)
on 2006-05-21 14:07
(Received via mailing list)
On 5/21/06, removed_email_address@domain.invalid 
<removed_email_address@domain.invalid> wrote:
>           else
> b.instance_eval { foo 1 }          #=> 1
> b.instance_eval { foo }             #=> 1
>
> See http://www.artima.com/rubycs/articles/ruby_as_dsl3.html for more.
>
>
>

Interesting code. Unfortunately, it's not relevant to the issue I
raised.

That issue stems from the way the Ruby interpreter handles names. As
you know, regular names in Ruby can be both method calls and local
variables. Whever a name could be treated as either one of these, the
interpreter prefers to treat it as a local variable. Example:

class Foo
  def bar
    @fred.bar
  end

  def bar=(val)
    @fred.bar = val
  end

  def initialize
    @fred = Struct.new(:bar).new
  end
end

foo = Foo.new

Outside of instance foo's scope, the accessor methods we built work as
expected:

foo.bar = 1
foo.instance_eval('@fred.bar')  #=> 1

However, notice what happens once we try calling them from within foo's
scope:

foo.instance_eval('bar')  #=> 1   -- Ok so far...
foo.instance_eval('bar = 22')
foo  #=> #<Foo:0x2c8bb10 @fred=#<struct #<Class:0x2c8bac8> bar=1>>

Whoops! @fred.baz wasn't changed. So what was?

What happened is that the interpreter could interpret "bar = 22" in
foo's scope in two different ways:

1. self.bar=(22)  # What we actually want
2. create local variable bar and assign to it the value 22

And since it always prefers the local variable interpretation, number
2 is what actually happens. In fact, now even method bar doesn't work
within foo:

foo.instance_eval('bar') #=> 22
foo.instance_eval('@fred.bar') #=> 1

Within foo, bar returns 22, but @fred.bar is still 1. That's because
now foo also has two possible interpretations, and the local-variable
interpretation is again prefered, so bar returns the value of local
variable bar.

I can see two ways to deal with this issue:

1. Always prefix foo.bar= calls with self within foo. This eliminates
the  local-variable possible interpration. However, it is kind of
ugly, and it's easy to introduce elusive bugs by forgetting the
prefix.

2. Avoid using the bar= syntax. You can either use a seperate setter
method set_bar, or what the code you posted does (set and return bar
to arg if supplied, just return bar otherwise).

Hope the issue is clearer :)
Robert K. (Guest)
on 2006-05-21 16:43
(Received via mailing list)
2006/5/21, Alder G. <removed_email_address@domain.invalid>:
> I can see two ways to deal with this issue:
>
> 1. Always prefix foo.bar= calls with self within foo. This eliminates
> the  local-variable possible interpration. However, it is kind of
> ugly, and it's easy to introduce elusive bugs by forgetting the
> prefix.
>
> 2. Avoid using the bar= syntax. You can either use a seperate setter
> method set_bar, or what the code you posted does (set and return bar
> to arg if supplied, just return bar otherwise).

A nice variant of 2 makes use of the fact that a method can receive a
hash without using curly braces:

>> class Foo
>> def init(params)
>> params.each {|k,v| print k,"=",v,"\n"}
>> end
>> end
=> nil
>> Foo.new.init(:name => "foo", :size => 123)
size=123
name=foo
=> {:size=>123, :name=>"foo"}

Instead of printing you can of course use key value pairs for setting
instance variables or invoking setters.

Kind regards

robert
This topic is locked and can not be replied to.