Ruby: 如果列存在则使用它,如果不存在则添加到其他列

Ruby: If column exists use it, if not add to other column

我正在解析 CSV 并试图区分将添加到 JSONB :data 列的 Model 和 "virtual" 列中的列。到目前为止,我得到了这个:

rows = SmarterCSV.process(csv.path)
rows.each do |row|
  row.select! { |x| Model.attribute_method?(x) } # this ignores non-matches
  Model.create(row)
end

从 CSV 行中删除 列与 Model 不匹配的列。相反,我想将所有这些数据添加到 Model 中名为 :data 的列中。我该怎么做?

编辑

可能在 select! 之前有这样的事情?

row[:data] = row.select { |x| !Model.attribute_method?(x) }

你试过了吗:

row[:data] = row.delete_if {|k,v| !Model.attribute_method?(k) }
Model.create(row)

这将从行散列中删除元素,并将键值对添加回 :data 键下的行。

你可以试试这个has_attribute?

row[:data] = row.keep_if { |x| !Model.has_attribute?(x) }

有多种方法可以做到这一点。一种特别直接的方法是使用来自 Rails' ActiveSupport 扩展的 Hash#slice!,它的工作方式类似于 Array#slice! 和 returns 一个带有参数中未给出的键的哈希,而保留给定的密钥:

rows = SmarterCSV.process(csv.path)
attrs = Model.attribute_names.map(&:to_sym)

rows.each do |row|
  row[:data] = row.slice!(*attrs)
  Model.create(row)
end

P.S。这可能会在 "Stupid Ruby Tricks," 下归档,但如果您使用的是 Ruby 2.0+,则可以利用双拼音 (**) 来实现这种紧凑的结构:

rows.each do |row|
  Model.create(data: row.slice!(*attrs), **row)
end

P.P.S。如果您的 CSV 很大并且您发现自己有性能问题(调用 create 几千次——随后的数据库 INSERTs 并不便宜),我建议检查 activerecord-import gem.它正是为这类事情而设计的。有了它你会做这样的事情:

rows = SmarterCSV.process(csv.path)
attrs = Model.attribute_names.map(&:to_sym)

models = rows.map do |row|
  row[:data] = row.slice!(*attrs)
  Model.new(row)
end

Model.import(models)

activerecord-import 文档中还有其他更快的选项。