Rails:对集合而不是数据库表使用现有的模型验证规则

时间:2014-01-27 12:20:13

标签: ruby-on-rails validation ruby-on-rails-4 mongoid bulkinsert

Rails 4,Mongoid而不是ActiveRecord(但是为了这个问题,这应该改变任何东西)。

假设我有一个MyModel域类,其中包含一些验证规则:

class MyModel
  include Mongoid::Document

  field :text, type: String
  field :type, type: String

  belongs_to :parent

  validates :text, presence: true
  validates :type, inclusion: %w(A B C)
  validates_uniqueness_of :text, scope: :parent # important validation rule for the purpose of the question
end

其中Parent是另一个域类:

class Parent
    include Mongoid::Document

    field :name, type: String

    has_many my_models
end

此外,我在数据库中填充了一些有效数据的相关表。

现在,我想从CSV文件导入一些数据,这可能与数据库中的现有数据冲突。最简单的方法是为CSV中的每一行创建一个MyModel实例,并验证它是否有效,然后将其保存到数据库中(或丢弃它)。

这样的事情:

csv_rows.each |data| # simplified 
  my_model = MyModel.new(data) # data is the hash with the values taken from the CSV row

  if my_model.valid?
    my_model.save validate: false
  else
    # do something useful, but not interesting for the question's purpose
    # just know that I need to separate validation from saving
  end
end

现在,对于有限数量的数据,这非常顺利。但是当CSV包含数十万行时,这会非常慢,因为(最坏的情况)每行都有写操作。

我想做的是存储有效项目列表,并在文件解析过程结束时将它们全部保存。所以,没有什么复杂的:

valids = []
csv_rows.each |data|
  my_model = MyModel.new(data)

  if my_model.valid?  # THE INTERESTING LINE this "if" checks only against the database, what happens if it conflicts with some other my_models not saved yet?
    valids << my_model
  else
    # ...
  end
end

if valids.size > 0
  # bulk insert of all data
end

如果我可以确定CSV中的数据不包含违反MyModel验证规则的重复行或数据,那将是完美的。


我的问题是:如何针对数据库和valids数组检查每一行,而不必重复MyModel中定义的验证规则(避免有他们重复了吗?

我不考虑采用不同的(更有效的)方法吗?

1 个答案:

答案 0 :(得分:2)

您可以做的是验证为模型,将属性保存在哈希中,推送到valids数组,然后批量插入值usint mongodb的insert

valids = []
csv_rows.each |data|
  my_model = MyModel.new(data)

  if my_model.valid?
    valids << my_model.attributes
  end
end

MyModel.collection.insert(valids, continue_on_error: true)

然而,这不会阻止NEW重复...因为您可以使用哈希和复合键执行以下操作:

valids = {}
csv_rows.each |data|
  my_model = MyModel.new(data)

  if my_model.valid?
    valids["#{my_model.text}_#{my_model.parent}"] = my_model.as_document
  end
end

然后,以下任何一种都可以使用,DB Agnostic:

MyModel.create(valids.values)

或MongoDB'ish:

MyModel.collection.insert(valids.values, continue_on_error: true)

或者更好......

确保集合上有uniq索引:

class MyModel
  ...
  index({ text: 1, parent: 1 }, { unique: true, dropDups: true })
  ...
end

然后只需执行以下操作:

MyModel.collection.insert(csv_rows, continue_on_error: true)

http://api.mongodb.org/ruby/current/Mongo/Collection.html#insert-instance_method http://mongoid.org/en/mongoid/docs/indexing.html

提示:我建议如果您预计会有数千行以500左右的批次执行此操作。