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左右的批次执行此操作,我建议您这样做。