1

How can I tell Ruby (Rails) to ignore protected variables which are present when mass-assigning?

class MyClass < ActiveRecord::Base
    attr_accessible :name, :age
end

Now I will mass-assign a hash to create a new MyClass.

MyClass.create!({:name => "John", :age => 25, :id => 2})

This will give me an exception:

ActiveModel::MassAssignmentSecurity::Error: Can't mass-assign protected attributes: id

I want it to create a new MyClass with the specified (unprotected) attributes and ignore the id attribute.

On the side note: How can I also ignore unknown attributes. For example, MyClass doesn't have a location attribute. If I try to mass-assign it, just ignore it.

tereško
  • 58,060
  • 25
  • 98
  • 150
Artem Kalinchuk
  • 6,502
  • 7
  • 43
  • 57

5 Answers5

5

Use Hash#slice to only select the keys you're actually interested in assigning:

# Pass only :name and :age to create!
MyClass.create!(params.slice(:name, :age))

Typically, I'll add wrapper method for params to my controller which filters it down to only the fields that I know I want assigned:

class MyController

  # ...

  def create
    @my_instance = MyClass.create!(create_params)
  end

protected

  def create_params
    params.slice(:name, :age)
  end
end
user229044
  • 232,980
  • 40
  • 330
  • 338
  • 2
    So? That's the whole point. You're only accept the variables that you *do* know about. The variables you *don't* know about are the once being pared away, you're slicing out and using the ones you *do* know about. – user229044 Feb 21 '13 at 17:48
  • Yeah you have to consider what you are updating/creating when you assign things. This is the whole reason why Rails turned mass assignment protection on by default. This solution only assigns the variables that you define which should be what you want. – Michael Papile Feb 21 '13 at 17:50
  • @MichaelPapile Right. How can I get unprotected attributes for a certain class? – Artem Kalinchuk Feb 21 '13 at 17:52
  • @ArtemKalinchuk If you have unrelated questions, please ask them as an actual question. Comments are not for this sort of back-and-forth discussion. – user229044 Feb 21 '13 at 17:53
  • @meagar I was hoping there would be a Rails solution. For example `.create!({}, :ignore_protected => true)`. No such thing? – Artem Kalinchuk Feb 21 '13 at 17:58
  • @ArtemKalinchuk No, no such thing. Use slice, as stated in the answer, and stop asking additional questions in comments. – user229044 Feb 21 '13 at 18:02
  • @meagar This doesn't answer the question correctly. I want it to ignore protected variables. I don't want to specify which attributes to actually update. `config.active_record.mass_assignment_sanitizer = :logger` will actually ignore protected variables. So there is a Rails way. – Artem Kalinchuk Feb 21 '13 at 18:35
  • @ArtemKalinchuk You didn't *ask* how to ignore protected attributes, you asked how to "ignore unknown attributes". According to the code you've posted, occupation is *not* a protected attribute, it's (presumably) a field in your database which you're not explicitly referencing via a `attr_accessible`; that is *not the same* as a protected attribute. – user229044 Feb 21 '13 at 18:38
  • @meagar The title specifically says "Ruby - Ignore protected attributes". I modified the code in the question to clarify what I mean by protected attribute. – Artem Kalinchuk Feb 21 '13 at 18:51
  • You can also use slice's opposite: except. http://stackoverflow.com/questions/8790381/is-there-an-opposite-function-of-slice-function-in-ruby – fregas Jan 08 '15 at 22:57
3

Setting mass_assignment_sanitizer to :logger solved the issue in development and test.

config.active_record.mass_assignment_sanitizer = :logger
Artem Kalinchuk
  • 6,502
  • 7
  • 43
  • 57
1

You can use strong_parameters gem, that will be in rails 4.

See the documentation here.

This way you can specify the params you want by action or role, for example.

Kaeros
  • 1,138
  • 7
  • 7
0

If you want to get down and dirty with it, and dynamically let only a model's attributes through, without disabling ActiveModel::MassAssignmentSecurity::Errors globally:

params = {:name => "John", :age => 25, :id => 2}
MyClass.create!(params.slice(*MyClass.new.attributes.symbolize_keys.keys)

The .symbolize_keys is required if you are using symbols in your hash, like in this situation, but you might not need that.

trevorgrayson
  • 1,806
  • 1
  • 21
  • 29
0

Personally, I like to keep things in the model by overriding assign_attributes.

def assign_attributes(new_attributes, options = {})
  if options[:safe_assign]
    authorizer = mass_assignment_authorizer(options[:as])
    new_attributes = new_attributes.reject { |key|
      !has_attribute?(key) || authorizer.deny?(key)
    }
  end
  super(new_attributes, options)
end

Use it similarly to :without_protection, but for when you want to ignore unknown or protected attributes:

MyModel.create!(
  { :asdf => "invalid", :admin_field => "protected", :actual_data => 'hello world!' },
  :safe_assign => true
)
# => #<MyModel actual_data: "hello world!">
Kyle McClellan
  • 664
  • 7
  • 23