マイペースなRailsおじさん

Ruby、Ruby on Rails、オブジェクト指向設計を主なテーマとして扱います。だんだん大きくなっていくRuby on Rails製プロダクトのメンテナンス性を損なわない方法を考えたり考えなかったりしている人のブログです。

RubyXLを使ってExcelを編集してクライアントに返す

railsでエクセルを編集してクライアントに返そうとしたときのやり方を残しておきます。

作るもの

  1. クライアントがエクセルファイルをアップロードする
  2. サーバが受け取ったエクセルに何かしらの変更を加える
  3. クライアントが変更されたファイルをダウンロードする

といった動きになる機能をRailsで作ります。 出来上がったソースコードはこちら。

GitHub - ytnk531/excel-example: rails excel example

Excelの編集

RubyXLというgemを使います。

github.com

他にもエクセルを開けるgemはあるのですが、

  • Roo: 読み込みのみ
  • AXSLX: 新規作成のみ

という感じで用途が絞られています。 今回は、編集ができるRubyXLを使います。

単体だと、以下のような感じで使えます。

require 'rubyXL'

# ファイルを読み込んでRubyXL::Workbookにデシリアライズ
workbook = RubyXL::Parser.parse("path/to/Excel/file.xlsx")
# ブック→シート→行→セルという構造になっている
worksheet1 = workbook[0]
row1 = worksheet1[0]
cell1 = row1[0]
# 書き込むときはWorksheetのメソッドが使える
worksheet1.add_cell 0, 0, 'changed'
# 保存
workbook.save

rails new

railsプロジェクトを作ります。 今回は横着してモデルを作らないので、ActiveRecordなどいらないモジュールを生成しないようオプションを付けます。

rails new -MOCJ

ルーティング

excelリソースへのルートを作ります。 加えて、ルートをexcelsにします。

下記とおりルーティングされます

  • GET / -> ExcelsController#show
  • GET /excels -> ExcelsController#show
  • POST /excels -> ExcelsController#create

GET /excelsでファイルを受け取るフォームを表示し、POST /excelsでサーバへのファイルアップロードと編集後ファイルの送信を行います。

Rails.application.routes.draw do
  # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
  root to: "excels#show"
  resource :excel, only: [:show, :create]
end

excel-example/routes.rb at master · ytnk531/excel-example · GitHub

コントローラ

showメソッドでは何もせず、ページを表示するだけです。 createにはエクセルファイルが送られて来るので、ファイルの編集とクライアントへの送信を行います。

class ExcelsController < ApplicationController
 def show
 end

 def create
   file = params[:file]
   # ファイルを開く
   workbook = RubyXL::Parser.parse file.path
   # 編集する
   workbook[0].add_cell 0, 0, 'changed'
   # 編集したファイルを送る
   send_data workbook.stream.string, type: file.content_type, filename: 'modified.xlsx'
 end
end

アップロードされたファイルはActionDispatch::Http::UploadedFileオブジェクトとして受け取ることができます。 tempfileに受け取ったファイルそのものが入っているので、RubyXLオブジェクトに変換します。

編集したファイルは、send_dataメソッドを使って送信します。

データとして渡すのは、Workbook#streamで取得できるバイナリのストリームを、Stream#stringで文字列にしたものです。 コンテントタイプを指定する必要がありますが、UploadedFileは受信したときのContent-Typeを記録しているので、同じものを指定しておけばいいでしょう。

excel-example/excels_controller.rb at master · ytnk531/excel-example · GitHub

ビュー

エクセルファイルをアップロードするためのビューを作ります。

<%= form_tag({action: :create}, multipart: true) do %>
  <div>
  <%= file_field_tag :file %>
  </div>
  <div>
  <%= submit_tag "Send" %>
  </div>
<% end %>

https://github.com/ytnk531/excel-example/blob/master/app/views/excels/show.html.erb

ファイル選択と送信ができます。 送信したファイルはExcelsController#createで処理します。

f:id:ytnk531:20190302133745p:plain

試してみる

このようなファイルを用意し、

f:id:ytnk531:20190302031633p:plain

送ってみます。

f:id:ytnk531:20190302140054p:plain

編集されたファイルのダウンロードが始まります。

f:id:ytnk531:20190302135833p:plain

開いてみます。期待通り編集されたエクセルをダウンロードできました。

f:id:ytnk531:20190302135554p:plain

まとめ

  • RubyXLでエクセルの編集ができる
  • アップロードされたファイルはUploadedFileオブジェクトに記録される
  • RubyXLで保存したワークブックは、保存しなくてもデータ送信できる

UploadedFileの存在を知らなかったので、うまくいくかわからなかったのですが、うまくrailsと一緒に使うことができました。