目的
テキスト型のカラムを持つモデルにおいて、入力フォーム(テキストエリア)に特定のテンプレートを表示したい。
例えば以下のようなもの。
【本日の業務内容】 ・ ・ 【所感・課題】 【明日の予定】 ・
- 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 %>
感想
テンプレート定義が多いと、モデルが結構汚れるので悩ましいところ。
もしテンプレート自体をデータベースで管理したいとなった場合は、方針自体を見直したほうがよさそう。