【问题标题】:Rails: Ignoring non-existant attributes passed to create()Rails:忽略传递给 create() 的不存在属性
【发布时间】:2010-11-08 21:23:34
【问题描述】:

我有以下 Rails 模型:

class CreateFoo < ActiveRecord::Migration
  def self.up
    create_table :foo do |t|
      t.string :a
      t.string :b
      t.string :c
      t.timestamps
    end
  end

  def self.down
    drop_table :foo
  end
end

如果我尝试使用不存在的附加属性创建新记录,则会产生错误:

Foo.create(a: 'some', b: 'string', c: 'foo', d: 'bar')
ActiveRecord::UnknownAttributeError: unknown attribute: d

有没有办法让 create() 忽略模型中不存在的属性?或者,在创建新记录之前删除不存在的属性的最佳方法是什么?

非常感谢

【问题讨论】:

  • 我正在查询第 3 方 Web 服务,该服务返回我正在解析为哈希的 XML。对于服务返回的每个字段,模型中都会有一个字段,但我需要确保如果他们决定返回任何其他字段,这不会阻止创建记录。有 100 多个字段,因此无法单独映射每个字段。
  • 那么,您正在从某人那里读取 XML 文件,并且您正试图将其注入模型中?而且您还说您可以有很多选择,对吗?
  • 好的,所以提供的答案在技术上很好。但是,从维护 p.o.v.你应该仔细考虑这一点。依赖注入很棘手。 Java中有很多这样的东西,但没有一个真的那么神奇。它增加了复杂性,恕我直言,使您的代码更加脆弱和容易出错。我会考虑添加一个对象或函数来解析/排序所需的字段,然后创建模型。这类似于工厂模式或抽象工厂模式。

标签: ruby-on-rails ruby-on-rails-3


【解决方案1】:

您可以使用Hash#slicecolumn_names 方法也作为类方法存在。

hash = {a: 'some', b: 'string', c: 'foo', d: 'bar'}
Foo.create(hash.slice(*Foo.column_names.map(&:to_sym)))

【讨论】:

  • 这是一个超级有用的单行字,它为我节省了大量时间。谢谢!
  • 我不明白为什么这个答案没有更多的选票。它更简单,更有条理。你确定.map(&amp;:to_sym) 部分吗?我设法让它只用Foo.create(hash.slice(*Foo.column_names))
【解决方案2】:

试图想一个可能更有效的方法,但现在:

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

【讨论】:

  • 我认为你有错误,hash.select 返回一个数组,而不是一个哈希。
  • 是的,你是对的,应该只使用拒绝选项。我最初有,在思考之前正在打字:P
  • 补充一下,delete_if 似乎比 reject 更有效,因为它适用于哈希的原始副本而不是复制它。
  • 您是对的,但是您正在修改内存中的对象。如果这对您有用,请务必使用它并且您不需要创建新的哈希。 Ruby 文档中对此进行了很好的解释。您也可以使用#reject!做同样的事情,但是避免为此设置一个变量,因为如果它不拒绝任何东西,它将返回 nil。我只是采取长期方法,因为有些人不知道#delete_if 和#reject 的影响! (即修改原件)但如果您了解结果,那么一定要在必要时使用它。 :)
  • 怎么样:hash.each{|k,v| @something[k] = v if @something.has_attribute?(k)}
【解决方案3】:

我认为在 Foo 的模型类中使用 attr_accessible 方法可以实现您想要的,例如:

class Foo < ActiveRecord::Base

  attr_accessible :a, :b, :c

  ...
end

这将只允许设置/更新那些用 attr_accessible 列出的属性。

