"WARNING: Can't mass-assign protected attributes"

Ruby on-RailsActiverecordDevise

Ruby on-Rails Problem Overview


I have used RESTful techniques to generate a model (in fact, I am using Devise gem, which does that for me), and I have added new fields called first_name and last_name to the model. Migration went fine. I added attr_accessor :first_name, :last_name to the model and expected it would just work. But when I try to mass-assign new instances with Doctor.create({:first_name=>"MyName"}) etc., I am getting errors saying I can't mass-assign protected attributes.

I thought the whole point of using attr_accessor was to get around the protectedness of the fields of a model. Can you help me make sense of this message?

Edit: oh, and by the way the records do not get created either. I thought they should be since this is just a warning, but they are not on the database.

Edit2: here is my model

class Doctor < User
  has_many :patients
  has_many :prescriptions, :through=> :patients

  validates_presence_of :invitations, :on => :create, :message => "can't be blank"

  attr_accessor :invitations
end

and the schema, which doesn't have the first_name and last_name because they are created in the users table, which is the ancestor of doctors. I used single table inheritance.

create_table :doctors do |t|
  t.integer :invitations

  t.timestamps
end

and this is the migration to change the users table

add_column :users, :first_name, :string
add_column :users, :last_name, :string
add_column :users, :type, :string

EDIT: here is the seed file. I am not including the truncate_db_table method, but it works.

%w{doctors patients}.each do |m|
  truncate_db_table(m)  
end  

Doctor.create(:invitations=>5, :email=>"[email protected]", :first_name=>"Name", :last_name=>"LastName")
Patient.create(:doctor_id=>1, :gender=>"male", :date_of_birth=>"1991-02-24")

Ruby on-Rails Solutions


Solution 1 - Ruby on-Rails

Don't confuse attr_accessor with attr_accessible. Accessor is built into Ruby and defines a getter method - model_instance.foo # returns something - and a setter method - model_instance.foo = 'bar'.

Accessible is defined by Rails and makes the attribute mass-assignable (does the opposite of attr_protected).

If first_name is a field in your model's database table, then Rails has already defined getters and setters for that attribute. All you need to do is add attr_accessible :first_name.

Solution 2 - Ruby on-Rails

To hack your app together in an insecure way totally unfit for production mode:

Go to /config/application.rb Scroll down towards the end where you'll find

{config.active_record.whitelist_attributes = true}

Set it to false.

EDIT/btw (after 4 months of ruby-intensive work including an 11 week workshop): DHH believes that, for noobies (his words), "up and running" is more important than "very secure".

BE ADVISED: A a lot of experienced rails developers feel very passionate about not wanting you to do this.

UPDATE: 3 years later, another way to do this -- again, not secure, but better than the above solution probably because you have to do it for each model

class ModelName < ActiveRecord::Base
  column_names.each do |col|
    attr_accessible col.to_sym
  end
  ...
end

Solution 3 - Ruby on-Rails

Don't use attr_accessor here. ActiveRecord creates those automatically on the model. Also, ActiveRecord will not create a record if a validation or mass-assignment error is thrown.

EDIT: You don't need a doctors table, you need a users table with a type column to handle Rails Single Table Inheritance. The invitations will be on the users table. Ah, I see in your added code sample you do have type on users. Get rid of the doctors table, move invitations over to users, and I think you should be ok. Also get rid of the attr_accessor. Not needed.

Keep in mind that rails STI uses the same table for all classes and subclasses of a particular model. All of your Doctor records will be rows in the users table with a type of 'doctor'

EDIT: Also, are you sure you only want to validate presence of invitations on creation and not updates?

Solution 4 - Ruby on-Rails

Add attr_accessible : variable1, variable2 to your table route file.

Solution 5 - Ruby on-Rails

Agree with @Robert Speicher answer But I will strongly recommend that You should use Strong parameter instead of attr_accessible to protect from mass asignment.

Cheers!

Solution 6 - Ruby on-Rails

If you want to disable mass assignment protection for an individual call (but not globally), the :without_protection => true option can be used. I find this useful for migrations and other places where the keys/values of the hash are hard-coded or otherwise known to be safe.

Example here (works in rails 3.2 as well): https://apidock.com/rails/v3.1.0/ActiveRecord/Base/create/class

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionpicardoView Question on Stackoverflow
Solution 1 - Ruby on-RailsRobert SpeicherView Answer on Stackoverflow
Solution 2 - Ruby on-Railsboulder_rubyView Answer on Stackoverflow
Solution 3 - Ruby on-RailsDave SimsView Answer on Stackoverflow
Solution 4 - Ruby on-RailsAneezView Answer on Stackoverflow
Solution 5 - Ruby on-RailsManish ShrivastavaView Answer on Stackoverflow
Solution 6 - Ruby on-RailsKyle McClellanView Answer on Stackoverflow