Rails 3:如何在没有模板文件的情况下响应csv?

我有一个具有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并只渲染输出,这可能吗?

我一直在努力解决同样的问题。 我可能找到了解决方案。

我在阅读Renderers.add源代码时找到了一些线索:json和:xml(链接用于Rails 3.0.10代码,3.1可能已经有一些更改): https : //github.com/rails/rails/blob/ v3.0.10 / ActionPack的/ LIB / action_controller /金属/ renderers.rb

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

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

这可以是任何东西,只需确保返回带键/值对的哈希。 Hash的工作效果优于数组,与键相比,您可以稍后在CSV输出中添加标题行。 as_csv的想法来自Rails的as_json方法,它返回一个由to_json用来生成实际JSON(文本)输出的Ruby对象。

使用as_csv方法,将以下代码放在app中的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自己添加了一些默认选项(比如:模板和其他一些选项),这会在将它们传递给to_csvto_csv 。 当然,您可以通过将自己的首选CSV选项添加到初始化程序来更改默认的CSV呈现行为。

希望这可以帮助!

这是一个古老的问题,但这里是针对新版本Rails(目前使用3.2.11和Ruby 1.9.3)的自定义渲染器的更新方法,取自ActionController :: Renderers文档: http : //api.rubyonrails.org/类/ ActionController的/ Renderers.html#方法-C-附加

正如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创建代码移动到其他类或模型,但将其放在内联中仅用于说明。

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

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

 respond_with TripReport.new.to_csv 

首先在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 

我遇到了与你所经历的类似问题,奥利弗。 我意识到在我的路径文件中我使用的是resource而不是resources 。 我不知道你的Admin :: ReportsController类中是否有其他动作或者你的路径文件是什么样的,但是如果Reports有标准的REST动作,我就会解决这个问题。

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

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

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

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