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を使ったのはモデル側で空のオブジェクトを生成し外部であるコントローラーでも活用でき、クラス内で定義したバリデーショにも生かすことができるからです。
最後に
現場では安易にレコードを保存させることはもちろん、保守性を意識し隙間を埋めるようなバリデーションをひかなくてはならないことを学びました。 今後も学んだことを少しづつアウトプットできたらと思います。