如何将 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 << user.attributes.values
如何在我的控制器中呈现这个 CSV 字符串?换句话说,我希望页面向用户返回一个 CSV。
@HenleyChiu Checkout comma,它使您能够以“rails-way”呈现 CSV 数据respond_to |format| format.csv render :csv => 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。那个版本还不知道 separator
和 once
。所以你可能需要从网站下载最新的二进制文件: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
如何将 DAO 查询转换为 Yii2 ActiveRecord。如何从连接表中选择和使用特定列