Why does attr_accessor clobber the existing variables in this model in Ruby on Rails?
-
11-09-2019 - |
Question
I was bitten by this recently, and it'd be useful to know precisely what's happening to make this happen, so others avoid this mistake.
I have a model User, with a schema like so:
create_table "users", :force => true do |t|
t.string "user_name"
t.string "first_name"
t.string "last_name"
t.string "email"
t.string "location"
t.string "town"
t.string "country"
t.string "postcode"
t.boolean "newsletter"
In the class user.rb, I have a attr_accessor for three methods:
class User < ActiveRecord::Base
# lots of code
attr_protected :admin, :active
# relevant accessor methods
attr_accessor :town, :postcode, :country
end
Now in my user controller, if I have the following method:
def create
@user = User.new params[:user]
end
When when I try to create a new user with the contents in this params hash:
--- !map:HashWithIndifferentAccess
# other values
country: United Kingdom
dob(1i): "1985"
dob(2i): "9"
dob(3i): "19"
town: london
The returned object has empty strings for the country
, town
and postcode postcode
values, like so.
(rdb:53) y user1
--- !ruby/object:User
attributes:
# lots of attributes that aren't relevant for this example, and are filled in okay
postcode:
country:
town:
I can tell that the attr_accessor methods are clobbering Active Record's existing accessor methods, because when I take them out all works fine, so the solution is fairly straightforward - just take them out.
But what exactly is happening when here?
I'm looking here in the Rails API docs for Active Record, and here in Ruby's own docs about attr_accessor
, but I'm still slightly hazy about how attr_accessor
is breaking things here.
Any able to shed some light to stop some other poor soul falling foul of this?
Solution
When you add an attr_accessor to a class, it defines two methods on it, e.g. User#postcode and User#postcode=.
If the name of the accessor is equal to a name of a model attribute, things break (if you're not careful). When you assign attributes to the model, User#postcode= is called and in your case it does nothing except
@postcode = value
So the value just gets stored in an instance variable and doesn't appear in the attributes hash.
Whereas in a normal scenario (without an accessor) this would go to method_missing and eventually trigger something like
write_attribute(:postcode, value)
And then it would appear in your model's attributes. Hope that makes sense.
OTHER TIPS
Why at first place you are using attr_accessor :town, :postcode, :country
? Active Record has setter/getter methods for you. Just drop that line, things should work.
You might want to use attr_accessible
on ActiveRecord models to enable mass-assignment of attributes. You don't need attr_accessor
as getters / setters are already defined for model attributes.