HHH v4

Rails Controller to render a CSV

Edit
equivalent Web Development
Public

Solution 1 - Use CSV#generate and send_data

# app/controllers/exports_controller.rb
class ExportsController < ApplicationController
  before_action :set_model

  def create
    respond_to do |format|
      format.csv { send_data csv_string, filename: filename }
    end
  end

  private

  def csv_string
    CsvService.new(@model).call
  end
   
  def set_model
    @model = Model.find(params[:model_id])
  end

  def filename
    "model-#{@model.id}_#{DateTime.now.strftime("%Y-%m-%d_%H%M")}.csv"
  end
end

# app/services/csv_service.rb
class CsvService
  attr_reader :model

  def initialize(model)
    @model = model
  end

  def call
    CSV.generate do |csv|
      csv << ["Id", "Name"]
      model.other_models.map do |other_model|
         csv << [other_model.id, other_model.name]
      end
    end
  end
end


Solution 2 - Use Rails to render view/partials

Simmilar approapproach  like jbuilder

# app/controllers/exports_controller.rb
class ExportsController < ApplicationController
  before_action :set_model

  def create
    respond_to do |format|
      format.csv { render :create }
    end
  end

  private

  def set_model
    @model = Model.find(params[:id])
  end
end

# app/controllers/views/exports/create.csv.erb
Id,Name
<% model.other_models.each do |other_model| %>
<%= render "exports/row", other_model: other_model %>
<% end %>

# app/controllers/views/exports/_row.csv.erb
<%= other_model.id -%>,<%= sanitize_for_csv other_model.name -%>


# app/helpers/csv_helper.rb
module CsvHelper
  def sanitize_for_csv(value)
    CSV.generate_line([value.to_s]).strip.gsub("\n",'\n').html_safe
  end
end


If you need a filname:

def create
  #...
  respond_to do |format|
    str = render_to_string action: :create, layout: false, formats: [:csv]
    format.csv { send_data str, filename: filename }
  end
  
  private
  
  def filename
    "campaign-#{@campaign_strategy.id}_#{DateTime.now.strftime("%Y-%m-%d_%H%M")}.csv"
  end
end


Test/RSpec



# spec/request/exports_spec.rb
RSpec.describe ExportsController, type: :request do
  let(:company) { create(:company, :with_user) }
  let(:result) { CSV.parse response.body }

  describe "POST /models/123/exports" do
    it "generates a CSV " do
      model = create(:model)
      other_model = create :other_model, name: "Infinity Gauntlet"
      model.other_models << other_model

      post model_exports_path(model, format: :csv)

      expect(response).to have_http_status(200)
      expect(response.headers["Content-Type"]).to eq("text/csv")
      expect(result[0]).to eq(["Id", "Name"])
      expect(result[1]).to eq(["#{other_model.id}", "Infinity Gauntlet"])
    end
  end
end