【问题标题】:How to write a Rake task that imports data and handles deletions?如何编写一个导入数据并处理删除的 Rake 任务?
【发布时间】:2015-06-15 22:43:03
【问题描述】:
我想做与此问题How to write Rake task to import data to Rails app? 中解释的相同的事情。
但是,我对接受的答案不满意,因为它不考虑源中已删除的项目。
考虑到源中已删除的条目,最简单、最符合 Rails 的方法是什么?
注意事项:
- 当使用 .find_or_initialize_by_identifier 并且从不删除时,多余的条目会保留在表中。
- 据我所知,在每次导入前使用 .delete_all 时,主键不会重置,会很快接近其限制。
- 我可以删除表并在 rake 任务中使用 ::Migrations.create_table 但架构和迁移中的定义必须与 rake 任务中的代码保持同步,这似乎是不可取的。
【问题讨论】:
标签:
ruby-on-rails
import
rake
recreate
【解决方案1】:
您绝对不应该删除所有记录,然后从数据中重新创建它们。这将产生各种问题,例如破坏其他表中的任何外键字段,这些字段在删除之前指向对象。这就像为了拥有一扇不同颜色的门而将一所房子推倒并重建它。因此,“查看它是否存在,如果存在则更新它(如果它不同),如果不存在则创建它”是正确使用的策略。
您没有说明删除的标准是什么,但如果它是“应删除导入数据中未提及的任何记录”,那么您只需要跟踪输入数据中的一些唯一字段然后删除其唯一字段不在该列表中的所有记录。
因此,您执行导入的代码可能看起来像这样(从另一个问题复制代码:此代码以非常笨拙的方式设置数据,但我不打算在这里解决)
namespace :data do
desc "import data from files to database"
task :import => :environment do
file = File.open(<file to import>)
identifiers = []
file.each do |line|
#disclaimer: this way of setting the data from attrs[0], attrs[1] etc is crappy and fragile and is not how i would do it
attrs = line.split(":")
identifier = attrs[0]
identifiers << identifier
if p = Product.find_or_initialize_by_identifier(identifier)
p.name = attrs[1]
etc...
p.save!
end
end
#destroy any which didn't appear in the import data
Product.where("identifier not in (?)", identifiers).each(&:destroy)
end
end
【解决方案2】:
我使用的是 .delete_all 和一个没有 rails 的默认 id auto_increment 列的表模式,以避免在 .delete_all 之后增加值。
create_table :airport_locations, id: false do |t|
t.string :iata_faa_code, :primary_key
t.float :latitude
t.float :longitude
end
add_index :airport_locations, :iata_faa_code
注释
- 数据集相当小(约 5000 个条目)并且更新很少发生。
- 如果表很小,则可以像 Max Williams 回答中解释的那样跟踪已删除的项目。尽管具有数千个条目的表可能需要大量内存,并且可能需要更复杂的策略(例如使用临时表)来查找已删除的条目。