在Rails中,如何将记录返回为CSV文件?

56

我有一个简单的数据库表,名为“Entries”:

class CreateEntries < ActiveRecord::Migration
  def self.up
    create_table :entries do |t|
      t.string :firstName
      t.string :lastName
      #etc.
      t.timestamps
    end
  end

  def self.down
    drop_table :entries
  end
end

我该如何编写一个处理程序,以CSV文件的形式返回Entries表的内容(最好以一种自动在Excel中打开的方式)?

class EntriesController < ApplicationController

  def getcsv
    @entries = Entry.find( :all )

    # ??? NOW WHAT ????

  end

end

至少在较新版本的Rails中,您也可以使用Entry.all - nonopolarity
10个回答

91

FasterCSV 是首选,但如果你想直接从Rails应用程序中提供它,你也需要设置一些响应头。

我保留一个方法来设置文件名和必要的头部:

def render_csv(filename = nil)
  filename ||= params[:action]
  filename += '.csv'

  if request.env['HTTP_USER_AGENT'] =~ /msie/i
    headers['Pragma'] = 'public'
    headers["Content-type"] = "text/plain" 
    headers['Cache-Control'] = 'no-cache, must-revalidate, post-check=0, pre-check=0'
    headers['Content-Disposition'] = "attachment; filename=\"#{filename}\"" 
    headers['Expires'] = "0" 
  else
    headers["Content-Type"] ||= 'text/csv'
    headers["Content-Disposition"] = "attachment; filename=\"#{filename}\"" 
  end

  render :layout => false
end

使用它可以让我在我的控制器中轻松地拥有像这样的内容:

respond_to do |wants|
  wants.csv do
    render_csv("users-#{Time.now.strftime("%Y%m%d")}")
  end
end

并且拥有以下这样的视图:(generate_csv 是来自 FasterCSV 的)

UserID,Email,Password,ActivationURL,Messages
<%= generate_csv do |csv|
  @users.each do |user|
    csv << [ user[:id], user[:email], user[:password], user[:url], user[:message] ]
  end
end %>

哎呀,我以为我已经完成了,直到看到了你的答案!感谢你提供的头部细节,如果我在使用现有工具时遇到麻烦,我会记住这些内容的。 - Eric
10
如上所述,FasterCSV只是Ruby 1.9及以上版本中的CSV。现在,generate_csv方法已更名为CSV.generate。 - Robin Clowers
4
在generate_csv/CSV.generate代码块的结尾处添加".html_safe",可以使数据中的逗号被正确处理。如果没有这个调用,我的csv文件中会有很多""。 - stcorbett
1
我发现自己一遍又一遍地参考这个答案,所以我最终下定决心制作了一个 Rails 3 宝石,提供了一个稍微修改过的 render_csv 方法,该方法在此处提供:https://github.com/spindance/rendered_csv - David van Geest

28

我接受了(并投票支持!)@Brian的答案,因为他首先指引我使用FasterCSV。然后当我搜索这个gem时,我还在这个维基页面上找到了一个相当完整的示例。将它们组合起来,我最终选择了以下代码。

顺便说一下,安装这个gem的命令是: sudo gem install fastercsv (全部小写)

require 'fastercsv'

class EntriesController < ApplicationController

  def getcsv
      entries = Entry.find(:all)
      csv_string = FasterCSV.generate do |csv| 
            csv << ["first","last"]
            entries.each do |e|
              csv << [e.firstName,e.lastName]
            end
          end
          send_data csv_string, :type => "text/plain", 
           :filename=>"entries.csv",
           :disposition => 'attachment'

  end


end

5
我建议将CSV生成移入视图中:这是更多的展示逻辑,而不是你通常在控制器中想要的。 - Clinton Dreisbach
你可能想将类型设置为“text/csv”。否则,Safari会将其保存为“entries.csv.txt”。 - silasjmatson

26

不使用FasterCSV的另一种方法:

在初始化文件(如config/initializers/dependencies.rb)中引用ruby的csv库。

require "csv"

作为一些背景,以下代码基于Ryan Bate的高级搜索表单,创建一个搜索资源。在我的情况下,搜索资源的show方法将返回先前保存的搜索结果。它还响应csv,并使用视图模板来格式化所需的输出。
  def show
    @advertiser_search = AdvertiserSearch.find(params[:id])
    @advertisers = @advertiser_search.search(params[:page])
    respond_to do |format|
      format.html # show.html.erb
      format.csv  # show.csv.erb
    end
  end

