26 Marattr_accessible and mass-assignment
Friday, 26 March 2010 — 23:31It’s always a good practice to use attr_accessible in your models, to protect sensible attributes from being filled through the technique known as mass-assignment. With mass-assignment we understand methods such as: new(attributes), update_attributes(attributes), or attributes=(attributes), that allow us to create new records very easily.
So, on one side, we can keep skinny controllers using mass-assignment, but on the other side, we can create a vulnerability in our code for hackers, to inject sensitive information in our models, like passwords, user admins, owner and so on…
From API doc, here is a clear example:
class Customer < ActiveRecord::Base attr_accessible :name, :nickname end customer = Customer.new(:name => "David", :nickname => "Dave", :credit_rating => "Excellent") customer.credit_rating # => nil. Not accessible customer.attributes = { :name => "Jolly fellow", :credit_rating => "Superb" } customer.credit_rating # => nil. Not accessible customer.credit_rating = "Average" customer.credit_rating # => "Average", It is accessible
It can be tedious, as you need to add all accessible methods there, or you will get:
WARNING: Can't mass-assign these protected attributes
But even with that, attr_accessible should be the way to go. If you have a long list of items, you can use attr_protected instead. BUT there is one more thing to take into account.
When you add attr_accessible to your models, ActiveRecord will protect it from mass-assignment as expected, but will also remove not acceptable attributes on mass-assignment. Let’s continue with previous Customer class example:
customer.attributes = { :name => "Jolly fellow", :credit_rating => "Superb", :non_existing_attr =>"whatever" }and this returns:
WARNING: Can't mass-assign these protected attributes: non_existing_attr
but it’s just a warning. If we remove the attr_accessible line from the model, we will get a NoMethodError exception instead:
NoMethodError: undefined method `non_existing_attr='
The reason is in ActiveRecord. When we call attributes=, and we have attr_accessible/attr_protected set, this is executed:
attributes = remove_attributes_protected_from_mass_assignment(attributes) if guard_protected_attributes ... def remove_attributes_protected_from_mass_assignment(attributes) .. attributes.reject { |key, value| !self.class.accessible_attributes.include?(key.gsub(/\(.+/, "")) || attributes_protected_by_default.include?(key.gsub(/\(.+/, "")) } ... end
So to sum up, those params not in attr_accessible are removed, and you will never get an exception. Take this into account when you use attr_accessible