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

发布于 2024-10-20 06:47:32 字数 643 浏览 1 评论 0原文

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

I have an object that has a to_csv method and I want to pass it to respond_with to render csv from my controller. My code looks like this:

class Admin::ReportsController < AdminController

  respond_to :csv

  def trips
    respond_with TripReport.new
  end
end

Instances of TripReport have a to_csv method.

When I make a request to that action I get the following error:

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

So it looks like the controller is looking for a template file to render. How can I get around this?

I'd rather the csv format responded in a similar way to json, so it calls to_csv on the object and just renders the output, is this possible?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

清风无影 2024-10-27 06:47:32

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

我在阅读 :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

这可以是任何内容,只需确保返回一个哈希值即可带有键/值对。哈希比数组效果更好,因为使用键您可以稍后向 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 呈现行为。

希望这有帮助!

I've been struggling with the exact same problem. I might have found a solution.

I found some clues while reading the Renderers.add source code for :json and :xml (link is for Rails 3.0.10 code, 3.1 might have some changes already):
https://github.com/rails/rails/blob/v3.0.10/actionpack/lib/action_controller/metal/renderers.rb

First, add a simple as_csv method to your model definition:

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

This can be anything, just make sure to return a hash with key/value pairs. A Hash works better than an Array, as with keys you're able to add a header row to the CSV output later on. The idea for as_csv comes from Rails' as_json method, which return a Ruby object that is used by to_json to generate the actual JSON (text) output.

With the as_csv method in place, put the following code in a file in config/initializers inside your app (name it csv_renderer.rb, for example):

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

And finally, add CSV support to your controller code:

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

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

  # ...

end

The initializer code is largely based on the :json and :xml behaviour from the Rails source code (see link above).

Currently, the options hash passed to the block doesn't get passed to the to_csv call, as CSV is quite picky on which options it allows to be sent. Rails adds some default options by itself (like :template and some others), which gives you an error when passing them to to_csv. You can change the default CSV rendering behaviour by adding your own preferred CSV options to the initializer, of course.

Hope this helps!

赏烟花じ飞满天 2024-10-27 06:47:32

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

正如 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 创建代码移动到其他类或模型,但将其内联放在此处只是为了说明。

This is an old question but here's an updated method for the custom Renderer for newer versions of Rails (currently using 3.2.11 and Ruby 1.9.3) taken from the ActionController::Renderers documentation: http://api.rubyonrails.org/classes/ActionController/Renderers.html#method-c-add

As florish said, create an initializer but add this code:

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

And use it as such:

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

I take no credit for the code above, it's straight from the documentation, but this worked for me in Rails 3.2.11 so pointing it out for people coming across this thread for the first time.

In my project I'm not using a to_csv method, I'm actually building the CSV manually first. So here's what mine looks like:

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

You should obvious move the CSV creation code to some other class or model but putting it here inline just to illustrate.

烟雨扶苏 2024-10-27 06:47:32

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

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

respond_with TripReport.new.to_csv

I think your model would have to have a to_csv method that returns the attributes as csv.

After that, if Rails doesn't call the to_csv method implicitly, I would try

respond_with TripReport.new.to_csv
醉态萌生 2024-10-27 06:47:32

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

First create a renderer for the CSV mime type in config/initializers/csv_renderer.rb

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

Then add a to_csv method to your model. If your data is an array or hash you might consider creating a new class for that collection with its own to_csv and to_json methods, instead of having everything in the controller. If its an ActiveRecord model you can use the following in an initializer:

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

You can then pass an ActiveRecord relation to respond_with:

def index
  respond_with(Item.all, filename: 'items')
end
傲世九天 2024-10-27 06:47:32

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

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

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

I ran into what I would guess is a similar problem to what you were experiencing, Oliver. I realized that in my routes file I was using resource instead of resources. I do not know if you have other actions in your Admin::ReportsController class or what your routes file looks like, but this is how I would tackle the problem if Reports has the standard REST actions.

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

If that does not apply, run rake routes to see if your routes are configured correctly.

瘫痪情歌 2024-10-27 06:47:32

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

# controller code
respond_to :html, :csv

def index
  respond_with Person.all
end

# view
views/persons/index.csv.erb

One of the possible solutions is to implement another view with all data you want.

# controller code
respond_to :html, :csv

def index
  respond_with Person.all
end

# view
views/persons/index.csv.erb
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文