Rails 3: Как ответить_cith 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
на объекте и просто выводит вывод, возможно ли это?
Ответы
Ответ 1
Я боролся с той же проблемой. Я мог бы найти решение.
Я нашел некоторые подсказки при чтении исходного кода Renderers.add для: json и: xml (ссылка для кода 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
Это может быть что угодно, просто верните хэш с парами ключ/значение. Хэш работает лучше, чем Array, так как с ключами вы можете добавить строку заголовка в выход CSV позже. Идея as_csv
исходит из метода Rails 'as_json
, который возвращает объект Ruby, который используется to_json
для генерации фактического вывода JSON (text).
С помощью метода 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
Код инициализатора в основном основан на: json и: xml исполнении исходного кода Rails (см. ссылку выше).
В настоящее время хеш options
, переданный блоку, не передается в вызов to_csv
, поскольку CSV довольно разборчив, какие параметры он позволяет отправлять. Rails добавляет некоторые параметры по умолчанию самостоятельно (например: шаблон и некоторые другие), что дает вам ошибку при передаче их в to_csv
. Вы можете изменить поведение рендеринга CSV по умолчанию, конечно же, добавив свои собственные предпочтительные параметры CSV для инициализатора.
Надеюсь, это поможет!
Ответ 2
Это старый вопрос, но здесь обновленный метод для настраиваемого Renderer для новых версий 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
Я не беру на себя ответственность за приведенный выше код, это прямо из документации, но это работало для меня в 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
Ответ 4
Сначала создайте рендерер для типа mime CSV в 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
Затем добавьте метод 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 к response_with:
def index
respond_with(Item.all, filename: 'items')
end
Ответ 5
Я столкнулся с тем, что, как я думаю, похож на проблему, с которой вы столкнулись, Оливер. Я понял, что в файле маршрутов я использовал resource
вместо resources
. Я не знаю, есть ли у вас другие действия в вашем классе Admin:: ReportController или как выглядит ваш файл маршрутов, но я решил бы решить эту проблему, если отчеты имеют стандартные действия 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