【问题标题】:Rails 3: How to respond_with csv without having a template file?Rails 3:如何在没有模板文件的情况下响应 csv?
【发布时间】:2011-03-03 14:55:06
【问题描述】:

我有一个具有to_csv 方法的对象,我想将它传递给respond_with 以从我的控制器呈现csv。我的代码如下所示:

class Admin::ReportsController < AdminController

  respond_to :csv

  def trips
    respond_with TripReport.new
  end
end

TripReport 的实例有一个 to_csv 方法。

当我对该操作发出请求时,我收到以下错误:

ActionView::MissingTemplate (Missing template admin/reports/trips with {:formats=>[:csv], :handlers=>[:erb, :builder, :rjs, :rhtml, :rxml], :locale=>[:en, :en]} in view paths

所以看起来控制器正在寻找要渲染的模板文件。我该如何解决这个问题?

我宁愿 csv 格式以类似于 json 的方式响应,所以它在对象上调用 to_csv 并只渲染输出,这可能吗?

【问题讨论】:

  • 嗨奥利弗,你有什么运气解决这个问题吗?我在这里遇到了同样的问题。

标签: ruby-on-rails csv


【解决方案1】:

我一直在努力解决完全相同的问题。我可能已经找到了解决方案。

我在阅读 :json 和 :xml 的 Renderers.add 源代码时发现了一些线索(链接是 Rails 3.0.10 代码,3.1 可能已经有一些变化): https://github.com/rails/rails/blob/v3.0.10/actionpack/lib/action_controller/metal/renderers.rb

首先,在模型定义中添加一个简单的as_csv 方法:

class Modelname < ActiveRecord::Base
  # ...
  def as_csv
    attributes
  end
end

这可以是任何东西,只要确保返回带有键/值对的散列即可。 Hash 比 Array 更有效,因为您可以稍后将标题行添加到 CSV 输出中。 as_csv 的想法来自 Rails 的 as_json 方法,该方法返回一个 Ruby 对象,to_json 使用该对象来生成实际的 JSON(文本)输出。

使用as_csv 方法,将以下代码放入您应用内config/initializers 的文件中(例如,将其命名为csv_renderer.rb):

require 'csv' # adds a .to_csv method to Array instances

class Array 
  alias old_to_csv to_csv #keep reference to original to_csv method

  def to_csv(options = Hash.new)
    # override only if first element actually has as_csv method
    return old_to_csv(options) unless self.first.respond_to? :as_csv
    # use keys from first row as header columns
    out = first.as_csv.keys.to_csv(options)
    self.each { |r| out << r.as_csv.values.to_csv(options) }
    out
  end
end

ActionController::Renderers.add :csv do |csv, options|
  csv = csv.respond_to?(:to_csv) ? csv.to_csv() : csv
  self.content_type ||= Mime::CSV
  self.response_body = csv
end

最后,为您的控制器代码添加 CSV 支持:

class ModelnamesController < ApplicationController
  respond_to :html, :json, :csv

  def index
    @modelnames = Modelname.all
    respond_with(@modelnames)
  end

  # ...

end

初始化代码主要基于 Rails 源代码中的 :json 和 :xml 行为(参见上面的链接)。

目前,传递给块的 options 哈希不会传递给 to_csv 调用,因为 CSV 对允许发送哪些选项非常挑剔。 Rails 自己添加了一些默认选项(例如 :template 和其他一些选项),当您将它们传递给 to_csv 时会出现错误。当然,您可以通过将自己喜欢的 CSV 选项添加到初始化程序来更改默认的 CSV 呈现行为。

希望这会有所帮助!

【讨论】:

    【解决方案2】:

    这是一个老问题,但这里是更新版本的 Rails(当前使用 3.2.11 和 Ruby 1.9.3)的自定义渲染器的更新方法,取自 ActionController::Renderers 文档:http://api.rubyonrails.org/classes/ActionController/Renderers.html#method-c-add

    正如 florish 所说,创建一个初始化程序,但添加以下代码:

    ActionController::Renderers.add :csv do |obj, options|
      filename = options[:filename] || 'data'
      str = obj.respond_to?(:to_csv) ? obj.to_csv : obj.to_s
      send_data str, :type => Mime::CSV,
        :disposition => "attachment; filename=#{filename}.csv"
    end
    

    并像这样使用它:

    def show
      @csvable = Csvable.find(params[:id])
      respond_to do |format|
        format.html
        format.csv { render :csv => @csvable, :filename => @csvable.name }
      end
    end
    

    我不相信上面的代码,它直接来自文档,但这在 Rails 3.2.11 中对我有用,所以向第一次遇到这个线程的人指出。

    在我的项目中,我没有使用 to_csv 方法,我实际上是先手动构建 CSV。所以这是我的样子:

    def show
      items = Item.where(something: true)
      csv_string = CSV.generate do |csv|
        # header row
        csv << %w(id name)
        # add a row for each item
        items.each do |item|
          csv << [item.id, item.name]
        end
      end
      respond_to do |format|
        format.csv { render :csv => csv_string, :filename => "myfile.csv" }
      end
    end
    

    您显然应该将 CSV 创建代码移至其他一些类或模型,但将其内联放在此处只是为了说明。

    【讨论】:

      【解决方案3】:

      我认为您的模型必须有一个 to_csv 方法,将属性作为 csv 返回。

      之后,如果 Rails 不隐式调用 to_csv 方法,我会尝试

      respond_with TripReport.new.to_csv
      

      【讨论】:

      • 正如我所说,我的对象响应to_csv。将字符串传递给 respond_with 不起作用,它期望可以响应 to_#{format}
      【解决方案4】:

      首先为config/initializers/csv_renderer.rb中的CSV mime类型创建一个渲染器

      ActionController::Renderers.add :csv do |collection, options|
        self.content_type ||= Mime::CSV
        self.headers['Content-Disposition'] = "attachment; filename=#{options[:filename]}.csv" if options[:filename]
        self.response_body = collection.to_csv
      end
      

      然后将to_csv 方法添加到您的模型中。如果您的数据是数组或散列,您可能会考虑使用自己的 to_csv 和 to_json 方法为该集合创建一个新类,而不是将所有内容都放在控制器中。如果它是一个 ActiveRecord 模型,您可以在初始化程序中使用以下内容:

      require 'csv'
      
      module CsvRenderer
        def to_csv(options={})
          columns = column_names
          columns += options[:include] if options[:include]
          CSV.generate do |csv|
            csv << columns
            all.pluck(*columns).each do |row|
              csv << row
            end
          end
        end
      end
      
      ActiveRecord::Base.extend CsvRenderer
      

      然后您可以将 ActiveRecord 关系传递给 respond_with:

      def index
        respond_with(Item.all, filename: 'items')
      end
      

      【讨论】:

        【解决方案5】:

        Oliver,我猜我遇到了与您遇到的类似的问题。我意识到在我的路由文件中我使用的是resource 而不是resources。我不知道您的 Admin::ReportsController 类中是否还有其他操作,或者您的路由文件是什么样的,但如果 R​​eports 具有标准 REST 操作,我会这样解决问题。

        scope :module => 'Admin' do
          resources :reports do
            get :trips, :on => :collection
          end
        end
        

        如果不适用,请运行 rake routes 以查看您的路由是否配置正确。

        【讨论】:

          【解决方案6】:

          一种可能的解决方案是使用您想要的所有数据实现另一个视图。

          # controller code
          respond_to :html, :csv
          
          def index
            respond_with Person.all
          end
          
          # view
          views/persons/index.csv.erb
          

          【讨论】:

            猜你喜欢
            • 2011-05-06
            • 2011-06-22
            • 2021-10-30
            • 1970-01-01
            • 2015-06-18
            • 2020-09-27
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多