LoginSignup
0
0

More than 3 years have passed since last update.

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

Posted at

はじめに

https://qiita.com/engineer_ikuzou/items/e78fe4d5b4977bbe86c2
こちらの続編です。

前提

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

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の箇所と、そうでない部分を分けるための処理のようです。

[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メソッド 7~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メソッドの深掘りです。全てを読み込むと永遠に終わりのない勉強なので、どこをどこまで深掘りするか、学習の目的をしっかり意識することが大事だと思いました。

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