Railsコードリーデイング② validatesメソッドを深掘りしてみた　【備忘録】

# はじめに
https://qiita.com/engineer_ikuzou/items/e78fe4d5b4977bbe86c2
こちらの続編です。

# 前提
名前の長さ30文字制限をかけています。

```console
u = User.new(name: 'sample name')
```

```app/models/user.rb
class User < ApplicationRecord
  binding.pry
  validates :name, length: { maximum: 30 }
end
```

# validatesメソッド2行目から見ていきます。


```rails/activemodel/lib/active_model/validations/validates.rb
def validates(*attributes)
        defaults = attributes.extract_options!.dup
        validations = defaults.slice!(*_validates_default_keys)
```
defaultsには前回確認した値がは格納されています。
未確認の引数_validates_default_keysが出てきたので、確認してみると、ハッシュで条件文が格納されていました。

https://railsguides.jp/active_record_validations.html
条件付きvalidationの箇所と、そうでない部分を分けるための処理のようです。

```terminal
[4] pry(User)> defaults
=> {:length=>{:maximum=>30}}
[5] pry(User)> _validates_default_keys
=> [:if, :unless, :on, :allow_blank, :allow_nil, :strict]
```
validations：　dafaultsでsliceの対象外のhashを格納
dafaults:     sliceの対象となったifなどがあれば格納
上記のように格納される処理でした。

# 学び① sliceメソッド
引数で指定されたキーとその値だけを含む Hash を返します。
A=[ a:100, b:300, c:500 ]という配列に対して、A.slice(:a)は{:a=>100}を返します。
https://docs.ruby-lang.org/ja/latest/method/Hash/i/slice.html

また、全く意識していなかったのですが破壊的メソッド自体も定義されており、その中でsliceを使用しているのですね。
少し考えたらわかることだと思うのですが、意識できていませんでした。
また破壊的メソッドはそれ自体を更新する一方で、返り値としては更新されなかった部分を返してくれるのですね。

```
From: 
rails/activesupport/lib/active_support/core_ext/hash/slice.rb:11 Hash#slice!:

    10: def slice!(*keys)
 => 11:   omit = slice(*self.keys - keys)
    12:   hash = slice(*keys)
    13:   hash.default      = default
    14:   hash.default_proc = default_proc if default_proc
    15:   replace(hash)
    16:   omit
    17: end
```
10: omitに、引数keysを除くhashがあれば避けておき、返り値とする。
15: hashはこちらで更新。


# validatesメソッド3,4行目
```rails/activemodel/lib/active_model/validations/validates.rb
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?
```
[:if, :unless, :on, :allow_blank, :allow_nil, :strict]に該当するもののみvalidatesの引数にある場合は、エラーを起こします。
条件付きvalidatesの場合、条件true時のvalidationを記載する必要があるため、記載があるかどうかチェックしています。


# validatesメソッド 5,6行目

```rails/activemodel/lib/active_model/validations/validates.rb
validations.each do |key, options|
  key = "#{key.to_s.camelize}Validator"
```

`validations => {:length=>{:maximum=>30}}`
なのでkeyは`:length`, optionsは`{:maximum=>30}`です。
キャメルケースへの変換が行われ、keyは、`LengthValidator`となります。

# 学び② camelizeメソッド
デフォルトでアッパーキャメルケースに変換します。
https://www.rubydoc.info/gems/activesupport/String:camelize

# validatesメソッド ７~11行目
```rails/activemodel/lib/active_model/validations/validates.rb
begin
  validator = key.include?("::") ? key.constantize : const_get(key)
rescue NameError
  raise ArgumentError, "Unknown validator: '#{key}'"
end
```
validatorが既にモジュールの形で表記されているかで条件式が分かれています。
今回は、モジュール形式ではないので、const_getメソッドで`ActiveRecord::Validations::LengthValidator`が返ります。

# 学び③ constantizeメソッド
引数の文字列で指定した名前で定数を探す。
https://railsdoc.com/page/constantize

# 学び④ const_getメソッド
name で指定される名前の定数の値を取り出します。
https://docs.ruby-lang.org/ja/latest/method/Module/i/const_get.html

#　終わりに
今回はここまでです。
メソッドもそれぞれどのような挙動をしているのか確認することもできるのですが、そこの深掘りはしませんでした。あくまでvalidatesメソッドの深掘りです。全てを読み込むと永遠に終わりのない勉強なので、どこをどこまで深掘りするか、学習の目的をしっかり意識することが大事だと思いました。