Railsのバリデーションの仕組みを知らなかったので調べてみました

こちらはLinc'well Advent Calendar 2019の14日目の記事となります。


タイトルの通りですが、これまでRailsを数カ月間使用してきて、

Modelで何十回もバリデーションを定義したり、
create!したらバリデーションに引っかかって`ActiveRecord::RecordInvalid`例外を起こしたり、
何十回も`valid?`メソッドを呼び出したりとしてきたのに、

そういえばvalidationの仕組みを何も知らないな。と思ったので、
バリデーション機能をコードを追いながら調べてみて、その過程をまとめてみました。

間違い等見つけていただけましたら、コメント欄でご指摘いただけたら嬉しいです！

また、文中のRailsのコードは`tag v6.0.0`のものです。

## 調べた中での一番の学び
いきなりですが、今回調べてみての一番の学びを先に述べておくと、下記の事柄でした。

- Railsのバリデーション機能は、コールバックを利用して実現されている。

## バリデーションと呼ばれる機能の構成
今回調べてみるに当たって、分けたほうが調べやすいと思ったので、
Railsのバリデーション機能を構成しているものを大きく以下の２つに分けてみました。

1. Modelに`validates :name, presence: true`のような文言を記述すると、バリデーションを定義できる。[^1]
2. 1で定義されたバリデーションに応じて、オブジェクトがデータベースに保存されるかが決まる。
(`save`や`create`をするときに、バリデーションに引っかかると保存されない。）

便宜上、以下では1を『バリデーション定義』、2を『バリデーション実行』という言葉で表現しています。

## 『バリデーション定義』のざっくり概要

そもそもバリデーションとしてModelに書いている`validates`は何なのかというところですが、
これはクラスメソッドにあたります。
(カスタムバリデーションの定義で使う`validate`や`validate_with`も同様です。）

私自身、正直`validates`がクラスメソッドであることは意識することはあまりなく、なんとなく使っていますが、
Modelの定義でよく目にする、`attr_accessor`や`has_many`も同様にクラスメソッドです。
（参考: [Railsによるメタプログラミング入門](https://shugo.net/tmp/metaprogramming.pdf) 前田 修吾さん）

また、このようなclass定義の直下で呼び出されるクラスメソッドを、書籍『メタプログラミングRuby 第2版』では『クラスマクロ』と紹介しています。

ということで、`validates`というクラスメソッドを探して、内容を確認すればどのようにバリデーションが定義されるかが分かるということになります。


## 『バリデーション実行』のざっくり概要


実際に、`create`のコードを確認してみると、以下の様に`save`が呼ばれ、その`save`の中で`perform_validations`というバリデーションらしきものが行われていることが分かります。

(ActiveRecordのどこに何が定義されているのかなどは、下記の書籍を参考にさせていただいて探しました。
参考: [ActiveRecord完全に理解した](http://tolarian-academy.net/tech-book-fest-7/) kinoppydさん）

``` active_record/persistence.rb
    def create(attributes = nil, &block)
      if attributes.is_a?(Array)
        attributes.collect { |attr| create(attr, &block) }
      else
        object = new(attributes, &block)
        object.save
        object
      end
    end
```


```active_record/validations.rb
    # The regular {ActiveRecord::Base#save}[rdoc-ref:Persistence#save] method is replaced
    # with this when the validations module is mixed in, which it is by default.
    def save(**options)
      perform_validations(options) ? super : false
    end

    ~省略~

    def perform_validations(options = {})
      options[:validate] == false || valid?(options[:context])
    end
```

そして、この`perform_validations`ではvalidateを実行しないというオプションがない場合に、`valid?`を呼びだしており、(Railsガイドでは『バリデーションをトリガする』と表現されていました。）
バリデーション実行の起点となるものは`valid?`であることが窺えます。

上記を踏まえると、超ざっくりですがバリデーション実行とは、`valid?`がtrueを返すか、falseを返すかによって、データベースにオブジェクトを保存するかどうかを決めることだと言い換えることができます。

順番が前後してしまいますが、先にバリデーション実行の詳細を追ってみた過程を記述していきます。

## `valid?`から始まる 『バリデーション実行』の処理の詳細を追ってみる
上で出てきた`active_record/validations.rb`で`valid?`を探して見てみると下記のようになっています。

`context ||= default_validation_context`は、この`valid?`の呼び出し元の処理がcreateとupdateのどちらによるものかという情報であるcontextを定義しているもので、

contextを引数に取る`super`を呼び出していることから、`valid?`をオーバーライドしていることが分かります。


``` active_record/validations.rb
    def valid?(context = nil)
      context ||= default_validation_context
      output = super(context)
      errors.empty? && output
    end
```

オーバーライドされている`valid?`を探してみると、`ActiveRecord::Validations`がincludeしている`ActiveModel::Validations`というモジュールに、`valid?`が定義されているのが見つかります。

```active_model/validations.rb
    def valid?(context = nil)
      current_context, self.validation_context = validation_context, context
      errors.clear
      run_validations!
    ensure
      self.validation_context = current_context
    end
```

（どうして２つのValidationsというモジュールが存在するのかに関して、書籍『メタプログラミングRuby 第2版』の『9.2.3 Validations モジュール』において言及されていました。
元々は`ActiveRecord::Validations`のみしか存在せず、後から機能が`ActiveModel::Validations`に分割された経緯や理由が書かれており面白かったです。）

そして、この`valid?`を見ると、明らかにバリデーションを実行していそうな`run_validations!`というメソッドの呼び出し箇所があります。

`run_validations!`は以下のようになっており、中身を確認してきます。
`_run_validate_callbacks`はひとまず置いて、
`errors.empty?`を見ると、インスタンス変数の`@errors`の中身の存在確認をしているだけのメソッドであることから、
`_run_validate_callbacks`がバリデーションっぽいことをして、`@errors`に値を入れるのだなと予測できました。


```active_model/validations.rb
    def run_validations!
      _run_validate_callbacks
      errors.empty?
    end
```

そこで、この`_run_validate_callbacks`を見に行こうと思ったのですが、
コードジャンプしても全文検索しても見つからなかったため、
動的に定義されるメソッドであると推測されました。

が、それらしきものを発見することができず、ググったところ下記の記事が見つかりました。
（参考: [Railsのvalidationの実行過程を調べる](https://qiita.com/wakasa51/items/b3d65eac5cdad7787083)[^2]）

上記の記事を引用させていただくと、この`_run_validate_callbacks`は、
> ざっくりまとめると、当該モデルに設定されたcallbacksの内、 :validateのキーに引っかかるcallbacksを実行していっていると言うことになります。

とのことでした。

この時点では、『:validateのキーに引っかかるcallbacks』というものが一体何なのか知らなかったのですが、
ここまでのバリデーション実行の流れをざっくりまとめると、下のようになりました。

1. オブジェクトを`save`する際に、`valid?`が呼ばれる。
2. `valid?`から始まる処理の中で、`_run_validate_callbacks`が実行されることで『:validateのキーに引っかかるcallbacks』というものが実行され、`@errors`に何かしらの値が入る（これはこの時点では予想ですが、カスタムバリデーションを定義する時に、`record.errors.add`のように`@errors`に値を入れることからも予想ができます。）
3. `@errors`の中身が空であるかを確認し、空であればtrueを、そうでなければfalseを返し、それによって`valid?`の戻り値が決まる。
4. `valid?`の戻り値がtrueなら、オブジェクトを保存する処理が続けられ、falseなら保存する処理が中止される。

（『:validateのキーに引っかかるcallbacks』が、Modelに定義されているコールバックの中で、`:validate`キーに対応する`ActiveSupport::Callbacks::CallbackChain`を指しているということが後で分かりました。)

## 『バリデーション定義』の処理の詳細を追ってみる

バリデーション実行の詳細がある程度分かったことから、
あとは、バリデーション定義の処理の中で、『:validateのキーに引っかかるcallbacks』が定義されているところを見つけることができれば、
バリデーションの機能の全体感を把握することができるので、そこをゴールとして進んでいきます。

`validates`がクラスの読み込み時に実行されるところから定義の処理は始まります。

処理の中身としては、受け取った引数を(`[:name, :presence: true]`みたいな)、
良い感じに加工し、最終的に`validates_with`を呼び出すものです。

```active_model/validations/validates.rb
    def validates(*attributes)
      defaults = attributes.extract_options!.dup
      validations = defaults.slice!(*_validates_default_keys)
  
      raise ArgumentError, "You need to supply at least one attribute" if attributes.empty?
      raise ArgumentError, "You need to supply at least one validation" if validations.empty?
  
      defaults[:attributes] = attributes
  
      validations.each do |key, options|
        key = "#{key.to_s.camelize}Validator"
  
        begin
          validator = key.include?("::") ? key.constantize : const_get(key)
        rescue NameError
          raise ArgumentError, "Unknown validator: '#{key}'"
        end
  
        next unless options
  
        validates_with(validator, defaults.merge(_parse_validates_options(options)))
      end
    end
```

軽く流れを追うと、ブロック引数の`key`には`:presence`や`:length`などが入り、`key = "#{key.to_s.camelize}Validator"`では、"PresenceValidator"といった文字列が作られます。

そして、その文字列から`validator = key.include?("::") ? key.constantize : const_get(key)`でクラス名の定数を取得し、`validates_with`に引数で渡しています。
(`activemodel/lib/active_model/validations`配下に、`LengthValidator`、`InclusionValidator`などのバリデーションの種類に対応したValidatorクラスがそれぞれ用意されています。）

最後の`validates_with`の呼び出しは、[カスタムバリデータ](https://railsguides.jp/active_record_validations.html#%E3%82%AB%E3%82%B9%E3%82%BF%E3%83%A0%E3%83%90%E3%83%AA%E3%83%87%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%82%92%E5%AE%9F%E8%A1%8C%E3%81%99%E3%82%8B)を使ったバリデーションの定義と同じ形になります。

そして、呼び出された`validates_with`が何をしているかというと、引数として渡されたValidatorクラス名を使って、インスタンスを作り、最後に`validate`メソッド呼び出しの引数に渡しています。

この`validate`はカスタムメソッドを使ったバリデーションを定義するときに使うものと同じです。
(`validate :expiration_date_cannot_be_in_the_past`みたいに使うやつです。）

```active_model/validations/with.rb
    def validates_with(*args, &block)
      options = args.extract_options!
      options[:class] = self

      args.each do |klass|
        validator = klass.new(options, &block)

        if validator.respond_to?(:attributes) && !validator.attributes.empty?
          validator.attributes.each do |attribute|
            _validators[attribute.to_sym] << validator
          end
        else
          _validators[nil] << validator
        end

        validate(validator, options)
      end
    end
  end
```
続けて、`validate`を見ていきます。

すると、`set_callback(:validate, *args, options, &block)`という、いかにも、『:validateのキーに引っかかるcallbacks』に関係していそうなメソッドを呼び出しているのが発見でき、

さらに、同じファイルに`define_callbacks :validate, scope: :name`という、これまた関係していそうなメソッドが`included`の中で呼ばれているのが見つかります。

```active_model/validations.rb
   define_callbacks :validate, scope: :name_badge: 
   
   ~省略~   

   def validate(*args, &block)
      options = args.extract_options!

      if args.all? { |arg| arg.is_a?(Symbol) }
        options.each_key do |k|
          unless VALID_OPTIONS_FOR_VALIDATE.include?(k)
            raise ArgumentError.new("Unknown key: #{k.inspect}. Valid keys are: #{VALID_OPTIONS_FOR_VALIDATE.map(&:inspect).join(', ')}. Perhaps you meant to call `validates` instead of `validate`?")
          end
        end
      end

      if options.key?(:on)
        options = options.dup
        options[:on] = Array(options[:on])
        options[:if] = Array(options[:if])
        options[:if].unshift ->(o) {
          !(options[:on] & Array(o.validation_context)).empty?
        }
      end

     set_callback(:validate, *args, options, &block)
   end
```

見つけたは良いものの、`define_callbacks`と`set_callback`が何なのか知らなかったので、ググってみると下記の記事が見つかりとても勉強になりました。

（参考: [ActiveModelでコールバックを使いたい時のまとめ - Qiita | Issus(イシューズ)
https://issus.me/projects/34/issues/71](https://issus.me/projects/34/issues/71)）
（参考: [railsのbefore_actionはどうやってうごいているか](https://qiita.com/kytiken/items/3655b4746a8cbbdc9833)）

上記記事を参考にざっくりまとめると、
`define_callbacks :validate`で`:validate`というnameを持つ一連のコールバックをまとめる`CallbackChain`インスタンスを作成し、
(コード中のコメントでは、sets of events in the object life cycle that support callbacksと表現されてました。）

`set_callback(:validate, *args, options, &block)`で、
先ほど定義した`:validate`という一連のコールバックの一つとして実行する処理として、引数で渡されたものを設定しています。

これで` _run_validate_callbacks`が具体的に何をするのかに繋がりました。
`set_callback(:validate, *args, options, &block)`で`:validate`というキーに引っかかる様にセットされた処理を実行していくということになります。
（また、複数形であることからも、複数のバリデーションがある場合は、複数回`set_callback`が呼ばれることもあるだろうと予想できます。）

`set_callback(:validate, *args, options, &block)`でコールバックの一つとして定義された処理こそがバリデーションの中身ということなります。

ということで次は、`set_callback(:validate, *args, options, &block)`がどのようなcallbackを定義するのかを追っていきます。

```active_support/callbacks.rb
    def set_callback(name, *filter_list, &block)
      type, filters, options = normalize_callback_params(filter_list, block)

      self_chain = get_callbacks name
      mapped = filters.map do |filter|
        Callback.build(self_chain, filter, type, options)
      end

      __update_callbacks(name) do |target, chain|
        options[:prepend] ? chain.prepend(*mapped) : chain.append(*mapped)
        target.set_callbacks name, chain
      end
    end
```

Callback.buildで作られる`ActiveSupport::Callbacks::Callback`インスタンスの一例としては、下記のようなものです。

`@kind`には:beforeや:afterなどの実行のタイミングを表す値が入り、指定がなかった場合は:beforeが入ります。
`@filter`には、引数で渡されたValidatorインスタンスが入り、これがコールバックで実行する処理の元となるっぽいです。

そして、それらを最後に`__update_callbacks`で一連のコールバック処理(chain)に追加しています。

```
 #<ActiveSupport::Callbacks::Callback:0x0000565553a47960
  @chain_config={:scope=>:name, :terminator=>#<Proc:0x0000565549744b50@/usr/local/bundle/gems/activesupport-5.2.2.1/lib/active_support/callbacks.rb:603>},
  @filter=#<ActiveRecord::Validations::PresenceValidator:0x0000565553a47cd0 @attributes=[:user], @options={:message=>:required}>,
  @if=[],
  @key=#<ActiveRecord::Validations::PresenceValidator:0x0000565553a47cd0 @attributes=[:user], @options={:message=>:required}>,
  @kind=:before,
  @name=:validate,
  @unless=[]>
```

あとは、`ActiveSupport::Callbacks::`が、どのように`ActiveModel::Validations::`配下のValidatorクラスのインスタンスを元にして、コールバックの処理を定義・実行しているのかを追えば、恐らく終わりというところまで来ました。

ところが、そのコールバックの`ActiveSupport::Callbacks`の仕組みを追っていったところ、
とても難しく、本記事にうまくまとめられなそうだったため、また別の記事として調べようと思います。（もしくはこの記事に追記します。）

（完全に実力不足でした。ここが一番気になるところなのにすみません。。）

ということで、一旦ここまでのバリデーション定義の流れをざっくりまとめると、下記のようになりました。

1. クラスの読み込み時に、`validates`といったバリデーション定義のクラスメソッドが実行される。
2. `validates`→`validates_with`(カスタムバリデータを用いたバリデーション定義で使うやつ）→`validate`（カスタムメソッドを用いたバリデーション定義で使うやつ）の順でメソッドが呼ばれる。
3. `validate`では、バリデーションの種類(:presenceとか:length)に応じて、対応するValidatorクラスのインスタンスを生成し、それを引数に含めて`set_callback`メソッドを呼び出す。
4. `set_callback`メソッドでは、引数で受け取ったValidatorを使用しながら、バリデーションを実行する処理を含むコールバックを定義する。（そして、このコールバックは`valid?`が呼ばれると実行されることになる。）


## 感想

色々と知らなかったことを知ることができ、とても勉強になりました。

余談として、`attr_accessor`といったアクセサや、`has_many`といったアソシエーションも同じクラスマクロとして調べる対象の候補だったのですが、
『[ActiveRecord完全に理解した](http://tolarian-academy.net/tech-book-fest-7/)』にて、前者は『すごく長い』、後者は『難しい』と紹介されていたので、特に何も書かれていなかったValidationsを今回は選択してみました。

勉強になったので、今度は他のクラスマクロも同様に調べてみようと思います。
（その前にCallbackとValidatorをきちんと調べてから。）

## 参考文献

下記の文献を見て色々と勉強させていただきながら、この記事を書いてみました。
どれも勉強になりました。

- [Railsのvalidationの実行過程を調べる](https://qiita.com/wakasa51/items/b3d65eac5cdad7787083)[^2]

- Paolo Perrotta(著),角 征典(翻訳)『メタプログラミングRuby 第2版』 オライリージャパン

- kinoppyd 著 『ActiveRecord 完全に理解した』技術書典 7（2019年秋）新刊 2019年9⽉22⽇ ver 1.0

- [Railsガイド](https://railsguides.jp/active_record_validations.html)

- [ActiveModelでコールバックを使いたい時のまとめ - Qiita | Issus(イシューズ)](https://issus.me/projects/34/issues/71)

- [railsのbefore_actionはどうやってうごいているか](https://qiita.com/kytiken/items/3655b4746a8cbbdc9833)

- [ActiveRecord::Baseを継承したクラスに登録されているコールバックを調べる方法](https://qiita.com/mah_lab/items/a80dbbf28547efef7019)

- [Qiitaで記事を公開するときに気を付けるべきマナーについて 〜無断でネットや書籍の内容を丸写しするのはやめよう〜](https://qiita.com/jnchito/items/215c2d51599eb29adabc)


[^1]: ここでModelと表している・想像しているものは主にApplicationRecordを継承しているクラスになります。ApplicationRecordはActiveRecord::Baseを継承しており、ActiveRecord::Baseにバリデーションの機能が組み込まれています。
[^2]: この記事にたどり着くのが書いてる中で後半の方になってしまったのですが、知りたいことがたくさん書いてあって、とても参考にさせていただきました。