68

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@gmail.com", :first_name=>"Name", :last_name=>"LastName")
Patient.create(:doctor_id=>1, :gender=>"male", :date_of_birth=>"1991-02-24")
picardo
  • 24,530
  • 33
  • 104
  • 151
  • I'm not very savvy on Rails 4 yet but I think this question is a Rails 3 question. The default hardcoded configuration in `config/application.rb` in Rails 4 is blank! – boulder_ruby Mar 21 '14 at 02:25
  • http://apidock.com/rails/ActiveRecord/Base/attr_accessible/class – shilovk Aug 31 '14 at 13:21

6 Answers6

141

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.

Robert Speicher
  • 15,382
  • 6
  • 40
  • 45
  • Now I'm getting "unknown attribute" error on invitations when I rake the seed file. I know I have this field in the database, though; it's in the migration file... – picardo Oct 15 '10 at 17:16
  • It's in the migration file, but did you run the migrations? Post your seeds file. – Robert Speicher Oct 15 '10 at 17:31
  • Did you update the other stuff? You've still got attr_accessor in your model even though that's wrong. – Robert Speicher Oct 15 '10 at 18:05
  • This could be related to the fact that STI uses only one table to for all subclasses, which Dave Simms pointed out. I have a doctors table, which has invitations field, but it should be in the users table instead. d'oh! – picardo Oct 15 '10 at 18:06
  • If you're subclassing the User table, have you checked for attr_accessible in User itself? Or attr_protected, but I suspect it's the former. You can add fields to be accessible in subclasses by calling #attr_accessible in the subclass. – François Beausoleil Oct 15 '10 at 20:13
11

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
boulder_ruby
  • 38,457
  • 9
  • 79
  • 100
  • 37
    That's really bad advice even with the caveat of "early coding". When do developers ever get a chance to go back and refactor their entire app? Do it properly from the start. – toxaq Jun 13 '12 at 09:17
  • 4
    "bear in mind this is a security hole but you can deal with it later" When were you planning on dealing with it? Later, when it's in production? I've just added "what's your opinion on attr_accessible?" to my list of interview questions. – toxaq Jun 19 '12 at 00:09
  • Can I suggest an edit to clarify your answer? From your post, it isn't completely clear that the default value is the more secure way, and changing it to false gives easy access to model attributes but opens the bad security hole. – Excalibur Jul 18 '12 at 23:11
  • 1
    this would help if you are migrating from rails 3.0x to rails 3.2.8, disabling this for time being and later enabling it again – Amol Pujari Sep 11 '12 at 05:54
  • So, could you explain HOW it is a security hole? I mean, in order to use update_atttributes you need access to the scope of within the Rails application itself. If someone has that then the app is already compromised. Disabling update_attributes isn't going to make your application more secure. – RedMage Mar 19 '14 at 15:24
2

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

Lance Roberts
  • 22,383
  • 32
  • 112
  • 130
Aneez
  • 157
  • 1
  • 2
  • 14
2

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?

Dave Sims
  • 5,060
  • 3
  • 24
  • 26
  • When I don't use it, though, Rails tells me "method not found" as I am raking the seed file. What should I do in that case? – picardo Oct 15 '10 at 16:46
  • Can you post your model code and rake task? Also the migration that created the model. Make sure those columns are created in the db. – Dave Sims Oct 15 '10 at 16:48
1

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

Kyle McClellan
  • 664
  • 7
  • 23
0

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!

Manish Shrivastava
  • 30,617
  • 13
  • 97
  • 101