💎

ActiveModel で 関連モデルのバリデーションを実行する(validates_associated を実装)

2021/07/06に公開

Ruby on Railsで開発をしているときに、DBに紐付かないデータを扱う際にActiveModelを使用することが多いと思います。
関連するモデルのバリデーションをしたい時には、ActiveRecordの場合はvalidates_associatedを使えば済みますが、ActiveModelにはこのメソッドはありません。

今回はActiveModelでvalidates_assosicatedと同等の処理(関連モデルのバリデーション)を実装でカバーする方法を紹介します。

validates_associatedとは

そもそもvalidates_associatedとは、ActiveRecordで関連するモデルのvalidationを実行したいときに使用するメソッドです。
https://railsguides.jp/active_record_validations.html#validates-associated

ActiveModelで使用したいときは関連するモデルをattributeとして持ち、操作するような場合が挙げられます。

今回は以下のような例で考えてみます。

class User < ApplicationRecord
end

class UserInfo
  include AcitveModel::Model
  attr_accessor :user
  delegate :name, :name=, to: :user, allow_nil: true
  
  def save
    return false if invalid?
    user.save!
    true
  end
end

ここで、UserInfo#saveを実行した際に、userのvalidationを実行するべきです。

valid?をオーバーライドする

上記の例でuserのvalidationを実行するなら、簡単に処理を追加するならUserInfo#saveif invalid?|| user.invalid? を追加すれば良さそうに思えます。

しかし、UserInfo#valid?では同等の結果が得られず、クラスの実装としては不十分と言えます。
そこで、UserInfo#valid?をオーバーライドすることでControllerからActiveRecordと同様に使えるようにします。

class User::Info
  include ActiveModel::Validations

~

  def valid?(context = nil)
    if super && user.valid?
      true
    else
      error.merge!(user.errors)
      false
    end
  end

こうすることで ControllerからはActiveModelかどうかを意識せずに、valid?saveを使うことができ、コードをシンプルに保つことができます。

まとめ

ActiveModelはとても便利なモジュールですが、時々ActiveRecordとの違いで困ることがあります。今回やってみて、validates_associatedの代わりは実装でカバーすることが出来ると思いました。個人的にはRails標準のActiveModelをなるべく使いたいと考えているので、この方法でよりActiveModelを使えるケースが増えればいいなと思います。

Discussion