Rails Controller to render a CSV
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