开发者

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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