Rails:对集合而不是数据库表使用现有的模型validation规则
Rails 4,Mongoid而不是ActiveRecord(但是为了这个问题,这应该改变任何东西)。
假设我有一个带有一些validation规则的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(ABC) 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实例,并validation它是否有效,然后将其保存到数据库中(或丢弃它)。
像这样的东西:
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 < 0 # bulk insert of all data end
如果我可以确定CSV中的数据不包含与MyModel的validation规则相反的重复行或数据,那将是完美的。
我的问题是 :如何针对数据库和valids
数组检查每一行,而不必重复MyModel
定义的validation规则(避免重复它们)?
我不考虑采用不同的(更有效的)方法吗?
您可以做的是validation为模型,将属性保存在哈希中,推送到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左右的批次执行此操作,我建议您这样做。