【讨论】:

    【解决方案4】:

    我经常使用这个(简化):

    params.select!{|x| Model.attribute_names.index(x)}
    Model.update_attributes(params)
    

    【讨论】:

    • 您可能想要使用x.to_s,因为attribute_names 是一个字符串数组。无论如何,+1 =)
    【解决方案5】:

    我想出了一个看起来像这样的解决方案,您可能会发现它很有帮助:

    def self.create_from_hash(hash)
      hash.select! {|k, v| self.column_names.include? k }
      self.create(hash)
    end
    

    这对我来说是一个理想的解决方案,因为在我的情况下,hash 来自一个反映我的架构的理想数据源(除了有其他字段)。

    【讨论】:

    • 我喜欢这个解决方案。有时,从传入的哈希(即 id)中删除一个项目可能很有用:FooModel.create_from_hash( hash.except('id') )
    【解决方案6】:

    当我设置时,我刚刚升级到 Rails 3.2 时遇到了这个确切的问题:

    config.active_record.mass_assignment_sanitizer = :strict
    

    它引起了我的一些创作!调用失败,因为以前被忽略的字段现在导致批量分配错误。我通过伪造模型中的字段来解决它,如下所示:

    attr_accessor   :field_to_exclude
    attr_accessible :field_to_exclude
    

    【讨论】:

      【解决方案7】:

      Re: 有没有办法让 create() 忽略模型中不存在的属性? -- 不,这是设计使然。

      您可以创建 create 将使用的 attr_setter --

      attr_setter :a # will silently absorb additional parameter 'a' from the form.
      

      Re:或者,在创建新记录之前删除不存在的属性的最佳方法是什么?

      您可以明确删除它们:

      params[:Foo].delete(:a) # delete the extra param :a
      

      但最好一开始就不要把它们放在那里。修改您的表单以省略它们。

      添加:

      鉴于更新的信息(传入数据),我想我会创建一个新的哈希:

      incoming_data_array.each{|rec|
        Foo.create {:a => rec['a'], :b => rec['b'], :c => rec['c']} # create new
                                                                    # rec from specific
                                                                    # fields
      }
      

      添加更多

      # 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
      

      【讨论】:

      • 感谢您的回答。当您回复时,我在上面添加了其他详细信息。是否可以反过来做并从哈希中删除“除...之外的所有属性”?
      • 如果您对此投了反对票,请添加评论原因。这个解决方案看起来不错。
      • 因为在 OP 的 cmets 中,他提到“我正在查询第 3 方 Web 服务,该服务返回我正在解析为哈希的 XML。” ...所以没有传入的数据数组,他还提到他有 100 多个字段,因此手动重新映射属性不是一种选择。 (在您的 delete_if 编辑之前被否决)无论如何,有超过 100 个字段,他应该只从记录中提取有效的属性键,而不是创建一个“守护者”数组(因为 Rails 已经为我们做了这个逻辑)。
      • @jenjenut233:有点苛刻。通常,我们对错误的答案、推荐危险的答案等投反对票。如果答案不能让 OP 满意地回答问题,那么 Op 不会给它打勾。请记住,答案可能有用或有帮助(因此值得投票),即使它没有完全解决 OP。最后,请记住,一组数据的 XML 通常会在 Ruby/Rails 程序中作为记录数组进行解析和使用。
      • 我不同意答案提供了一个“遥不可及”的解决方案。这与我接受的答案之间的唯一区别是,另一个解决方案利用了模型中的属性,而不是需要一个单独的保持器数组。不过这个答案很有用,所以我投了赞成票。
      【解决方案8】:

      我找到了一个效果很好的解决方案,最终是上述选项的组合。它允许传递(并忽略)无效参数,而有效参数则正确映射到对象。

      def self.initialize(params={})
        User.new(params.reject { |k| !User.attribute_method?(k) })
      end
      

      现在不要打电话给User.new(),而是打电话给User.initialize()。这将相当优雅地“过滤”正确的参数。

      【讨论】:

      • 优雅,是的。但要小心,当参数值为布尔值时,它不起作用。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-07-30
      • 2013-08-07
      • 2021-11-28
      相关资源
      最近更新 更多