On 5/21/06, [email protected] [email protected] 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:
- self.bar=(22) # What we actually want
- 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:
-
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.
-
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 