Search code examples
ruby-on-railscsvnested-resources

Rails - How to export nested resources to CSV in Rails?


I am trying to export some data to CSV in Rails 4. I have two models: Excursions and Inscriptions. One excursion has many inscriptions and one inscription belongs to one excursion.

I have my nested routes defined this way:

  resources :excursions do
    resources :inscriptions
    get 'exportcsv' => 'excursions#download'
  end

So the behavior I am trying to achieve is: when I visit the route /excursions/1/exportcsv, a CSV will be downloaded to my computer and it will contain all the inscriptions with excursion_id = 1 in CSV format.

In my excursion model I have defined self.to_csv:

  def self.to_csv(options = {})
    CSV.generate(options) do |csv|
      csv << column_names
      self.inscriptions.each do |inscription|
        csv << inscription.attributes.values_at(*column_names)
      end
    end
  end

And my excursion controller's method download:

  def download
    @excursion = Excursion.find(params[:id])
    respond_to do |format|
      format.html
      format.csv { send_data @excursion.to_csv }
    end
  end

EDIT: When I go to a route like: /excursions/:id/exportcsv the server is throwing an ActiveRecord::RecordNotFound error. This error is easy to solve, but if I solve the RecordNotFound I get an ActionController::UnknownFormat in this line:

  def download
    @excursion = Excursion.find(params[:id])
    respond_to do |format|  ########THIS LINE
      format.html
      format.csv { send_data @excursion.to_csv }
    end
  end

What I am doing wrong? Maybe all this approach is not correct...


Solution

  • I would update the routes to the following:

    resources :excursions do
      get 'download', on: :member, constraints: { format: /(html|csv)/ }
      resources :inscriptions
    end
    

    Also there is a bug in your model code. You are exporting inscriptions but are using Excursion column_names instead of Inscription column_names. Following is the updated model code.

    class Excursion < ActiveRecord::Base
      def to_csv(options = {})
        CSV.generate(options) do |csv|
        inscription_column_names = Inscription.column_names
    
        csv << inscription_column_names
    
        self.inscriptions.each do |inscription|
          csv << inscription.attributes.values_at(*inscription_column_names)
        end
      end
    end
    

    Now try to access http://localhost:3000/excursions/:id/download.csv

    Replace :id with an existing Excursion records id. If you still encounter ActiveRecord::RecordNotFound error, then the problem could be that you are trying to access an Excursion that doesn't actually exist in the database.