normalizes を使ってフリーテキストのテンプレート機能を作る #rails

目的

テキスト型のカラムを持つモデルにおいて、入力フォーム(テキストエリア)に特定のテンプレートを表示したい。

例えば以下のようなもの。

【本日の業務内容】
・
・

【所感・課題】


【明日の予定】
・
  • placeholder とは異なり、テンプレートに対してユーザーが修正するような形で使いたい
  • 厳密なデータの構造化(項目を分ける)が必要ない、あるいは頻繁に項目が変わるようなもの

実現に当たっての課題

  • ユーザーがテンプレートから値を書き換えていない場合は、DBに値を保存させたくない(nil として扱いたい)
  • 複数のモデルで似たようなことをやりたいので、汎用的な仕組みにしたい

方針

  • normalizes を使って、テンプレートの処理を正規化として任せる
  • ActiveSupport::Concern でモジュール化し、各モデルで使い回せるようにする

実装例

# app/models/concerns/template_normalizable.rb

module TemplateNormalizable
  extend ActiveSupport::Concern

  class_methods do
    def normalizes_template_for(attribute, template:, allow_template_value: false)
      normalizes attribute, with: lambda { |value|
        # 比較用に改行コードの統一
        normalized_template = template.to_s.gsub("\r\n", "\n").strip
        normalized_value = value.to_s.gsub("\r\n", "\n").strip

        return nil if normalized_value.blank?
        return nil if !allow_template_value && normalized_value == normalized_template

        value
      }

      # ViewやAPIレスポンスなどで使うためのメソッドを動的生成
      define_method("#{attribute}_or_template") do
        public_send(attribute).presence || template
      end
    end
  end
end

ブラウザのテキストエリアから送信される改行コードは \r\n (CRLF) になることが多いため、Ruby側で定義したテンプレートの改行コード \n (LF) と単純に文字列比較すると一致しないケースがある。

そのため、比較前に改行コードを統一し、差異を吸収するようにしてある。

また、テンプレート文字列を必要に応じてそのまま保存できるような allow_template_value オプションも追加した。

使用例

モデル側

# app/models/daily_report.rb
class DailyReport < ApplicationRecord
  # モジュールを include
  include TemplateNormalizable

  # 複数行にわたるテンプレートはヒアドキュメントで定義したほうが可読性がいいかも
  REPORT_TEMPLATE = <<~TEXT.strip
    【本日の業務内容】
    ・
    ・

    【所感・課題】

    【明日の予定】
    ・
  TEXT
  # 対象のカラムとテンプレートを渡して宣言する
  normalizes_template_for :content, template: REPORT_TEMPLATE
  
  # テンプレートの値そのまま保存を許可する場合は allow_template_value を使う
  normalizes_template_for :blocker, template: '特になし', allow_template_value: true
end

View側

<%= #{カラム名}_or_template形式でメソッドが用意される %>
<%= f.text_area :content, value: daily_report.content_or_template %>

感想

テンプレート定義が多いと、モデルが結構汚れるので悩ましいところ。

もしテンプレート自体をデータベースで管理したいとなった場合は、方針自体を見直したほうがよさそう。