If you work with legacy databases, you don’t always have the option of changing column names when something conflicts with Ruby or Rails. A very common example is having a column named “class” in one of your tables. Rails *really* doesn’t like this, and like the wife or girlfriend who really hates your new haircut, it will complain at every possible opportunity:
# trying to set the poorly named attribute ruby-1.9.2-p0 > u = User.new :class => '1995' NoMethodError: undefined method `columns_hash' for nil:NilClass
# trying to set a different attribute that is only guilty by association ruby-1.9.2-p0 > u = User.new :name NoMethodError: undefined method `has_key?' for nil:NilClass
# trying to set the attribute later in the game ruby-1.9.2-p0 > u = User.new => #<User id: nil, name: nil, class: nil, created_at: nil, updated_at: nil> ruby-1.9.2-p0 > u.class = '1995' NoMethodError: undefined method `private_method_defined?' for nil:NilClass
Like the aforementioned wife/girlfriend, you’re not going anywhere until this issue is resolved. Luckily, Brian Jones has solved this problem for us with his gem safe_attributes. Rails automatically creates accessors (getter and setter methods) for every attribute in an ActiveRecord model’s table. Trying to override crucial methods like “class” is what gets us into trouble. The safe_attributes gem turns off the creation of any dangerously named attributes.
Just do this:
# app/models/user.rb class User < ActiveRecord::Base bad_attribute_names :class end
After including the gem in your bundler, pass bad_attribute_names the list of offending column names, and it will keep Rails from trying to generate accessor methods for it. Now, this does come with a caveat: you don’t have those accessors. Let’s try to get/set our :class attribute:
ruby-1.9.2-p0 > u = User.new => #<User id: nil, name: nil, class: nil, created_at: nil, updated_at: nil> ruby-1.9.2-p0 > u.class = '1995' => "1995" ruby-1.9.2-p0 > u => #<User id: nil, name: nil, class: "1995", created_at: nil, updated_at: nil> ruby-1.9.2-p0 > u.class => User(id: integer, name: string, class: string, created_at: datetime, updated_at: datetime)
The setter still works (I’m guessing that it was still created because there wasn’t a pre-existing “class=” method) and we can verify that the object’s attribute has been properly set. But calling the getter defaults to…well, the default behavior.
The answer is to always use this attribute in the context of a hash. You can send the object a hash of attribute names/values, and that works. This means your controller creating and updating won’t have to change. Methods like new, create, update_attribute, update_attributes, etc will work fine.
If you want to just set the single value (to prevent an immediate save, for example) do it like this:
ruby-1.9.2-p0 > u[:class] = '1996' => "1996" ruby-1.9.2-p0 > u => #<User id: nil, name: nil, class: "1996", created_at: nil, updated_at: nil>
Basically, you can still set the attribute directly, instead of going through the rails-generated accessors. But we’re still one step away from a complete solution. We want to be able to treat this attribute like any other, and that requires giving it a benign set of accessors (getter and setter methods). One reason to do this is so we can use standard validations on this attribute.
Adding accessors to our model is this simple:
# add to app/models/user.rb def class_name= value self[:class] = value end def class_name self[:class] end
We’re calling the accessors “class_name”, and now we can use that everywhere instead of the original attribute name. We can use it in forms:
# example, not found in code <%= f.text_field :class_name %>
Or in validations:
# add to app/models/user.rb validates_presence_of :class_name
Or when creating a new object:
# example, not found in code User.create :class_name => 'class of 1995'
If you download the code, these additions are test-driven, meaning I wrote the tests for those methods before writing the methods themselves, to be sure they worked properly. I encourage you to do the same.