Help us understand the problem. What is going on with this article?

RailsでAxlsxを使ってxlsxを生成

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

参考文献

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away