views:

343

answers:

3

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?

+2  A: 

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.

khelll
Khell, - I wasn't asking how to fix this; that's taken care of. I was trying to understand what's happening under the hood.
Chris Adams
+5  A: 

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.

neutrino
A: 

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.

Koraktor