Подтвердить что ты не робот

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 на объекте и просто выводит вывод, возможно ли это?

4b9b3361

Ответ 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