Rails 3: How to respond_with csv without having a template file?

后端 未结 6 2319
时光取名叫无心
时光取名叫无心 2020-12-28 17:29

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:

6条回答
  •  一生所求
    2020-12-28 17:44

    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!

提交回复
热议问题