LoginSignup
29

More than 5 years have passed since last update.

Railsで既存のエクセルファイルをテンプレートにできる魔法のヘルパー

Last updated at Posted at 2018-06-04

はじめに

今回のコードは、以下の環境で動作確認しています。

  • 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 にこんな感じのエクセルファイルを配置します。

image.png

5. 出力してみよう

routing を追加して、

config/routes.rb
get 'sample1', to: 'excel#sample1'

/sample1 にアクセスすると、レンダリングされたエクセルファイルがダウンロードされます 🧙‍♀️

image.png

帳票の種類が多かったり、帳票レイアウトの変更が多い案件の場合、エクセルファイルで直接レイアウトなどを修正できるため、開発も運用もすごく楽になると思います :smile:

6. おまけ

excel_template_helpercell_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

こんなテンプレートを用意すると、

image.png

配列変数を自動展開させて、任意の一覧表を出力させることもできます。

image.png

工夫次第ですね❤

終わりに

以上、どこかの案件で書いたコードの紹介でした。

誰かの役に立ちますように!

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
29