show.csv.erb文件如下所示:

<%- headers = ["Id", "Name", "Account Number", "Publisher", "Product Name", "Status"] -%>
<%= CSV.generate_line headers %>
<%- @advertiser_search.advertisers.each do |advertiser| -%>
<%- advertiser.subscriptions.each do |subscription| -%>
<%- row = [ advertiser.id,
            advertiser.name,
            advertiser.external_id,
            advertiser.publisher.name,
            publisher_product_name(subscription),
            subscription.state ] -%>
<%=   CSV.generate_line row %>
<%- end -%>
<%- end -%>

在报告页面的HTML版本中,我有一个链接可以导出用户正在查看的报告。以下是返回报告CSV版本的link_to:

<%= link_to "Export Report", formatted_advertiser_search_path(@advertiser_search, :csv) %>

6
如果您使用的是Rails 3,请不要忘记使用<%= CSV.generate_line(row).html_safe %>,以避免转义字符。 - dombesz
7
这个解决方案很好,但我不得不修改CSV.generate_line调用并将:row_sep设置为nil。这个更改可以从响应中删除不需要的空行。 代码: <%= CSV.generate_line row, {:row_sep => nil} %> - Wilson Freitas
2
补充Wilson的评论,Heroku(cedar stack-beta)将在csv文件中插入空行,除非您明确告诉它不要使用:row_sep => nil - nslocum
我还必须在generate_line_headers的末尾添加一个“ - ”。否则,标题行后面会有一行空行。 - drudru

23

有一个名为FasterCSV的插件可以很好地处理这个问题。


30
请注意,如果使用的是 Ruby 版本大于等于 1.9,则 FasterCSV 已成为标准的 CSV 库,并且现在被称为 CSV。在翻译过程中请保持原意,同时尽可能地让语言通俗易懂。 - kdt
关键在于 require 'csv',请参阅 https://ruby-doc.org/stdlib/libdoc/csv/rdoc/CSV.html。 - mb21

7

看一下 FasterCSV gem。

如果你只需要excel支持,你也可以直接生成xls文件。(参见Spreadsheet::Excel)

gem install fastercsv
gem install spreadsheet-excel

我认为以下选项适合在Windows Excel中打开csv文件:

FasterCSV.generate(:col_sep => ";", :row_sep => "\r\n") { |csv| ... }

关于ActiveRecord部分,可以使用类似这样的代码:
CSV_FIELDS = %w[ title created_at etc ]
FasterCSV.generate do |csv|
  Entry.all.map { |r| CSV_FIELDS.map { |m| r.send m }  }.each { |row| csv << row }
end

2
以下方法适用于我的情况,并在下载后使浏览器打开CSV类型的适当应用程序。
def index
  respond_to do |format|
    format.csv { return index_csv }
  end
end

def index_csv
  send_data(
    method_that_returns_csv_data(...),
    :type => 'text/csv',
    :filename => 'export.csv',
    :disposition => 'attachment'
  )
end

2
你需要在响应中设置Content-Type头,然后发送数据。Content_Type:application/vnd.ms-excel应该就可以了。
你可能还想设置Content-Disposition头,这样它看起来像一个Excel文档,并且浏览器会选择一个合理的默认文件名; 这是类似于Content-Disposition:attachment; filename =“# {suggested_name} .xls”这样的东西。
我建议使用更快的CSV Ruby gem来生成您的CSV,但也有内置的csv。 fastercsv示例代码(来自gem文档)如下:
csv_string = FasterCSV.generate do |csv|
  csv << ["row", "of", "CSV", "data"]
  csv << ["another", "row"]
# ...
end

谢谢你提供那个晦涩的Content-Type!我其实不确定最终是否会是Excel,但这很好知道。 - Eric

1

1
很好的回答,但为了更好,也许你可以解释一下为什么选择这个 gem(而不是其他的)。 - Taryn East

0

如果你只是想从控制台获取csv数据库,你可以用几行代码实现

tags = [Model.column_names]
rows = tags + Model.all.map(&:attributes).map(&:to_a).map { |m| m.inject([]) { |data, pair| data << pair.last } }
File.open("ss.csv", "w") {|f| f.write(rows.inject([]) { |csv, row|  csv << CSV.generate_line(row) }.join(""))}

0

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