Alexey Suslyakov Alexey Suslyakov - 2 months ago 7
Ruby Question

Why not interpreted code can affect "messages" behavior in Ruby?

I've got a validator in ActiveRecord model, in which I faced with some realy strange behavior.

Example:

if status_changed?
p status # output on line below
# <= "my_status_1"
p my_conditions_1 # output on line below
# <= false

if my_conditions_1
errors.add(:status, 'Error1')
status = status_was
end

p status # output on line below
# <= nil

# my_conditions_2 depends on "status variable"
if my_conditions_2
errors.add(:status, 'Error2')
status = 2
end
end


Second condition always failed, because
status
somehow was setted to nil. But when I changed
status
to
self.status
everything started working as expected.

UPDATED

I've got the rule, that in case of assigning attribute I have to use
self
, thanks everyone who explained it. But part of the code's behavior still doesn't obvious to me


More general example:

class Detector
def status
"Everything ok"
end

def check
p status
# <= "Everything ok"
if false
status = "Danger!"
end

p status
# <= nil
end
end

detector = Detector.new
detector.check


Can someone explain it? How not interpreted code can "redirect" message from method to a variable? Is it ok?

Answer

To access object's attribute it's fine to do it with attribute.

While updating this attribute one should be using self.attribute, because otherwise how should Rails know you mean to set its attribute, not define local variable?

Rule of thumb: use self for assigning attribute, don't use it for reading the attribute.

EDIT

Regarding your update:

As @Jörg W Mittag said (who would say better?):

Well, status is un-initialized, und un-initialized local variables evaluate to nil, just like instance variables.

To make your code sample behave as you expect you would want to call status as a method. Look:

class Detector
  def status
    "Everything ok"
  end

  def check
    p status
    # <= "Everything ok"

    status = "Danger!" if false

    status() # or method(:status).call
    # <= "Everything ok"
  end
end

First p status works because Ruby looks for local variable status. When it does not find it, it looks for a method called status (by method lookup). So it prints it "Everything ok".

Then in parses the if statement and sees, that there's un-initialized local variable status. Thus, when you reference it, it is legitimately nil.

So in other words, make Ruby know exactly, what you mean.