Rails: Ignoring non-existant attributes passed to create()
I have the following Rails model:
class CreateFoo < ActiveRecord::Migration
def self.up
create_table :foo do |t|
t.string :a
t.string :b
t.string :c
t.timestamps
end
e开发者_运维技巧nd
def self.down
drop_table :foo
end
end
If I try and create a new record with an additional non-existent attribute, this produces an error:
Foo.create(a: 'some', b: 'string', c: 'foo', d: 'bar')
ActiveRecord::UnknownAttributeError: unknown attribute: d
Is there a way I can get create() to ignore attributes that don't exist in the model? Alternatively, what is the best way to remove non-existent attributes prior to creating the new record?
Many thanks
Trying to think of a potentially more efficient way, but for now:
hash = { :a => 'some', :b => 'string', :c => 'foo', :d => 'bar' }
@something = Something.new
@something.attributes = hash.reject{|k,v| !@something.attributes.keys.member?(k.to_s) }
@something.save
I use this frequently (simplified):
params.select!{|x| Model.attribute_names.index(x)}
Model.update_attributes(params)
I just had this exact problem upgrading to Rails 3.2, when I set:
config.active_record.mass_assignment_sanitizer = :strict
It caused some of my create! calls to fail, since fields that were previously ignored are now causing mass assignment errors. I worked around it by faking the fields in the model as follows:
attr_accessor :field_to_exclude
attr_accessible :field_to_exclude
Re: Is there a way I can get create() to ignore attributes that don't exist in the model? -- No, and this is by design.
You can create an attr_setter that will be used by create
--
attr_setter :a # will silently absorb additional parameter 'a' from the form.
Re: Alternatively, what is the best way to remove non-existent attributes prior to creating the new record?
You can remove them explicitly:
params[:Foo].delete(:a) # delete the extra param :a
But the best is to not put them there in the first place. Modify your form to omit them.
Added:
Given the updated info (incoming data), I think I'd create a new hash:
incoming_data_array.each{|rec|
Foo.create {:a => rec['a'], :b => rec['b'], :c => rec['c']} # create new
# rec from specific
# fields
}
Added more
# Another way:
keepers = ['a', 'b', 'c'] # fields used by the Foo class.
incoming_data_array.each{|rec|
Foo.create rec.delete_if{|key, value| !keepers.include?(key)} # create new rec
} # from kept
# fields
You can use Hash#slice
and column_names
method exists also as class method.
hash = {a: 'some', b: 'string', c: 'foo', d: 'bar'}
Foo.create(hash.slice(*Foo.column_names.map(&:to_sym)))
I came up with a solution that looks like this, you might find it helpful:
def self.create_from_hash(hash)
hash.select! {|k, v| self.column_names.include? k }
self.create(hash)
end
This was an ideal solution for me, because in my case hash
was coming from an ideal data source which mirrored my schema (except there were additional fields).
I think using the attr_accessible method in the model class for Foo would achieve what you want, e.g.,:
class Foo < ActiveRecord::Base
attr_accessible :a, :b, :c
...
end
This would allow the setting/updating of only those attributes listed with attr_accessible.
I found a solution that works fairly well, and is ultimately a combination of the above options. It allows for invalid params to be passed (and ignored), while valid ones are mapped correctly to the object.
def self.initialize(params={})
User.new(params.reject { |k| !User.attribute_method?(k) })
end
Now rather than calling User.new()
, call User.initialize()
. This will "filter" the correct params fairly elegantly.
精彩评论