Reputation: 21
def foo(bar)
'return value'
end
foo 'bar' # => "return value"
def foo=(bar)
'return value'
end
foo = 'bar' # => "bar"
send :foo=, 'bar' # => "return value"
I want foo = 'bar'
to return "return value"
but not to use send
for this purpose. How can I do this?
I need a desired behavior in my gem. Here is an example:
car = Car.new
car.gear # => :first
car.next_gear # => :second
car.gear # => :second
car.gear = :fourth # => false
car.gear # => :second
car.gear = :third # => :third
car.gear # => :third
Upvotes: 2
Views: 93
Reputation: 13574
Assignments always return the right hand side of an assignment.
Have a look at the ruby documentation for details:
Methods that end with an equals sign indicate an assignment method. For assignment methods the return value is ignored, the arguments are returned instead.
Having said that, foo = bar
also assigns to a local variable foo
instead of using the foo=
method. Again, this is defined in the ruby docs:
When using method assignment you must always have a receiver. If you do not have a receiver Ruby assumes you are assigning to a local variable
You can test that by running
local_variables #=> []
def foo=(bar);end
foo = 42
local_variables #=> [:foo]
You see that the local variable foo
was created. Better use self.foo = 'bar'
.
To address your specific problem with your gem: Follow Neil's advice and use an extra method like change_gear
for what you want to do. He gave you good council in his comments.
Upvotes: 5
Reputation: 78473
It's a Ruby gotcha: the return value of accessor methods get ignored.
This code will make it more clear what is actually happening:
#!/usr/bin/env ruby
def foo(bar)
p "called :foo w/ #{bar.inspect}"
end
def foo=(bar)
p "called :foo= with #{bar.inspect}"
end
ret = (foo :bar1) # calls foo(bar)
p "ret: #{ret}" # "ret: called :foo w/ :bar1"
ret = (foo = :bar2) # assigns a local variable foo = 'bar2'
p "ret: #{ret}" # "ret: bar2"
ret = (send :foo=, :bar3) # calls foo=(bar), returns what p returns
p "ret: #{ret}" # "ret: called :foo= with :bar3"
ret = (self.foo = :bar4) # calls foo=(bar), returns ???
p "ret: #{ret}" # "ret: bar4"
Basically, the Ruby parser (in 2.1 at least) behaves as if self.foo=
was calling an accessor method (even if it actually isn't assigning anything), and will always return the value passed to it irrespective of what you sent it, rather than the accessor's return value.
Demonstration:
#!/usr/bin/env ruby
class << self
attr_accessor :foo
def foo=(bar)
p "called :foo= with #{bar.inspect}"
@foo = :baz
end
end
ret = (self.foo = :bar)
p "ret: #{ret} vs @foo: #{@foo.inspect}"
Outputs:
"called :foo= with :bar"
"ret: bar vs @foo: :baz"
Edit: hat @tessi for the reference:
Methods that end with an equals sign indicate an assignment method. For assignment methods the return value is ignored, the arguments are returned instead.
Upvotes: 2
Reputation: 2469
I think the reason why it's failing is that local variable names take precedence over method names when they are defined.
So you need to use send
so that self
knows it's looking for a method instead of a variable.
Upvotes: 1