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

3

我正在解析一个CSV文件,试图区分Model中的列和将要添加到JSONB :data列中的“虚拟”列。目前为止,我已经做到了以下几点:

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) }
3个回答

3

有多种方法可以实现这一点。其中一种特别简单的方法是使用Rails的ActiveSupport扩展中的Hash#slice!,它类似于Array#slice!,返回一个哈希表,其中包含那些没有在参数中给出的键,同时保留了已经给出的键:

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. 可能应该将这归类为“愚蠢的Ruby技巧”,但如果你在使用Ruby 2.0+,你可以利用双星号(**)来实现这种紧凑的构造:

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

如果你的CSV文件很大,并且担心性能问题(调用create几千次——以及随后的数据库INSERT开销不小),我建议看看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 文档中,还有其他更快的选项。


那是一个非常棒的一行代码!谢谢! - t56k
1
很高兴能帮忙!我添加了一条关于你可能会发现有用的 gem 的注释,请看一下。 - Jordan Running
不是你第一次做这个了,是吧?非常感谢。 - t56k

1
你尝试过以下方法吗:

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

这将从行哈希中删除元素,并将键值对添加回行中,放在:data键下。

1
您可以尝试使用这个has_attribute?
row[:data] = row.keep_if { |x| !Model.has_attribute?(x) }

我喜欢使用 keep_if,但 has_attribute? 对我不起作用。 - t56k
Rails 3.2+可以使用http://apidock.com/rails/ActiveRecord/Base/has_attribute%3F或者http://api.rubyonrails.org/classes/ActiveRecord/AttributeMethods.html#method-i-has_attribute-3F。 - Rajarshi Das

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接