概要
Rails で Exel ファイル (.xlsx) を出力する必要が生じたので、Axlsx を利用してみた。導入時、ぽっこり空いていた落とし穴に物の見事にハマってしまったが、どうにか抜け出すことができた。無事生還。
Axlsx
Axlsx は Ruby 用の Office Open XML Spreadsheet 生成器らしいです。.xlsx
ファイルを簡単に生成してくれる凄い奴です。
Axlsx is an Office Open XML Spreadsheet generator for the Ruby programming language.
シノプシスって英単語、英語力低すぎなので初めて見たんですが、要約とか梗概って意味らしいですね。知らなかった。
Office Open XML
Office Open XML Spreadsheet ってなんやねん。俺は Excel(.xlsx) ファイルが欲しいねん。って方向けの補足です。
Office Open XML (OpenXML、OOXML) とは、XMLをベースとしたオフィススイート用のファイルフォーマットである。
Office Open XML は Microsoft Office 2007 以降で採用されているファイルフォーマットで、いわゆる.docx
とか.xlsx
とかのことです。つまり、Axlsx が生成してくれる Office Open XML Spreadsheetとは、.xlsx
ファイルのことです。
詳細は Wikipedia 大先生が語ってくれてます。
導入
Gemfile
Gemfile に以下を追記し、bundle install
を実行!
gem 'axlsx'
gem 'zip-zip'
zip-zip
という Gem をaxlsx
内部で使っているようなので、一緒に導入します。こいつがないとcannot load such file -- zip/zip (LoadError)
と、盛大に怒られます。怒られました。
MIME TYPE
コントローラーでrespond_to
を使って処理したいので、MIME TYPE を Rails に追加します。
.xlsx
の MIME TYPE はapplication/vnd.openxmlformats-officedocument.spreadsheetml.sheet
だそうです。とっても長くてびっくり。
下記の通り、mime_types.rb
に登録しておきます。
Mime::Type.unregister :xlsx
Mime::Type.register 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', :xlsx
unregister
については、必須ではないと思いますが、行っていない場合下記のような警告が出てしまいました。
warning: already initialized constant Mime::XLSX
warning: previous definition of XLSX was here
これで、respond_to
が使えるようになります。
def show
respond_to do |format|
format.html
format.xlsx do
# ここで処理する
end
end
end
MIME TYPE を追加しなかった場合、respond_for
を用いるとTo respond to a custom format, register it as a MIME type
といった内容のエラーを吐きます。
実装
すごく単純に、show
アクションに.xlsx
のフォーマットで要求が来たら、Excel ファイルを出力するというプログラムを実装してみました。
呼び出し元の画面には以下のようなリンクを記述しました。
<%= link_to "Excel出力", user_path(@user, format: :xlsx) %>
このとき生成されるURLは/user/id.xlsx
となるため、前述の通りrespond_to
を用いて処理できます。
def show
@user = User.find(params[:id])
respond_to do |format|
format.html
format.xlsx do
generate_xlsx # xlsxファイル生成用メソッド
end
end
end
private
def generate_xlsx
Axlsx::Package.new do |p|
p.workbook.add_worksheet(name: "シート名") do |sheet|
sheet.add_row ["First Column", "Second", "Third"]
sheet.add_row [1, 2, 3]
end
send_data(p.to_stream.read,
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
filename: "sample.xlsx")
end
end
これでリンクをクリックで、Excel ファイルを出力するプログラムができました。
超簡易サンプル
背景色を変更し、フォントを太字にしてみただけの超しょぼいサンプルです。
def generate_xlsx
Axlsx::Package.new do |p|
p.workbook.add_worksheet(name: @user.name) do |sheet|
styles = p.workbook.styles
title = styles.add_style(bg_color: 'c0c0c0', b: true)
header = styles.add_style(bg_color: 'e0e0e0', b: true)
sheet.add_row ["ユーザ情報"], style: title
sheet.add_row %w(名前 年齢), style: header
sheet.add_row [user.name, user.age]
end
send_data(p.to_stream.read,
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
filename: "#{user.name}.xlsx")
end
end
Excel のレイアウト調整などに関しては、こちらを参考にするのが良いかと思います。
落とし穴
Package#to_stream
を呼び出した時に、undefined method \`reopen' for "streamed":String
ってエラーが出て爆死しました。
原因
エラーメッセージそのままで検索をかけたところ、undefined method `reopen' for "streamed":String という、axlsx_rails
という Gem の Issue がヒットしました。
結論だけまとめると、
- Axlsx 2.0.0 は Rubyzip 1.0.0 以上では動かない。Rubyzip 0.9.9を使う必要がある。
- Axlsx 2.0.1 は RubyZip 1.1.1 以上では動かない。RubyZip 1.1.0 以下を使う必要がある。
とのことです。
Rubyzip という Gem も内部では使っていて、こいつのバージョンが想定外のものになっているのが原因のようです。
対応策
Gemfile で使用する Rubyzip のバージョンを指定してやれば大丈夫です。特に何かを指定していない場合、最新バージョンの2.0.1
が入っていると思うので、Rubyzip を以下の通り指定します。
gem 'rubyzip', '= 1.1.0'
その後、bundle update rubyzip
を実行で無事解決しました。
Axlsx-Rails
Axlsx-Rails の説明をざっとみたところ、こっちを使ったほうが Rails っぽく綺麗に実装できそうでしたので、利用してみました。
概要
Rails ではコントローラーがリクエストを受け取り、それに応じたビューを表示します。例えば、以下のようなコントローラーがあった場合、html
フォーマットでリクエストを受け取れば、app/views/users/show.html.erb
が表示されます。
def show
respond_to do |format|
format.html
format.xlsx
end
end
それと同じように、xlsx
フォーマットで受け取ったら、それに対応したビューを表示しちゃおうぜってのが、Axlsx-Rails です。たぶん。上記の例の場合、xlsx
のフォーマットでリクエストが来た場合、app/views/users/show.xlsx.axlsx
を表示してくれます。
導入
Gemfileに1行追加で終了です。
gem 'axlsx_rails'
実装
基本的には、先ほどコントローラー内で実装したAxlsx::Package
に対する処理をapp/views/users/show.xlsx.axlsx
に移管してあげれば終了です。
注意点としては以下の3点かと思います。
- ファイル名の指定はコントローラーで行う
-
Axlsx::Package.new
は不要でxlsx_package
を使用する -
send_data
が不要になる
具体的なソースは以下のような感じです。
def show
@user = User.find(params[:id])
respond_to do |format|
format.html
format.xlsx do
response.headers['Content-Disposition'] = "attachment; filename='#{@user.name}.xlsx'"
end
end
end
wb = xlsx_package.workbook
wb.add_worksheet(name: @user.name) do |sheet|
sheet.add_row ["First Column", "Second", "Third"]
sheet.add_row [1, 2, 3]
end
さきほどよりも Rails っぽい実装になりました。
所感
落とし穴にハマった時は絶望したが、とても簡単に Excel ファイルを生成できた。Excel のデザインやらにこだわりだすと大変そうだが、簡単なものでいいならサクッと実装できそう。
Axlsx の最終更新日、2013/09/12(September.12.13) で止まってるけど、大丈夫なんだろうか。