Foo= methods don't get called inside instance scope without


#1

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?


#2

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_expressions.html#S3
(you’ll have to scroll down a bit).

Kind regards

robert


#3

On 5/20/06, Robert K. removed_email_address@domain.invalid wrote:

http://ruby-doc.org/docs/ProgrammingRuby/html/tut_expressions.html#S3
(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.


#4

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.


#5

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 :slight_smile:


#6

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