ユーザーが画面に表示されている一覧情報をダウンロードしたい、という場合によく使われるのがcsv形式。
Ruby on Railsでcsv出力をしようとして、情報を集めていると色々な実装手順を見かけました。使っているライブラリなどはほとんど変わらないのですが、実装箇所が異なっているので、ちょっと混乱してしまいました。
今回は自分が調べた結果のまとめとしてRuby on Railsでcsv出力を行う際の3つのアプローチをご紹介していきます。
前提
基本的にRubyのライブラリであるCSV.generateを利用します。もっともシンプルにcsvの出力を行えます。
名前(name),説明(description),価格(price)という情報を持った製品(product)モデルのデータを表示させるとしましょう。
csv_data = CSV.generate do |csv| csv_column_names = ["製品名","製品説明","価格"] csv << csv_column_names @products.each do |product| csv_column_values = [ product.name, product.description, product.price, ] csv << csv_column_values end end
上記のように配列を渡していく形で、直感的に1行ずつcsv形式でデータを出力することができます。
注意しておいて欲しいのが、CSV.generate自体はcsvファイルを出力するのではなく、csv文字列を作成しているだけであるということです。上の例だとcsv_dataに入るのはStringのデータとなります。
また、これから話す手順のうち1と2はcsv出力用のviewファイルし、3はコントローラー内でcsvの作成と出力を行います。
いずれの方法も実際に動作確認済みです。
ではそれぞれの手順を見ていきましょう。
①csv出力用のアクションを作成する方法
まずは一番わかりやすいcsv出力用のアクションを作成する実装方法です。
この方法のメリット
- わかりやすい
この方法のデメリット
- REST設計に背く形となる
REST設計についての詳細はこちらを参考ください。
実装コード
コントローラー
一覧出力したいモデルのコントローラーにcsv出力専用のアクションを新規作成する。
class ProductsController < ApplicationController def csv_output @products = Product.all send_data render_to_string, filename: "products.csv", type: :csv end end
ルーティング
他の基本アクションも実装する前提とすると以下の通り。collectionの中に作成したいアクションを含めます。
Rails.application.routes.draw do root 'products#index' resources :products do collection do get 'csv_output' end end end
csv出力ファイル(ビュー)
最後に出力処理本体となるrubyファイル。link_toのパスにformat: :csvを設定すると自動で呼び出されます。なぜこの拡張子なのかは不明。
require 'csv' CSV.generate do |csv| csv_column_names = ["製品名","製品説明","価格"] csv << csv_column_names @products.each do |product| csv_column_values = [ product.name, product.description, product.price, ] csv << csv_column_values end end
ダウンロードリンク
最後にファイル出力を行うためのリンク。できればかっこいいボタンに仕上げましょう。今回は無骨にただのリンク。ポイントはformat: :csvです。
# 省略 <%= link_to "csv出力",csv_output_products_path(format: :csv) %> # 省略
所感
RESTにこだわる方から石を投げられそうな実装です。確かにindexで実装できそうな内容を新しくルーティングして作るというのは微妙。しかし複数のモデルにまたがる出力をしたい時などは使い所がありそうではあります。あとはどうしてもindexアクションに修正を加えたくない時とか。
②indexアクションからcsv出力ビューへ
一覧出力したいモデルのコントローラーのindexアクションから、formatを判定して、処理を分岐し、csv出力ファイルをレンダリングする形になります。csv出力ファイル(csv_output.csv.ruby)の内容は①と同様。
この方法のメリット
- REST設計に則っている
この方法のデメリット
- indexアクションに修正が必要
実装コード
コントローラー
respond_toで呼び出し時のformatを判定し、csvの場合、csv出力ファイルを呼び出します。それ以外の場合は通常通り、html.erbファイルが呼び出されます。
class ProductsController < ApplicationController def index @products = Product.all respond_to do |format| format.html format.csv do send_data render_to_string, filename: "products.csv", type: :csv end end end end
ルーティング
ルーティングに特別な設定は不要です。indexが呼び出されるルートが設定されていればOKです。
Rails.application.routes.draw do resources :products end
csv出力ファイル(ビュー)
①と同様のソースでOKです。
ダウンロードリンク
いつも通りのindex指定にformat: :csvを付け足すだけです。ボタンはかっこよく仕上げましょう。
<%= link_to "csv出力",products_path(format: :csv) %>
所感
一番ベーシックな実装ではないかと思います。特に欠点も見当たりませんし。もしcsv処理をコントローラーに一任したい場合は③の方法をオススメします。
③indexアクションのみで実装
indexアクション内、またはメソッドを作成してその中でcsvの作成と出力を行います。
この方法のメリット
- 処理がコントローラー内で完結する
この方法のデメリット
- コントローラーが複雑になる可能性がある
実装コード
コントローラー
respond_toで呼び出し時のformatを判定する部分までは②と同様。ただし、csv出力ファイルをレンダリングするのではなく、別実装したメソッドにてcsv生成と出力を行います。
一旦CSV.generateを変数に格納し、send_dataでファイルを出力しています。
class ProductsController < ApplicationController def index @products = Product.all respond_to do |format| format.html format.csv do products_csv end end end private def products_csv csv_date = CSV.generate do |csv| csv_column_names = ["製品名","製品説明","価格"] csv << csv_column_names @products.each do |product| csv_column_values = [ product.name, product.description, product.price, ] csv << csv_column_values end end send_data(csv_date,filename: "product3.csv") end end
ルーティング
②と同様に特別なルーティングは必要ありません。
csv出力ファイル(ビュー)
csv出力ファイルを作成する必要はありません。
ダウンロードリンク
②と同様です。かっこよく仕上げましょう。
所感
csvをビューに実装するということに気持ち悪さを感じる人にはいい実装方法かと思われます。個人的にはスタイリッシュで嫌いではないです。
おまけ
文字コードを指定したい
CSV.generateにオプションを指定するだけでOKです。①の実装例で試すとこんな感じ。
require 'csv' CSV.generate(encoding: Encoding::SJIS) do |csv| csv_column_names = ["製品名","製品説明","価格"] csv << csv_column_names @products.each do |product| csv_column_values = [ product.name, product.description, product.price, ] csv << csv_column_values end end
タイトルや空白行を出力したい
以下の通りの実装で行けます。渡す値が配列であれば問題ありません。こちらも①の実装例で。
require 'csv' CSV.generate do |csv| # タイトル csv << ["製品一覧"] # 空白行 csv << [] csv_column_names = ["製品名","製品説明","価格"] csv << csv_column_names @products.each do |product| csv_column_values = [ product.name, product.description, product.price, ] csv << csv_column_values end end
出力されるファイルは以下のようになります。
製品一覧 製品名,製品説明,価格 product1,this is product1,1000
まとめ
いかがだったでしょうか。あなた自身や開発現場のコーディング方針に適した手法を選択していただければ。
閲覧いただき、ありがとうございました。
-
-
文系出身エンジニアが26才でフリーランスになった感想
こんにちは。フリーランスエンジニアのてぃすです。 フリーランスエンジニアということは、つまりエンジニアとして独立しているということになり、すごい敷居が高い印象を持っている人が多いです。 けど僕は文系大 ...
続きを見る