问题
Unexpected Return Value from Method: Expecting Symbol
I have the following Ticket class:
class Ticket
VALID_STATES = %i[open closed invalid wontfix]
attr_reader :status
def status= new_state
new_state = new_state.to_sym
@status = new_state
end
end
When passed a String rather than a Symbol, the setter method unexpectedly returns a String, even though the correct value is being returned by the getter method. For example:
t = Ticket.new
t.status = 'closed'
#=> "closed"
t.status
#=> :closed
It looks like the correct value is being stored as a Symbol, but I'm at a loss as to why the method is returning "closed"
at the REPL when the last expression evaluated should return :closed
. My expectation is that the expression in question ought to resolve as @status = :closed
, and therefore should return a Symbol.
Can anyone explain why I'm getting a String rather than a Symbol as the return value from the setter method?
Caveats and Bike-Shedding Prevention
- I know this example could just use
@status = new_state.to_sym
rather than assigning back to new_state, but there's intermediate code that was removed to create this minimal example. I didn't want to change the code too much, as that defeats the point of showing what my real code is doing. It doesn't seem to make a difference for this specific problem anyway; I've tried it both ways. - I tried this with Ruby 2.3.1, 2.4.0-preview2, and JRuby 9.1.4.0, so it's not version-specific.
- Various debugging attempts ran afoul of other issues specific to the REPL's toplevel in both Pry and IRB, which I will open as a separate question. The point here is that trying to debug with alternative abstractions like
def foo=(str); @foo = str.to_sym; end
leads further down the rabbit hole. - It's extremely possible that the problem exists between the keyboard and the chair, but the focus of the question is really about why the return value is not of the expected class.
回答1:
It's expected. From the documentation:
Note that for assignment methods the return value will always be ignored. Instead, the argument will be returned:
def a=(value) return 1 + value end p(a = 5) # prints 5
Ruby allows you to chain assignments:
foo = bar = 'closed'
The above assigns "closed"
to both, foo
and bar
.
Returning the argument and ignoring the method's return value lets you replace bar
with a method call:
foo = t.status = 'closed'
IMO it would be quite surprising if the above would assign :closed
to foo
.
If you really want the return value, use send
or public_send
:
def a=(value)
return 1 + value
end
p(a = 5) # prints 5
p(send(:a=, 5)) # prints 6
回答2:
The answer is rather simple: in Ruby, assignments evaluate to the value being assigned. There's nothing special about method assignments.
来源:https://stackoverflow.com/questions/40421090/why-is-a-ruby-setter-method-returning-a-string-rather-than-a-symbol-as-the-last