attr_accessorでバリデーション

はじめに

今年の10月から晴れて駆け出しエンジニアになった坂本です。 業務の中での学びをアウトプットしていこうと思います。 あくまで私のアウトプットが目的なので至らない点があることを理解した上でご一読ください。

内容

今回は保存するmodelにバリデーションを設ける際にattr_accessorを経由させることで、保存するレコードの数を制御する。というテーマについて書いていこうと思います。

attr_accessorとは

下記のようなモデルクラスが定義されていたとします。

userモデル

class user

 def initialize(name, hobby)

      @name = name
      @hobby = hobby

   end
 
   sakamoto = User.new( '佐藤太郎' , 24 )
   sakamoto.name 

   
end

通常、クラスの外部からインスタンス変数にアクセスしようとすると「メソッドが定義されていません。」と警告を受けます。 上記のコードもクラスで定義したメソッドに対してアクセスすることはできないので外部から再定義や変更を加えることはできません。

userモデル

class user

 attr_accessor :name, :hobby

 def initialize(name, hobby)

      @name = name
      @hobby = hobby

   end
 
   sakamoto = User.new( '佐藤太郎' , 24 )
   p sakamoto.name 

   
end

しかし、同じクラスでもattr_accessorをクラスの中で定義してあげると先程のコードでも外部からクラス内部の情報に対してアクションを起こすことが可能になります。

つまり、クラス外部からクラス内部で定義されたメソッドに対して参照や変更を加えることができるようになるのがattr_accessorです。

バリデーションに生かす

上記の性質をもとにバリデーションに応用していこうと思います。 初めに書いたように今回は「1対多の親クラスから子クラスを制御するバリデーション」を組んでみようと思います。 下記のようなリレーションが組まれたモデルがあったとします。

userモデル

class User < ApplicationRecord

    has_many :hobbies, dependent: :destroy

end

user_hobbyモデル

class Hobby < ApplicationRecord

    belongs_to :user

end

userモデルに対してhobbyモデルが1対多の関係が組まれています。 ここまではよくあると思うのですが、ここから「userに対してhobbyは3つで抑えてね」と言われるとします。 この時に、

user_hobbyモデル

class Hobby < ApplicationRecord

     belongs_to :user

     validate :check_number_of_hobby

     def check_number_of_employees
         if hobby.count > 3
         errors.add(:user, "3つ以上は入力できません。")
     end

end

上記のように記述する方もいるのではないでしょうか? 私もこう書いてしまいました。 お気づきの方もいるでしょう。これだと一つのuser_hobbyテーブルのhobbyカラムに3つ以上レコードを入れることはできませんと言ってしまっているのです。 さらに、これだとuserクラスで定義されるはずのuserを参照することができないのでエラーになります。

ここでattr_accessor

今回の肝は「userモデルに対して3つまでしかhobbyテーブルは作れない」という点です。 つまり「hobbyテーブルでバリデーションをひいても意味がない」という解釈にたどりつけるか。という点が重要です。 保存されてくるレコードに対して1回1回バリデーションを当てはめていては、レコードの数を制御することはできません。 なぜなら1回1回保存されるのは1つのレコードのみなのでバリデーションが効かないからです。

なので今回の場合、「一度に保存されるレコードを一つのオブジェクトの中に代入し、バリデーションをかける」という作業をする必要があります。 以下のように、userモデルにattr_accessorで空のオブジェクトを生成してあげます。 さらにそのオブジェクトに対してバリデーションをひいてあげます。

userモデル

class User < ApplicationRecord

   attr_accessor :hobbies_count

    has_many :hobbies, dependent: :destroy

    validate :check_number_of_hobby

     def check_number_of_employees
          if hobbies_count.count > 3
          errors.add(:hobbies_count, "3つ以上は入力できません。")
     end

end

現状としては何も入っていないオブジェクトに対してバリデーションをかけている状態です。 このcheck_number_of_hobbyオブジェクトにコントローラーで保存するはずのレコードを配列で代入してあげることによって保存する前にレコードの制御を行うことができます。 attr_accessorを使ったのはモデル側で空のオブジェクトを生成し外部であるコントローラーでも活用でき、クラス内で定義したバリデーショにも生かすことができるからです。

最後に

現場では安易にレコードを保存させることはもちろん、保守性を意識し隙間を埋めるようなバリデーションをひかなくてはならないことを学びました。 今後も学んだことを少しづつアウトプットできたらと思います。