Webアプリケーションにとって、検索画面は無くてはならないものだと思います。典型的な検索画面といえば、ヘッダー部分に検索条件を入力するフィールドがあり、その下に結果一覧が表示されるもの。
さらに顧客の要望としてよく挙がるのが、「検索結果をExcelで見たい」というもの。みんなExcel好きですよねー(私も大好きですが)。
というわけで、CSV出力機能付きの検索画面を(なるべく手抜きで)実装してみよう思います。
目次
筆者の環境
- Rails 4.0.3 (別に4じゃなくても動くと思います)
Scaffold
まずはCustomerモデルを作成しましょう。サンプルアプリなので、scaffoldで済ましてしまいます。
📄Gemfile
$ rails g scaffold Customer name:string birthday:date sex:string address:string $ rake db:migrate
準備ができたら、データを適当に登録しておきます。でたらめなデータでよい場合は、私はFakerというgemでテストデータを作っています。参考までに、Faker導入手順も載せておきます。
group :development, :test do gem 'faker' end
bundle install
でインストールしたら、fixtureファイルを編集します。
📄customers.yml
<% 100.times do |i| %> customer<%= i %>: name: <%= Faker::Name.name %> birthday: <%= Faker::Business.credit_card_expiry_date - 30.years %> sex: <%= %W(male female).sample %> address: <%= Faker::Address.city %> <% end %>
編集し終わったら rake db:fixtures:load
するとdevelopmentデータベースにデータがロードされます。非常に簡単にテストデータが生成できました。
検索機能を実装する
検索機能も手抜きです。いや、手抜きというか、ransackという素晴らしいgemがあるので、これを利用しましょう。
📄Gemfile
gem 'ransack'
いつも通り bundle install
したら、次はコントローラを編集しましょう。
📄customer_controller.rb
class CustomersController < ApplicationController before_action :set_customer, only: [:show, :edit, :update, :destroy] def index @q = Customer.search(params[:q]) @customers = @q.result(distinct: true) end ...
ransackのGetting started通りの記述です。続いて、Viewに検索フォームを付加します。
📄customers/index.html.erb
<h1>Listing customers</h1> <%= search_form_for @q do |f| %> <%= f.label :name_cont, "Name" %><%= f.text_field :name_cont %> <%= f.label :birthday_eq, "Birthday" %><%= f.text_field :birthday_eq %> <%= f.label :sex_eq, "Sex" %><%= f.text_field :sex_eq %> <%= f.label :address_cont, "Address" %><%= f.text_field :address_cont %> <%= f.submit %> <% end %> <table> ...
ここまで実装できたら、実際に検索ができるか確認してみましょう。
ちゃんと検索できてます。ransackは便利だな・・・
CSV出力機能を実装する
ではいよいよ本題です。検索結果をCSV出力できるようにしましょう。実現方法は色々あると思いますが、今回は「かなり手抜き&汎用的」な方法で実現してみたいと思います。その名も、強引にページ遷移アタックです。
検索のGETリクエストは、画面に表示する場合でもCSVに出力する場合でも、内容は変わりません。URLを見てみましょう。何らかの検索をした上でURLバーを見てください。
Customerコントローラのindexアクションに?以下のパラメータが渡されるという意味ですが、CSV出力の場合も同様の検索処理なので、同じindexアクションでさばくのが良さげです。というわけで、CSV出力時は format: :csv
でリクエストが来ると仮定して、indexアクションを変更します。
📄customer_controller.rb
... def index @q = Customer.search(params[:q]) @customers = @q.result(distinct: true) respond_to do |format| format.html format.csv { send_data @customers.to_csv } end end ...
formatによって処理を分けています。format.csvで呼び出される @customers.to_csv
はCustomerモデルに実装しています。(本来はプレゼン層の仕事なので、モデルに書くべきでないとは思いますが、今回は単純化のためにモデルに実装しています。)
📄customer.rb
class Customer < ActiveRecord::Base def self.to_csv CSV.generate do |csv| csv << column_names all.each do |customer| csv << customer.attributes.values_at(*column_names) end end end end
csvをrequireする必要があるので、config/application.rbも編集します。
📄config/application.rb
require File.expand_path('../boot', __FILE__) require 'csv' require 'rails/all' # Require the gems listed in Gemfile, including any gems # you've limited to :test, :development, or :production. Bundler.require(:default, Rails.env) ...
RailsCastsまんまのコードですが笑。Railsを再起動したらCSV出力ができるはずです。画面で何か検索して結果が表示されたら、そのURLの /customers?utf8=...
の部分を /customers.csv?utf8=...
に変更してEnterを押してみてください。無事CSVがダウンロードできたでしょうか?
Excelで開くために
ダウンロードしたCSVファイルをExcelで開いてみましょう。上手く表示されている場合と、そうでない場合があると思います。それは『日本語を使用しているかどうか』によります。日本語を使用している場合は、日本語が文字化けし、場合によっては列もずれます。
かくも無残な姿。。。これはExcelでUTF8のCSVを扱うには、BOM付きで出力する必要があるからです。少しコントローラを修正しましょう。
📄customer_controller.rb
... def index @q = Customer.search(params[:q]) @customers = @q.result(distinct: true) respond_to do |format| format.html format.csv { send_csv @customers.to_csv } end end ...
send_data
メソッドを send_csv
メソッドに書き換えました。なんだ、こんな便利なメソッドあったのか・・って無いですよ、もちろん。自力で ApplicationControllerに実装するんです。
📄application_controller.rb
class ApplicationController < ActionController::Base # Prevent CSRF attacks by raising an exception. # For APIs, you may want to use :null_session instead. protect_from_forgery with: :exception protected def send_csv(csv, options = {}) bom = " " bom.setbyte(0, 0xEF) bom.setbyte(1, 0xBB) bom.setbyte(2, 0xBF) send_data bom + csv.to_s, options end end
これでBOM付きUTF8で出力できるはずです。再度CSVを出力し、Excelで開くと・・・
グレート。ひとまず、CSV出力については完了です。
CSV出力ボタンを実装する
さて最後のトドメが必要です。CSV出力ボタンを配置しなければなりません。さー、どうやって実装しましょうか?
まず最初に思い付きそうなのが、同じform内にもう一つボタンを配置するという案です。しかしこれだとindexアクションにformat無し(=html)でリクエストすることになるので、コントローラ内で処理を分岐するのが面倒臭そうです。そこで、今回はjavascriptを使用して、同じURLにフォーマットだけを変更して遷移するという、なんとも強引な方法で実装してみたいと思います。
まずは、ヘルパーにCSV出力ボタンのコードを実装します。色んな画面で使い回せそうなので、ApplicationHelperに定義することにしましょう。
📄application_helper.rb
module ApplicationHelper def button_to_csv(options = {}) button_to_function "Export CSV", "reloadWithFormat('csv');", options end end
クリックされると reloadWithFormat
というfunctionを呼び出すようにしています。このfunctionはcustomers.js.coffeeにでも定義しましょう。
📄customers.js.coffee
@reloadWithFormat = (format) -> url = "#{location.protocol}//#{location.host}#{location.pathname}.#{format}#{location.search}" location.href = url
何をしてるかというと、元のURLにフォーマットで指定された文字列を挿入して遷移してるだけ。遷移するといっても、遷移先が send_data
してるので、ブラウザ上の表示は変わらないです。では最後に、このボタンをViewに組み込みましょう。
📄customers/index.html.erb
... <tbody> <% @customers.each do |customer| %> <tr> <td><%= customer.name %></td> <td><%= customer.birthday %></td> <td><%= customer.sex %></td> <td><%= customer.address %></td> <td><%= link_to 'Show', customer %></td> <td><%= link_to 'Edit', edit_customer_path(customer) %></td> <td><%= link_to 'Destroy', customer, method: :delete, data: { confirm: 'Are you sure?' } %></td> </tr> <% end %> </tbody> </table> <%= button_to_csv %> ...
どこでもいいんですが、私は結果一覧の下に配置しました。
最後に動作を確認おきましょう。適当な条件で検索してExportボタンをクリックすると・・・
無事ダウンロードしたCSVをExcelで開けました。 完成です! お疲れ様でした!
URLを加工して遷移するなんて少々強引な気もしますが、コントローラの処理はスッキリしますし、出力ボタンも使い回しが効きます。単純なアプリなら充分実用に耐えうるのではないでしょうか。
他にもっと良い方法があれば教えて下さいね。
関連する記事
- 【実践】RailsでExcelレポート出力(その1)
- Ruby on Railsでカンマ付き金額を扱う
- chosen-railsによる検索機能付きセレクトボックスで、検索画面作成の手間を省く
- simple_formとTwitter bootstrapで作る俺流鉄板Railsアプリ(その1)
- 自分はこんな感じでRailsアプリを作っております
ピンバック: モデルのデータを CSV として出力する方法 [Rails] – Site-Builder.wiki