Search code examples
ruby-on-railsrubyruby-on-rails-3ruby-on-rails-3.1

Confused about attr_accessor and attr_accessible in rails


this is a simple signup application

schema.rb

create_table "users", :force => true do |t|
t.string   "email"
t.string   "password_hash"
t.string   "password_salt"
t.datetime "created_at",    :null => false
t.datetime "updated_at",    :null => false

User.rb

attr_accessible :email, :password, :password_confirmation
attr_accessor :password
before_save :encrypt_password
validates_confirmation_of :password
validates_presence_of :password, :on => :create
validates_presence_of :email
validates_uniqueness_of :email
.
.
.

Why using password in both attr_accessible and attr_accessor?

When i removed attr_accessor :password, in rails console, i got an error when executing:

user = User.new
user.password # => no method error

but when i execute this:

user = User.new
user.email # => nil

which means user.email working without adding it in attr_accessor, why?!!

and also this is working:

user = User.new
user.password_confirmation # => nil

but when i removed:

validates_confirmation_of :password

it will not work, why??.


Solution

  • attr_accessor and attr_accessible, despite almost identical spelling, are absolutely different methods.

    attr_accessor, a native Ruby method which defines a getter and a setter method for the instance of the class:

    class User
      attr_accessor :password
    end
    
    u = User.new
    u.password = "secret"
    u.password # => "secret"
    

    attr_accessible is a method brought by Rails and it is meant to "whitelist" already existing attributes of a model. Attributes enumerated in attr_accessible can be later changed via mass-assignment of model attributes (while other attributes will be blacklisted and not changeable):

    class Account < ActiveRecord::Base
      # First, you define 2 attributes: "password" and "created_at"
      attr_accessor :password
      attr_accessor :created_at
    
      # Now you say that you want "password" attribute
      # to be changeable via mass-assignment, while making
      # "created_at" to be non-changeable via mass-assignment
      attr_accessible :password
    end
    
    a = Account.new
    
    # Perform mass-assignment (which is usually done when you update
    # your model using the attributes submitted via a web form)
    a.update_attributes(:password => "secret", :created_at => Time.now)
    
    a.password # => "secret"
    # "password" is changed
    
    a.created_at # => nil
    # "created_at" remains not changed
    

    You use attr_accessible to prevent meddling with some attributes of your models by "outsiders" (e.g. you wouldn't want your "Account.superadmin" attribute to be changeable via a simple form submission, which would be a bad security issue).

    Note, that you can change the attributes individually, regardless of their "whitelisting/blacklisting" status:

    a.created_at = Time.now
    
    a.created_at # => 2012-09-16 10:03:14