LoginSignup
0
0

More than 3 years have passed since last update.

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

Last updated at Posted at 2021-04-29

はじめに

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

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

https://qiita.com/jabba/items/8a9ac664eb2a0e61e621

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

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

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の引数)がハッシュクラスかどうか判定しています。

学び② 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行読み込むだけで相当な勉強になりました。(コードリーディング恐るべし)
疑問点が残ってしまいましたが、わかり次第追記して修正します。

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