0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【Rails】バリデーションのカスタムコンテキストon:を複数同時に指定したい場合の方法と仕組み

Posted at

Railsでは、on:を使うことで、バリデーション実行タイミングを指定できたり、カスタムコンテキストとして実行するかしないかを調整できます。

on:で定義したカスタムコンテキストをvalid?等の引数に渡すことでバリデーションのチェックができます。
Railsガイドではvalid?(:xxx)の使い方が載っていますが、一度にカスタムコンテキストを複数指定する方法を書いておきたいと思います。

結論

結論としては、配列で指定してあげれば良いです。

controller
def create
  book.valid?([:xxx, :yyy])
  ...
end
book.rb
with_options on: :xxx do
  validates :title, presence: true
  ...
end

with_options on: :yyy do
  validates :content, presence: true
  ...
end

こうすることで、:xxx:yyyどちらのバリデーションチェックもかけることができます。

疑問に思ったこと

配列で指定できるのは良かったのですが、
「なんで配列でも機能するし、シンボルを1つ渡しただけでも機能するんだろう?」と思いました。
なので、ソースコードを除いてみることにしました。

Rails内の仕組み

初級者のため読むのにかなり苦労しました。ざっくりしかわかっていない点はご容赦ください、、、

1.まず、valid?メソッドの中でself.validation_contextに、引数をそのまま渡しています。(上記の例だと[:xxx, :yyy]

gems/activemodel-6.0.6/lib/active_model/validations.rb 333行目あたり
def valid?(context = nil)
  current_context, self.validation_context = validation_context, context
  errors.clear
  run_validations!
ensure
...

2.そして、以下ところで定義されているオプション(options[:on])と先ほど代入したvalidation_contextで一致するものがあるかどうかを調べていました。

gems/activemodel-6.0.6/lib/active_model/validations.rb 152行目あたり

def validate(*args, &block)
  ...
      options[:if].unshift ->(o) {
        !(options[:on] & Array(o.validation_context)).empty? # ①
      }
    end
  ...
end

つまり、、、
Array(o.validation_context)として配列の形にしてから処理をしているので、[:xxx, :yyy]のような配列であっても、:xxxのように単にシンボルであっても処理ができるということがわかって、腑に落ちました。

方法としてはシンプルですが、この方法は自分のコーディングにも活かせそうだなと思いました。

ちなみに

ちなみに上記のコードを読んでいる中ですごくRubyの勉強になった点がありました。

gems/activesupport-6.0.6/lib/active_support/callbacks.rb 425行目あたり
def make_lambda
  lambda do |target, value, &block|
    target, block, method, *arguments = expand(target, value, block)
    target.send(method, *arguments, &block)
       # target => #<Book id: nil, title: "", content: "", created_at: nil, updated_at: nil>
       # method => :instance_exec
       # arguments => [#<Book id: nil, title: "", content: "", created_at: nil, updated_at: nil>]
       # block => #<Proc:.../lib/active_model/validations.rb:166 (lambda)> ⇦上記コード①のオブジェクト
  end
end

ここのtarget.send(method, *arguments, &block)でおこなっている事についてです。
各変数に入っている値がコメントアウトで示した場合、ここで行われているのは、
sendメソッドでinstance_execメソッドが、引数をargumentsを展開したBookオブジェクトとして呼ばれ、①のブロックのオブジェクトが実行される、というものでした。

つまり、ここでは①のブロックが、Bookオブジェクトを引数oとして実行されている、ということになりそうです。

最後に

今までは配列にすれば複数指定できる、OK!として終わってしまっていたところを、完全に理解できなかったとはいえ、ソースコードを追うところまでやってみたのは良かったかなと思います。

「ちなみに」で書いた部分については、sendメソッドの理解、instance_execというメソッドの存在と理解、lambdaの理解が足りていなかった私にとっては読み解くのが難しく、頭が痛くなりそうでしたが、これを機に身をもって理解できて良かったと思いました。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?