ActiveRecord: self.field vs field

TL;DR version:
“self.field” is only necessary when you are assigning a new value to a field.

It always confused me why it seemed like sometimes using the field name from the database failed to work in model methods that I wrote. When the method was simple, things would work fine, but then something would happen and it suddenly stopped working. I never quite put together what was going on until now. I would start out with something like this:

# Very simple, overused, contrived example
def full_name
  "#{prefix} #{first_name} #{last_name}"
end

It works beautifully and looks clean. But then, I’d need to add some defaults, like so.

def full_name
  prefix = "Mr." if prefix.blank?
  first_name = "First" if first_name.blank?
  last_name = "Last" if last_name.blank?
  "#{prefix} #{first_name} #{last_name}"
end

At first glance, it looks right, but it’s not. It causes full_name to produce “Mr. First Last”, every time you run it. WTF? Here’s where I would usually overreach and get it horribly wrong. I know that the fields are available as “self.field”, so I make one more change.

def full_name
  self.prefix = "Mr." if self.prefix.blank?
  self.first_name = "First" if self.first_name.blank?
  self.last_name = "Last" if self.last_name.blank?
  "#{self.prefix} #{self.first_name} #{self.last_name}"
end

So, one WTF leads right to another. This code is horrible. It’s ugly and it feels like there should be a better answer. In this example, it’s not so bad, but what usually ends up happening is this type of code gets written in a method that is already fairly ugly, and because of a simple misunderstanding, it goes directly from bad to worse.

Finally having some time, and just being fed up enough, I did some searching and found this. It ends up being fairly easy to understand once you think about it. In ActiveRecord models, the fields are methods. The way Ruby works, writing “field = ” creates a local variable instead of calling the “field=” method on self. It is the very act of assigning the field with a default that causes the issue. So, you can scale it back a little and rewrite the code like this. You only need “self.field” when assigning a new value.

def full_name
  self.prefix = "Mr." if prefix.blank?
  self.first_name = "First" if first_name.blank?
  self.last_name = "Last" if last_name.blank?
  "#{prefix} #{first_name} #{last_name}"
end

Much better.

Invalid Authenticity Token, IE and Underscores

I spent a couple hours time which I will never get back trying to figure this one out. My rails application worked perfectly fine in Firefox, but IE was throwing an error. That’s not terribly unusual with IE, but this time something was fishy. The error is all over the google. There are several suggested fixes, none of which was working for me. The main one was adding a p3p header to the response that is supposed to to convince IE to allow cookies to be set for your domain. I finally stumbled upon this page that has a subtle reference to not using an underscore in your domain name. Of course, right there in the middle of the subdomain name I had chosen was an underscore. As it turns out underscores are not allowed in hostnames.  Some browsers are just more forgiving than others.

Collection Select

I always fail to remember how to create a simple drop down for my models with a has_many / belongs_to relationship. It’s one of the most basic things you could possibly do with a form, and yet each time I want to hook one up I find myself opening up a tab and googling for it. Well, no more. From now on I’ll say to myself, “Self, you put that on your blog.” So without further ado… collection_select to the rescue!

Let’s say I have a model Nerd that has_many Computers. When I create or edit a computer, I want to select the nerd it belongs_to.


class Nerd
  has_many :computers
end

class Computer
  belongs_to :nerd
end

Then in my view for the computer I’d have this:

<% form_for :computer do |f| %>
  <p>
    <%= f.label :nerd %>
    <%= collection_select(:computer, :nerd_id, Nerd.all, :id, :name) %>
  </p>
<% end %>