#経緯
- .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'
# ...
ヘッダーを設定するためのメソッド
- ActionController::Streaming::send_file_headers!を参考にした。
- 細かい記述方法を気にしなくてよくなる。
- 必要なデータの渡し忘れを防げる。
/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\">"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"</kbd></pre>"
#テスト
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
シンプルなテストの例
#資料
Ruby CSV library
CSV export
- Railscasts #362 Exporting Csv And Excel
- Railscasts #362 Exporting Csv And Excel - YouTube
- GoRails #45 Exporting Records To CSV
Template Handlers
- Railscasts PRO #379 Template Handlers (pro)
- [Railscasts PRO #379 Template Handlers (pro) - YouTube] (https://www.youtube.com/watch?v=lEnw14mjhLk)
Rails
- rails/actionview/lib/action_view/template/handlers.rb
- rails/ActionController/Streaming/DEFAULT_SEND_FILE_OPTIONS
- rails/ActionController/Streaming/send_file
- rails/ActionController/Streaming/send_file_headers!
.rb template handlerが.rubyに変更された経緯
RSpec