如何将 ActiveRecord 模型数组转换为 CSV?

Posted

技术标签:

【中文标题】如何将 ActiveRecord 模型数组转换为 CSV?【英文标题】:How to convert array of ActiveRecord models to CSV? 【发布时间】:2013-10-12 14:29:11 【问题描述】:

我有一组 ActiveRecord 模型,我希望将其转换为 CSV。我尝试研究像 FasterCSV 这样的 gem,但它们似乎只适用于字符串和数组,而不是 ActiveRecord 模型。

简而言之,我要转换:

user1 = User.first
user2 = User.last
a = [user1, user2]

收件人:

   id,username,bio,email
    1,user1,user 1 bio,user1 email
    1,user2,user 2 bio,user2 email

有没有一种简单的 Rails 方法可以做到这一点?

【问题讨论】:

这个 railscast 可以帮助你:railscasts.com/episodes/362-exporting-csv-and-excel 谢谢!虽然我回避 Railscast,因为 Rails/gems 这些天移动得太快了,很多东西都过时了。 railcast 创作者应该考虑的事情。 【参考方案1】:

下面会将所有用户的属性写入一个文件:

CSV.open("path/to/file.csv", "wb") do |csv|
  csv << User.attribute_names
  User.find_each do |user|
    csv << user.attributes.values
  end
end

同样你可以创建一个 CSV 字符串:

csv_string = CSV.generate do |csv|
  csv << User.attribute_names
  User.find_each do |user|
    csv << user.attributes.values
  end
end

【讨论】:

你可以使用csv &lt;&lt; user.attributes.values 如何在我的控制器中呈现这个 CSV 字符串?换句话说,我希望页面向用户返回一个 CSV。 @HenleyChiu Checkout comma,它使您能够以“rails-way”呈现 CSV 数据respond_to |format| format.csv render :csv =&gt; User.limited(50) 这会将所有用户加载到内存中。如果您有大量记录,@YukiInoue 有更好的答案 @davkutalek 好点。我刚刚更新了可能会回答以使用 #find_each 之类的 @Yukilnoue 。感谢您指出这一点apidock.com/rails/ActiveRecord/Batches/find_each【参考方案2】:

@rudolph9 的回答真的很棒。我只是想给需要定期执行此任务的人留个便条:将其设为 rake 任务是个好主意!

lib/tasks/users_to_csv.rake

# usage:
# rake csv:users:all => export all users to ./user.csv
# rake csv:users:range start=1757 offset=1957 => export users whose id are between 1757 and 1957
# rake csv:users:last number=3   => export last 3 users
require 'csv' # according to your settings, you may or may not need this line

namespace :csv do
  namespace :users do
    desc "export all users to a csv file"
    task :all => :environment do
      export_to_csv User.all
    end

    desc "export users whose id are within a range to a csv file"
    task :range => :environment do |task, args|
      export_to_csv User.where("id >= ? and id < ?", ENV['start'], ENV['offset'])
    end

    desc "export last #number users to a csv file"
    task :last => :environment do |task, arg|
      export_to_csv User.last(ENV['number'].to_i)
    end

    def export_to_csv(users)
      CSV.open("./user.csv", "wb") do |csv|
        csv << User.attribute_names
        users.each do |user|
          csv << user.attributes.values
        end
      end
    end
  end
end

【讨论】:

【参考方案3】:

这可能与原始问题无关,但可以解决问题。如果您打算让所有或部分 Active Record 模型能够转换为 csv,则可以使用 ActiveRecord 关注点。下面是一个例子

module Csvable
  extend ActiveSupport::Concern 

  class_methods do
    def to_csv(*attributes)
      CSV.generate(headers: true) do |csv| 
        csv << attributes 

        all.each do |record| 
          csv << attributes.map  |attr| record.send(attr) 
        end 
      end
    end
  end
end

提供的属性将用作 CSV 的标题,并且预计该属性对应于包含类中的方法名称。 然后,您可以将它包含在您选择的任何 ActiveRecord 类中,在本例中为 User 类

class User 
  include Csvable 

end

用法

User.where(id: [1, 2, 4]).to_csv(:id, :name, :age)

注意:这仅适用于 ActiveRecord 关系,不适用于数组

【讨论】:

【参考方案4】:

如果您需要一些快速而肮脏的东西,而不是为了生产而只是为非技术用户获取一些数据,您可以将其粘贴到控制台中:

