Rails 3: Как ответить с csv без файла шаблона? - PullRequest
18 голосов
/ 03 марта 2011

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

Ответы [ 6 ]

14 голосов
/ 28 сентября 2011

Я боролся с точно такой же проблемой. Я мог бы найти решение.

Я нашел некоторые подсказки, читая исходный код 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

Это может быть что угодно, просто обязательно верните хеш с парами ключ / значение. Хэш работает лучше, чем массив, так как с ключами вы можете добавить строку заголовка к выходу 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

Код инициализатора в значительной степени основан на поведении: json и: xml из исходного кода Rails (см. Ссылку выше).

В настоящее время хэш options, переданный в блок, не передается в вызов to_csv, так как CSV довольно требователен к тому, какие опции он позволяет отправлять. Rails добавляет некоторые опции по умолчанию (например, template и некоторые другие), что выдает ошибку при передаче их в to_csv. Конечно, вы можете изменить поведение рендеринга CSV по умолчанию, добавив свои предпочтительные параметры CSV в инициализатор.

Надеюсь, это поможет!

10 голосов
/ 02 февраля 2013

Это старый вопрос, но вот обновленный метод для пользовательского 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
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 в какой-то другой класс или модель, но поместите его здесь, чтобы проиллюстрировать это.

2 голосов
/ 03 марта 2011

Я думаю, что ваша модель должна иметь метод to_csv, который возвращает атрибуты как csv.

После этого, если Rails не будет вызывать метод to_csv неявно, я бы попробовал

respond_with TripReport.new.to_csv
1 голос
/ 29 июля 2015

Сначала создайте средство визуализации для типа 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
0 голосов
/ 01 июля 2014

Одним из возможных решений является реализация другого представления со всеми необходимыми данными.

# controller code
respond_to :html, :csv

def index
  respond_with Person.all
end

# view
views/persons/index.csv.erb
0 голосов
/ 04 мая 2011

Я столкнулся с тем, что, как я предполагаю, является проблемой, с которой вы столкнулись, Оливер. Я понял, что в моем файле маршрутов я использовал resource вместо resources. Я не знаю, есть ли у вас другие действия в вашем классе Admin :: ReportsController или как выглядит ваш файл маршрутов, но именно так я бы решил проблему, если бы в Reports были стандартные действия REST.

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

Если это не применимо, запустите rake routes, чтобы проверить, правильно ли настроены ваши маршруты.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...