はじめに
今回のコードは、以下の環境で動作確認しています。
- Rails 5.2.0
- rubyXL 3.3.29
1. rubyXL Gem をプロジェクトに追加
Ruby で エクセルファイルを扱うのに rubyXL を利用します。
Gemfile
gem 'rubyXL'
2. 次に魔法のヘルパーを追加します
中身はとりあえずこれだけ❤
app/helpers/excel_template_helper.rb
module ExcelTemplateHelper
def excel_render(template_file)
RubyXL::Parser.parse(template_file).tap do |workbook|
workbook.worksheets.each do |worksheet|
@worksheet = worksheet
@worksheet.each_with_index do |row, row_num|
row&.cells&.each do |cell|
next if cell.nil?
cell_render(cell)
end
row_height_auto(row_num)
end
end
end
end
private
def content_eval(content)
view_context.instance_eval(%("#{content}")).gsub(/\R/, "\n") # エクセルの改行は LF
end
def cell_render(cell)
cell.change_contents(content_eval(cell.value))
cell.change_text_wrap(true) if (cell&.value&.lines("\n")&.count) > 1
rescue
cell.change_contents('error!')
cell.change_font_color('FF0000')
cell.change_fill('FFFF00')
end
def row_height_auto(row_num)
max_lines = @worksheet[row_num]&.cells&.map { |cell| cell&.value&.lines("\n")&.count || 0 }&.max
origin_height = [@worksheet.get_row_height(row_num), 20].max # 最小値が RubyXL::Row::DEFAULT_HEIGHT (= 13) では合わなかったので手動調整
@worksheet.change_row_height(row_num, origin_height * max_lines) if max_lines&.positive?
end
end
ポイントは content_eval
メソッド内の view_context.instance_eval()
です。これのおかげで、通常の view と同じように、エクセルファイル内でインスタンス変数を扱えるようになります。
3. 呼び出す controller 側のサンプルを書きます
こんな感じ。
app/controllers/excel_controller.rb
class ExcelController < ApplicationController
include ExcelTemplateHelper
def sample1
create_data
send_data(
excel_render('lib/excel_templates/sample1.xlsx').stream.string,
type: 'application/vnd.ms-excel',
filename: "#{@datetime.strftime("%Y%m%d_%H%M%S")}_sample1(#{@name}).xlsx"
)
end
private
def create_data
@datetime = Time.now
@name = 'm-kubo'
@multi_lines_text = "Remember kids,\nthe magic is with in you.\nI'm princess m-kubo."
end
end
4. テンプレートになるエクセルを用意しよう
例えば、 lib/excel_templates/sample1.xlsx
にこんな感じのエクセルファイルを配置します。
5. 出力してみよう
routing を追加して、
config/routes.rb
get 'sample1', to: 'excel#sample1'
/sample1
にアクセスすると、レンダリングされたエクセルファイルがダウンロードされます 🧙♀️
帳票の種類が多かったり、帳票レイアウトの変更が多い案件の場合、エクセルファイルで直接レイアウトなどを修正できるため、開発も運用もすごく楽になると思います
6. おまけ
excel_template_helper
の cell_render
メソッドを以下のように変更して、
app/helpers/excel_template_helper.rb
def spread_render(cell, array_name)
original_style_index = cell.style_index
original_row_height = @worksheet.get_row_height(cell.row)
view_context.instance_eval(array_name).each_with_index do |_array, index|
target_row = cell.row + index
target_column = cell.column
target_value = cell.value.gsub(array_name) { "#{array_name}[#{index}]" } # 配列変数部に添え字を付与する
@worksheet.add_cell(target_row, target_column, content_eval(target_value))
@worksheet[target_row][target_column].style_index = original_style_index
@worksheet.change_row_height(target_row, original_row_height)
end
end
def normal_render(cell)
cell.change_contents(content_eval(cell.value))
cell.change_text_wrap(true) if (cell&.value&.lines("\n")&.count) > 1
end
def cell_render(cell)
if /(?<array_name>@[\w-]+)\[:[\w-]*\]/ =~ cell.value
spread_render(cell, array_name) # 配列変数を展開しながらのレンダリング
else
normal_render(cell) # 通常のレンダリング
end
rescue StandardError
cell.change_contents('error!')
cell.change_font_color('FF0000')
cell.change_fill('FFFF00')
end
こんなテンプレートを用意すると、
配列変数を自動展開させて、任意の一覧表を出力させることもできます。
工夫次第ですね❤
終わりに
以上、どこかの案件で書いたコードの紹介でした。
誰かの役に立ちますように!