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

# はじめに
プログラミング学習を開始して１年。自己流でアプリ開発を続けることによる成長幅に限界を感じ、次のステップとしてコードリーディングをすることにしました。
こちらの記事では、コードリーディングで得た知識の備忘録を記していきます。

なお今回はこちらの記事を参考にし、コードリーディングの環境を整えることができました。
またvalidatesの解説も載っていましたのでそちらもかなり参考にしていますが、今回は私のような初学者に価値があるような内容を共有させて戴きます。
>https://qiita.com/jabba/items/8a9ac664eb2a0e61e621

# 今回はmodelのvalidatesメソッドをリーディング


###ターミナルで以下を実行。

```console
rails c
[1] pry(main)> u = User.new(name: 'sample name')
```

### nameの長さの最大が30というvalidatesの場合の処理を見ていきます。

```user.rb
class User < ApplicationRecord
  #binding.pryで処理を止める
  binding.pry
  validates :name, length: { maximum: 30 }
end
```

### validatesメソッドがどのように宣言されているのか。

Userクラスは`ApplicationRecord`を継承していますが、さらに元をたどると`ActiveRecord::Base`を継承しています。
以下Railsのreadmeで以下のようにmodel層に関して解説がありました。

> Model layer
models can also be ordinary Ruby classes, or Ruby classes that implement a set of interfaces as provided by the Active Model module.
モデルは、通常のRubyクラス、またはActiveModelモジュールによって提供される一連のインターフェースを実装するRubyクラスにすることもできます。
https://github.com/rails/rails

いつも利用している便利なメソッドは、ActiveModelモジュールによって提供されたものということがわかりました。

## 本題の`validates.rb`を読んでみた。


#### 確かにActiveModelモジュールの中に宣言されていました。

```rails/activemodel/lib/active_model/validations/validates.rb
module ActiveModel
  module Validations
    module ClassMethods
      def validates(*attributes)
       
      （省略）

      end
    end
  end
end
```

## 処理①引数の調整

```rails/activemodel/lib/active_model/validations/validates.rb
def validates(*attributes)
        defaults = attributes.extract_options!.dup
        validations = defaults.slice!(*_validates_default_keys)
```

extract_options!の継承元は以下の通り。

```rails/activesupport/lib/active_support/core_ext/array/extract_options.rb
# frozen_string_literal: true

class Hash
  # By default, only instances of Hash itself are extractable.
  # Subclasses of Hash may implement this method and return
  # true to declare themselves as extractable. If a Hash
  # is extractable, Array#extract_options! pops it from
  # the Array when it is the last element of the Array.
  def extractable_options?
    instance_of?(Hash)
  end
end

class Array
  # Extracts options from a set of arguments. Removes and returns the last
  # element in the array if it's a hash, otherwise returns a blank hash.
  #
  #   def options(*args)
  #     args.extract_options!
  #   end
  #
  #   options(1, 2)        # => {}
  #   options(1, 2, a: :b) # => {:a=>:b}
  def extract_options!
    if last.is_a?(Hash) && last.extractable_options?
      pop
    else
      {}
    end
  end
end

```
defaultに代入する値では、
渡された引数の最後がハッシュクラスの直接のインスタンスで書かれている場合は、pop
それ以外は、{}
を代入しています。

### 学び① is_a?(mod)
is_a?(mod)メソッドを使用すれば、オブジェクトが指定されたクラス mod かそのサブクラスのインスタンスであるとき真を返します。
したがって、ここではattributes(validatesの引数)がハッシュクラスかどうか判定しています。

https://docs.ruby-lang.org/ja/latest/method/Object/i/is_a=3f.html

### 学び② instance_of?(klass)

>オブジェクトがクラス klass の直接のインスタンスである時真を返します。
>https://docs.ruby-lang.org/ja/latest/method/Object/i/instance_of=3f.html

### 学び③　popとpush
基本情報処理の試験勉強で学んでいたものでした。
popは配列の末尾から指定個数取り出し、取り出した要素を返す。
pushは末尾に新たな要素を加える。

>https://docs.ruby-lang.org/ja/latest/method/Array/i/pop.html
>https://docs.ruby-lang.org/ja/1.8.7/method/Array/i/push.html

### 学び④ dup
オブジェクトを複製することができます。
そのもの自体を変更したくない時に便利そうです。
https://docs.ruby-lang.org/ja/latest/method/Object/i/clone.html

### 疑問点
`instance_of?(Hash)`のみで条件式として十分なのではないのか？
どのような意図があって、AND条件なのだろうか。


# 終わりに
本日はここまでにします。
たった1行読み込むだけで相当な勉強になりました。（コードリーディング恐るべし）
疑問点が残ってしまいましたが、わかり次第追記して修正します。