From: Alder Green Date: 2006-05-21T19:06:21+09:00 Subject: Re: foo= methods don't get called inside instance scope without explicit receiver On 5/21/06, bpettichord@gmail.com wrote: > 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. > > > 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 #=> # 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 :) -- -Alder