Edited at

Rails - .rubyテンプレートでCSVダウンロード

More than 3 years have passed since last update.

Screenshot 2015-08-03 16.24.10.png


経緯


  • .ruby template handlerを使用するとCSV関連のロジックを綺麗にモデルから分離できると知り調査。

  • 今後のためにメモ。


やりたいこと


  • UsersテーブルのデータをCSVとしてエクスポートしたい。

  • ビューテンプレートに実装したい。


この方法の長所


  • ビューテンプレートに実装すると、(何もしなくても)ビューヘルパーメソッドにアクセス可能。

  • モデルに実装しないので、モデルをコンパクトにできる。

例:railscasts/379-template-handlers

response.headers["Content-Disposition"] = 'attachment; filename="products.csv"'

CSV.generate do |csv|
csv << ["Name", "Price", "URL"]
@products.each do |product|
csv << [
product.name,
number_to_currency(product.price),
product_url(product)
]
end
end


環境


  • Ruby 2.2.1

  • Rails 4.2.3


手順


/config/application.rbでCSVモジュールをrequire


/config/application.rb

require File.expand_path('../boot', __FILE__)

require 'csv'
require 'rails/all'

# ...



ヘッダーを設定するためのメソッド


/app/helpers/csv_helper.rb

module CsvHelper

def set_file_headers(options)

[:filename, :disposition].each do |arg|
raise ArgumentError, ":#{arg} option required" if options[arg].nil?
end

disposition = options[:disposition]
disposition += %(; filename="#{options[:filename]}") if options[:filename]

headers.merge!(
'Content-Disposition' => disposition,
'Content-Transfer-Encoding' => 'binary'
)
end
end



エクスポートしたいデータをクエリ


/app/controllers/users_controller.rb

class UsersController < ApplicationController

include CsvHelper
# ...

def index
@users = User.all
end

# ...
end



*.csv.rubyファイルを新規作成し、CSV処理をビューテンプレートとして実装


/app/views/users/index.csv.ruby

# ==> 1. Set response headers

# http://api.rubyonrails.org/classes/ActionDispatch/Request.html#method-i-headers
set_file_headers filename: "users-#{Date.today}.csv",
disposition: "attachment"

# ==> 2. Set options if you want (e.g. :col_sep, :headers, etc)
# http://ruby-doc.org/stdlib-2.0.0/libdoc/csv/rdoc/CSV.html#DEFAULT_OPTIONS
options = { headers: true }

# ==> 3. Generate csv that is to be downloaded

attributes = %w(id username sign_in_count created_at confirmed_at updated_at)

CSV.generate(options) do |csv|
# Column names in a first row
csv << attributes

# Write each record as an array of strings
@users.unscoped.each do |user|
csv << attributes.map{ |attr| user.send(attr) }
end
end



users_path(format: "csv")へのリンクを好きなところに設置


/app/views/users/index.html.haml

= link_to "CSVダウンロード", users_path(format: "csv"), class: "btn btn-warning"



実際に出力されたデータ

"<pre class=\"debug_dump\"><kbd style=\"color:brown\">&quot;id,username,sign_in_count,created_at,confirmed_at,updated_at\\n1,Masa Nishiguchi,2,2015-07-30 20:30:25 UTC,2015-07-30 20:30:24 UTC,2015-08-02 22:07:55 UTC\\n2,Elton Gottlieb,0,2015-07-30 20:30:25 UTC,2015-07-30 20:30:25 UTC,2015-07-30 20:30:25 UTC\\n3, (...中略...) ,Elroy Howe,1,2015-07-30 20:30:25 UTC,2015-07-30 20:30:25 UTC,2015-07-31 13:40:07 UTC\\n&quot;</kbd></pre>"

Screenshot 2015-08-03 16.32.49.png


テスト

RSpecのコントローラスペックは、デフォルトの状態ではresponse.bodyが空になっておりresponse.bodyによるCSV内容確認は不可能。

render_viewsを用いて強制的にrspecにビューを生成させることが可能ということを知り、下記のテストを作成。

@hidakatsuyaさん、情報ありがとうございました。)


/spec/controllers/users_controller_spec.rb

RSpec.describe UsersController, type: :controller do

# ...

describe "admin user" do
before { log_in_as FactoryGirl.create(:admin), no_capybara: :true }

describe 'GET #index' do

before(:all) { 10.times { FactoryGirl.create(:user) } }

it "renders the index page" do
get :index
expect(response).to render_template :index
end

describe "CSV format" do
render_views #<== 強制的にrspecにビューを生成させる

before { get :index, format: "csv" }

let(:user) { User.first}

it { expect(response).to render_template :index }
it { expect(response.headers["Content-Type"]).to include "text/csv" }

attributes = %w(id username sign_in_count created_at confirmed_at updated_at)

attributes.each do |field|
it "has column name - #{field}" do
expect(response.body).to include field
end
end

attributes.each do |field|
it "has correct value for #{field}" do
expect(response.body).to include user[field].to_s
end
end

it "has correct number of rows" do
num_of_rows = 1 + User.all.count
expect(response.body.split(/\n/).size).to eq num_of_rows
end
end
end

# ...
end
end


Screenshot 2015-08-04 10.09.25.png

シンプルなテストの例


資料

Ruby CSV library

CSV export

Template Handlers

Rails

.rb template handlerが.rubyに変更された経緯

RSpec