Wednesday, October 24, 2007

Rails, models and modifying columns

I had my first WTF moment with Ruby and Rails over the past couple of days. I'm working on a personal project (you'll see it soon), and I'm using Rails. It's an opportunity to learn a new language, and see if the buzz around Rails is true.

Anyways, I was having problem with a model, here's the code I had:

class Call < ActiveRecord::Base
  def my_status(user)
    if (a_party == user)
      a_party_status
    else
      b_party_status
    end
  end

  def update_my_status(user, status)
    if (a_party == user)
      a_party_status = status
    else
      b_party_status = status
    end
  end
end

My controller looked like this:

 @call = Call.find(params[:id])
 @call.update_my_status(current_user(),params[:status])

This didn't work. No matter what I did, I couldn't modify call. I put a tonne of debug lines in, and eventually got to this:


 @call = Call.find(params[:id])
 @call.update_my_status(@call.a_party, true);
 
 if !@call.a_party_status 
   @call.a_party_status = true
   if @call.a_party_status
      # Hrm, first method didn't work, but the second one did?
   else
      # They both failed!
   end
 end

Amazingly, setting the value outside of the class worked, but setting it inside of the method in the class didn't. I was stumped. Google wasn't any help, it seems this is something that no one else runs into as a problem.

Finally, I found a tutorial that showed me what was going wrong, even though it didn't say it explicitly... It had the following example code:

>> player = Player.new
=> #<Player:0x28ef720 @attributes={"team_id"=>nil, "name"=>nil, "number"=>nil,
"position"=>nil}, 
@new_record=true>

Looking at that, it all clicked. ActiveRecord makes extensive use of the "method missing" functionality available in Ruby. Essentially, it looks at what you are trying to invoke and performs the action on the fly if Ruby can't figure it out itself. That explains what is going on with the "update_my_status" method. Since it is an assignment, Ruby will create a local variable, "method missing" isn't invoked and the class attributes are never changed! The solution? Put "self." in front of the attribute assignments:

  def update_my_status(user, status)
    if (a_party == user)
      self.a_party_status = status
    else
      self.b_party_status = status
    end
  end

That was extremely frustrating. I need to see if there is a "warnings as errors" option. It's also an indication that I need to start working on my own stuff earlier in the evening. My brain seems to shut down after 9:30.

Oddly, it seems that I'm the only one who has this problem. After talking a friend, he said, "I always put self. in front of everything out of convention".

Nuts.

Oh, and Rails? It Rocks.

2 comments:

Brecht said...

Hey dude,

I just found your blog post after googling for a couple of hours. You're not the only one who had this problem after all ;)
It's really weird nobody else questions this. In the book "agile web development with rails", they also use 'self', but I didn't find an explanation why.
Anyway, it's a pretty shady design decision. Try explaining that to somebody who is new to rails...

Jason Pollock said...

I'm glad I could help! :)