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
中定义的验证规则(避免有他们重复了吗?
我不考虑采用不同的(更有效的)方法吗?
答案 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左右的批次执行此操作。