Rails

RailsでAxlsxを使ってxlsxを生成

More than 1 year has passed since last update.


概要

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.

Axlsx Synopsis


シノプシスって英単語、英語力低すぎなので初めて見たんですが、要約とか梗概って意味らしいですね。知らなかった。


Office Open XML

Office Open XML Spreadsheet ってなんやねん。俺は Excel(.xlsx) ファイルが欲しいねん。って方向けの補足です。


Office Open XML (OpenXML、OOXML) とは、XMLをベースとしたオフィススイート用のファイルフォーマットである。

Wikipedia: Office Open XML


Office Open XML は Microsoft Office 2007 以降で採用されているファイルフォーマットで、いわゆる.docxとか.xlsxとかのことです。つまり、Axlsx が生成してくれる Office Open XML Spreadsheetとは、.xlsxファイルのことです。

詳細は Wikipedia 大先生が語ってくれてます。


導入


Gemfile

Gemfile に以下を追記し、bundle installを実行!


Gemfile

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に登録しておきます。


config/initializers/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 ファイルを出力するというプログラムを実装してみました。

呼び出し元の画面には以下のようなリンクを記述しました。


show.html.erb

<%= link_to "Excel出力", user_path(@user, format: :xlsx) %>


このとき生成されるURLは/user/id.xlsxとなるため、前述の通りrespond_toを用いて処理できます。


users_controller.rb

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 がヒットしました。

結論だけまとめると、


  1. Axlsx 2.0.0 は Rubyzip 1.0.0 以上では動かない。Rubyzip 0.9.9を使う必要がある。

  2. Axlsx 2.0.1 は RubyZip 1.1.1 以上では動かない。RubyZip 1.1.0 以下を使う必要がある。

とのことです。

Rubyzip という Gem も内部では使っていて、こいつのバージョンが想定外のものになっているのが原因のようです。


対応策

Gemfile で使用する Rubyzip のバージョンを指定してやれば大丈夫です。特に何かを指定していない場合、最新バージョンの2.0.1が入っていると思うので、Rubyzip を以下の通り指定します。


Gemfile

gem 'rubyzip', '= 1.1.0'


その後、bundle update rubyzipを実行で無事解決しました。


Axlsx-Rails

Axlsx-Rails の説明をざっとみたところ、こっちを使ったほうが Rails っぽく綺麗に実装できそうでしたので、利用してみました。


概要

Rails ではコントローラーがリクエストを受け取り、それに応じたビューを表示します。例えば、以下のようなコントローラーがあった場合、htmlフォーマットでリクエストを受け取れば、app/views/users/show.html.erbが表示されます。


users_contoroller.rb

def show

respond_to do |format|
format.html
format.xlsx
end
end

それと同じように、xlsxフォーマットで受け取ったら、それに対応したビューを表示しちゃおうぜってのが、Axlsx-Rails です。たぶん。上記の例の場合、xlsxのフォーマットでリクエストが来た場合、app/views/users/show.xlsx.axlsxを表示してくれます。


導入

Gemfileに1行追加で終了です。


Gemfile

gem 'axlsx_rails'



実装

基本的には、先ほどコントローラー内で実装したAxlsx::Packageに対する処理をapp/views/users/show.xlsx.axlsxに移管してあげれば終了です。

注意点としては以下の3点かと思います。


  1. ファイル名の指定はコントローラーで行う


  2. Axlsx::Package.newは不要でxlsx_packageを使用する


  3. send_dataが不要になる

具体的なソースは以下のような感じです。


users_controller.rb

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



show.xlsx.axlsx

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) で止まってるけど、大丈夫なんだろうか。


参考文献