こんにちは!kossyです!
さて、今回はRailsでvalidationを行う際のwith_optionsの使い方について、ブログに残してみたいと思います!
Documentを読んで使い方を学ぶ
まずはドキュメントを読んでみましょう。
5.3 条件付きバリデーションをグループ化する
1つの条件を複数のバリデーションで共用できると便利なことがあります。これはwith_optionsを使うことで簡単に実現できます。
class User < ApplicationRecord with_options if: :is_admin? do |admin| admin.validates :password, length: { minimum: 10 } admin.validates :email, presence: true end end
with_optionsブロックの内側にあるすべてのバリデーションには、if: :is_admin?という条件が渡されます。
Userがadminの場合にのみ適用したいvalidationを行う例が示されています。
次はこのドキュメントを読みます。
with_options(options, &block) public
An elegant way to factor duplication out of options passed to a series of method calls. Each method called in the block, with the block variable as the receiver, will have its options merged with the default options hash provided. Each method called on the block variable must take an options hash as its final argument.
一連のメソッド呼び出しに渡されるオプションから重複を除外するための洗練された方法。
ブロック変数をレシーバーとしてブロック内で呼び出される各メソッドのオプションは、提供されているデフォルトのオプションハッシュとマージされます。ブロック変数で呼び出される各メソッドは、最後の引数としてオプションハッシュを取る必要があります。
with_optionsの使い方の例として、dependent: :destroyをまとめて定義する方法が記載されていました。
class Account < ActiveRecord::Base with_options dependent: :destroy do |assoc| assoc.has_many :customers assoc.has_many :products assoc.has_many :invoices assoc.has_many :expenses end end
上記のようにwith_optionsの引数にdependent: :destroy とブロックを渡すことで、
まとめて定義することができるようです。
以下のように記述した場合でも上記の例と同じ効果を得られるみたいです。
class Account < ActiveRecord::Base with_options dependent: :destroy do has_many :customers has_many :products has_many :invoices has_many :expenses end end
また、with_optionsにifとブロックを渡して定義する記述方法もあります。
class Post < ActiveRecord::Base with_options if: :persisted?, length: { minimum: 50 } do validates :content, if: -> { content.present? } end end
この場合は、インスタンスがDBに保存済みかどうかを判定して、保存済みだった場合に適用されるバリデーションになっています。
with_optionsを使う場合の注意点
ドキュメントにこのようなNOTEがありました。
NOTE: You cannot call class methods implicitly inside of with_options. You can access these methods using the class name instead:
class Phone < ActiveRecord::Base enum phone_number_type: [home: 0, office: 1, mobile: 2] with_options presence: true do validates :phone_number_type, inclusion: { in: Phone.phone_number_types.keys } end end
なるほど、with_options内だとselfを参照できないから、明示的にPhone.phone_number_types.keysを呼び出さないといけないと注意を促しているんですね。
知らないとハマりそうです。
binding.pryしながら実行してみた
試しに以下のように、インスタンス自身を参照できるのか試してみます。
with_options presence: true do |_self| binding.pry validates :condition, presence: true end
pry $ _self
=> #<ActiveSupport::OptionMerger:0x2b00cbb1492c>
インスタンスそのものが返ってくると予想していましたが、
ActiveSupport::OptionMergerクラスが返ってきました。
apidock.com
joker1007.hatenablog.com
次は_selfを渡さずに試してみます。
with_options presence: true do binding.pry validates :condition, presence: true end
pry $ self => #<ActiveSupport::OptionMerger:0x2b00cbb1492c>
うーん、やはりwith_optionsメソッドのブロック内だと、selfはActiveSupport::OptionMergerになるみたいですね。
勉強になりました。
大いに参考にさせていただいた記事
この場を借りて御礼を申し上げます。
Active Record バリデーション - Railsガイド
with_options (Object) - APIdock
Railsで特定の条件下で走るバリデーションを作りたい - Qiita