Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

Rails: Form ObjectとVirtusを使って属性をサニタイズする(翻訳)

概要

原著者の許諾を得て翻訳・公開いたします。

Form Objectパターンについては「肥大化したActiveRecordモデルをリファクタリングする7つの方法(翻訳)」もご覧ください。

Rails: Form ObjectとVirtusを使って属性をサニタイズする(翻訳)

私たちはDrivyのコードベースで、Form ObjectのビルドにVirtus gemを使っています。これによって以下のメリットを得られました。

  • コントローラやビューにビジネスロジックを含めないようにする
  • 永続しない属性を取り扱える
  • 特定のバリデーションを、モデルに直接追加せずに追加できる
  • カスタムのデータバリデーションをフォームに直接表示できる
  • includeすることで機能をActiveModel::Modelから使える

ユーザー入力のサニタイズ、データの整形、スペースの除去といった作業が必要になったときに、Virtusを使って行える便利な方法をご紹介します。

#coerceを使う

文字列として記録されているVAT番号からスペースをすべて除去したいとします。シンプルなユースケースですが、このコンセプトはもっと複雑な状況にも適用できます。

まず、サニタイズ対象となる属性のカスタム属性オブジェクトを定義する必要があります。そのオブジェクトで#coerceメソッドを使うためにVirtus::Attributeの継承も必要です。次に、実行したい整形処理をメソッドに定義します。

class SanitizedVatNumber < Virtus::Attribute
  def coerce(value)
    value.respond_to?(:to_s) ? value.to_s.gsub(/\s+/, '') : value
  end
end

次はVirtusのForm Objectでvat_number属性(これが更新対象です)をSanitizedVatNumberとして指定します。

class CompanyForm
  attribute :vat_number, SanitizedVatNumber

  def initialize(...)

  end
end

以上でおしまいです。フォームが送信されるとvat_numberがサニタイズされます。

RSpecでテストする

カスタムのVirtus属性に基本的なテストを追加するのも簡単です。RSpecを使う場合は次のようにします。

describe SanitizedVatNumber do
  let(:object) { described_class.build(described_class) }
  subject { object.coerce(value) }


  context 'vat_numberがnilの場合' do
    let(:value)  { nil }

    it { is_expected.to eq('') }
  end

  context 'vat_numberがスペースを含む場合' do
    let(:value)  { 'EN XX 999 999 999' }

    it { is_expected.to eq('ENXX999999999')}
  end
end

まとめ

Form Objectの責務の増加(属性をフォーム内で直接サニタイズするリスクにつながる)を避けることができます。また、Virtusのカスタム#coerceは複数のフォームで再利用でき、単体テストも非常に行いやすくなります。

本記事が気に入った方はぜひDrivyエンジニアリングチームの募集要項をご覧ください。

関連記事

肥大化したActiveRecordモデルをリファクタリングする7つの方法(翻訳)

Railsで重要なパターンpart 1: Service Object(翻訳)

Railsで重要なパターンpart 2: Query Object(翻訳)


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。