非ActiveRecordの検証エラーメッセージをローカライズする

ActiveRecordを継承しないクラスに対し、バリデーションとそのエラーメッセージをローカライズする方法。


(1)バリデーション機能を持つクラスの実装
ポイントは冒頭のmixin。これによりActiveRecordのような振る舞いが可能となり、validatesメソッドが使えるようになる。
また、後述のform_forヘルパーにこのクラスのインスタンスを渡すとpersisted?メソッドが呼び出されるので、persisted?の定義も必要。

class Import::CSV
  include ActiveModel::Validations
  include ActiveModel::Conversion
  
  attr_accessor :file
  
  validates :file, :presence => true
  def persisted?; false; end
end

(2)ビューの実装
form_forの引数@csvは(1)のインスタンス。ポイントは特になく、いたって普通にActiveRecordのフォームを実装する感じで実装する。

<%= form_for(@csv, :url => import_csv_path, :multipart => true) do |f| %>
  <% if @csv.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@csv.errors.count, "error") %> prohibited this csv from being saved:</h2>

      <ul>
      <% @csv.errors.full_messages.each do |msg| %>
        <li><%= msg %></li>
      <% end %>
      </ul>
    </div>
  <% end %>
  
  <div class="field">
    <%= f.label :file %>
    <%= f.file_field :file %>
  </div>
    
  <div class="actions">
    <%= f.submit 'confirm' %>
  </div>
<% end %>

(3)ロケールファイルの実装
ポイントはActiveRecordではなく、ActiveModelをmixinしたクラスなのでキーはactivemodel配下に設定すること。それ以外はActiveRecordと同様。
尚、本題から逸れるけどネームスペースを持つクラスは以下のようにスラッシュ(Import::CSV => import/csv)で表す。

ja:
  activemodel:
    attributes:
      import/csv:
        file: CSVファイル
    errors:
      messages:
        blank: "を入力してください。"

以上