Emil Trotuar
Emil Trotuar

Reputation: 21

Syntactic sugar forces me to use an ugly statement

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?

Update

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

Answers (4)

tessi
tessi

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

Denis de Bernardy
Denis de Bernardy

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

PericlesTheo
PericlesTheo

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

Alex D
Alex D

Reputation: 30445

You need to do this:

 self.foo = 'bar'

Upvotes: 0

Related Questions