require 'csv'
class ActiveRecord::Relation
  def to_csv
    ::CSV.generate do |csv|
      csv << self.model.attribute_names
      self.each do |record|
        csv << record.attributes.values
      end
    end
  end
end

然后执行:User.select(:id,:name).all.to_csv

如果您要进行生产,我可能会将其变成 ActiveRecord::Relation 周围的装饰器,更准确地确保您的字段/属性的顺序。

【讨论】:

如果您在命名范围而不是 .all 上使用它,则会失败并显示“NoMethodError:ActiveRecord::Relation 的未定义方法‘模型’”。有人有建议的修复方法吗? 嗯...你在什么情况下这么称呼它?也许您是在 AR 以某种方式实际加载之前加载的?由于模型是 AR::R 上的一个属性,所以结果为空时可能没有设置它。您确定返回的结果中有记录吗?您也可以尝试在您的命名范围上调用 all,但如果他们提供帮助,我会感到惊讶。 它与 .all.to_csv 工作的上下文相同,我只是将 .all 更改为我在模型中定义的命名范围。它与创建代理对象的范围有关,但我无法进一步深入研究这个问题。对我来说,它不是一个炫耀的东西,但如果想办法让它发挥作用会很好,因为它是一个通用且非常优雅的解决方案。【参考方案5】:

使用julia_builder,您可以非常轻松地配置 csv 导出。

class UserCsv < Julia::Builder
  # specify column's header and value
  column 'Birthday', :dob
  # header equals 'Birthday' and the value will be on `user.dbo`

  # when header and value are the same, no need to duplicate it.
  column :name
  # header equals 'name', value will be `user.name`

  # when you need to do some extra work on the value you can pass a proc.
  column 'Full name', ->  "# name.capitalize  # last_name.capitalize " 

  # or you can pass a block
  column 'Type' do |user|
    user.class.name
  end
end

然后

users = User.all
UserCsv.build(users)

【讨论】:

【参考方案6】:

另一个类似的答案,但这是我通常做的。

class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true

  def self.to_csv
    CSV.generate do |csv|
      csv << column_names
      all.find_each do |model|
        csv << model.attributes.values_at(*column_names)
      end
    end
  end
end

我通常不会破解现有模块,而是将此代码放在 ApplicationRecord 类中,这是所有模型的基类(通常)。

如果需要进一步阐述,我会在 to_csv 方法中添加一个命名参数,并在此类中尽可能多地处理这些功能。

这样,to_csv 方法将可用于模型及其关系。例如

User.where(role: :customer).to_csv
# => gets the csv string of user whose role is :customer

【讨论】:

这是最好的答案:find_each 批处理你的 sql 查询,这样你就不会使用大量内存【参考方案7】:

也可以为此使用 sql 引擎。例如。对于 sqlite3:

cat << EOF > lib/tasks/export-submissions.sql
.mode      csv
.separator ',' "\n"
.header    on


.once "submissions.csv"

select
  *
from submissions
;
EOF

sqlite3 -init lib/tasks/export-submissions.sql db/development.sqlite3 .exit

如果你在 CentOS 7 上——它附带了 2013 年发布的 sqlite。那个版本还不知道 separatoronce。所以你可能需要从网站下载最新的二进制文件:https://sqlite.org/download.html 本地安装,并使用本地安装的完整路径:

~/.local/bin/sqlite3 -init lib/tasks/export-submissions.sql db/development.sqlite3 .exit

【讨论】:

【参考方案8】:

我遇到了同样的问题,并结合了其中几个答案,因此我可以在模型或关系上调用 to_csv,然后输入文件名并创建一个 csv 文件。

class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true

  def self.to_csv
    require 'csv'

    p "What is the name of your file? (don't forget .csv at the end)"

    file_name = gets.chomp

    CSV.open("#file_name", "wb") do |csv|
      csv << column_names
      all.find_each do |model|
        csv << model.attributes.values_at(*column_names)
      end
    end
  end
end

现在您可以从控制台调用.to_csv 对任何模型或任何数据库查询或活动记录关系。

【讨论】:

以上是关于如何将 ActiveRecord 模型数组转换为 CSV?的主要内容,如果未能解决你的问题,请参考以下文章

将对象数组转换为 ActiveRecord::Relation

Rails:将复杂的 SQL 查询转换为 Arel 或 ActiveRecord

Yii2 - ActiveRecord 到数组

如何将 DAO 查询转换为 Yii2 ActiveRecord。如何从连接表中选择和使用特定列

如何将此查询转换为ActiveRecord(Rails 5)

如何将对象数组转换为模型对象数组