見出し画像

初心者がRailsのコードリーディングに挑戦 #8


以前見たこちらの記事を参考に、Railsのコードリーディングに挑戦してみたいと思います。

◇ 前回までの記事一覧


確認用のRailsプロジェクトを作る

の続きから!

前回Gemの実験のときにちょっと進めてしまいましたが、順を追って書いておきます。

5.Userモデルをつくる
コマンドプロンプトに以下のように入力しました。

“rails generate” も “rails g” と略して書けるんですね

6.Userモデルを編集してValidationを入れる
user.rbに追記します。

もとはこれ。
追記しました。

参考サイトは ”ApplicationRecord” ではなく “ActiveRecord::Base” になっているんですが、書きかえた方がいいのかな。

このままやってみても問題なく動いてそうだったので、とりあえずこのままいってみます。

コードを読む

コマンドプロンプトでコンソールを起動します。

pry-railsなどのGemを入れたので、rails c入力後に出てくる表示がちょっと変わりましたね。

そして、u = User.new(name: 'sample name')と入力した後がこちら。

次にstepと入力します。
その結果がこちら。

画像下のずらっと並んでいる部分はvalidatesというメソッドを定義しているソースコードを表示してくれています。
赤線部にファイルの場所、赤丸部にファイルの中の何行目か、四角く囲った部分は「下の記述はActiveModelというモジュールの中のValidationsというモジュールの中のClassMethodsというモジュールの中のvalidatesというメソッドですよ。」と教えてくれています(モジュールについての詳細は下に)。
ソースコードのファイルを見に行っても記述が長すぎて「このメソッドはいったい何モジュールの中の何クラスにあるんだ?」と確認するのが大変なのですごくありがたいです。

ソースコードのファイルではこんな感じで書いてあります。

module ActiveModel
  module Validations
    module ClassMethods
             •
             •
             •
      def validates(*attributes)
        以下続く

こんな感じで”モジュール”というのは、クラスやメソッドをまとめることができるようです。


参考: モジュール(module)について

モジュール関連で気が付いた事をひとつ。
以前『とほほのRuby on Rails入門』で見たこの記述について

この右側のActionControllerというのは”モジュール”だったようで、Baseというのはその中にあるクラスを表しているようです。

なので「ApplicationControllerクラスは、ActionControllerモジュールのBaseクラスを継承しています」という意味だったみたいです。

例えばコードが膨大になってBaseクラスという名前が被ってしまったとしても、それぞれをモジュールの中に入れておけば “〇〇モジュールの中のBaseクラス” みたいな感じで特定できるから便利という事かなと思いました。


手順に戻ります。
stepした結果の107行目に注目 ↓
(元の記事とはRailsのバージョンが違うので行数も変わっています)

defaultsという変数に入れられているのは、attributesにextract_options!というメソッドをつけてdupメソッドを使ったものみたいです。

extract_options!はこの後見ていくようなので、先にdupメソッドについて調べます。

dupメソッド

Rubyで配列やハッシュなどのオブジェクトをコピーする場合、別の変数名で参照できる方法と、同じ内容を複製する方法の2つが使えます。具体的に前者は、「=」でオブジェクトをコピーした場合で、後者はdupメソッドまたはcloneメソッドを使った場合です。

【=演算子で配列をコピーした例】
a = [1, 2, 3]
b = a
p a # [1, 2, 3]が表示される
p b # [1, 2, 3]が表示される
a[0] = 10
p a # [10, 2, 3]が表示される
p b # [10, 2, 3]が表示される

【dupメソッドで配列をコピーした例】
a = [1, 2, 3]
b = a.dup
p a # [1, 2, 3]が表示される
p b # [1, 2, 3]が表示される
a[0] = 10
p a # [10. 2. 3]が表示される
p b # [1, 2, 3]が表示される

【=演算子で配列をコピーした例】ではaを参照するbという変数を作っただけだけど、【dupメソッドで配列をコピーした例】ではa = [1, 2, 3] をコピーしたので、bは [1, 2, 3] で固定されるということなんですね。

なのでここでは「attributes = 何か」が他のところで定義されていて、それをdefaultsにコピーしているんですね。

ではこの「attributes = 何か」とは何なのか。
それを見る方法があるようです。
コンソールでattributesと入力。

こんな感じで出てきます。
attributes = [:name, {:length=>{:maximum=>30}}]
と分かったので、
defaults = [:name, {:length=>{:maximum=>30}}]
という事なんですね。
ただしここにextract_options!というメソッドも絡んでくると。

なので次はが定義されているソースコードを見に行くんですね。

先程のコンソール画面のここ
↓ に今カーソルがあるので

そのままstepと入力すると、extract_options!の記述場所が表示されます。

ここの記述について、分かりやすそうな記事がこちら。

配列の最後の要素が Hash かつ extractable_options? が true なら、配列の最後の要素(Hash)を取り出す、そうでなければ空の Hash を返します。
つまり、Array#extract_options! は可変長引数からハッシュで指定されたオプションを取り出すためのメソッドです。

ハッシュというのは{:length=>{:maximum=>30}}みたいな感じで ”文字列を添字とする配列” だそうです。

参考: ハッシュ(Hash)について


そして、extractable_options?というのはextract_options!が記述されているのと同じファイルでこのように定義されています。

赤枠がHashクラスのextractable_options?
(白枠はArrayクラスのextract_options!)

extractable_options?メソッドの中のinstance_of?(Hash)という処理について、以下の記事が分かりやすかったです。

is_a?メソッド、instance_of?メソッド

Ruby on RailsやRubyには、要素の型を確認し、指定した型にあっていればtrue、違えばfalseを返すメソッドとして、is_a?, kind_of?, instance_of?が用意されています。

この3つは大体同じ挙動をするようです。
細かい違いはまた今度見るとして、今回使われているinstance_of?を見ていきます。

使い方は以下のような感じ。
a = [1, 2, 3]
a.instance_of?(Array) => true

aArray型(配列)であればtrueを返す。

◇ is_a?, kind_of?, instance_of?メソッドで指定できる主な型

先程のサイトから引用しました。

これをふまえてinstance_of?(Hash)というのを見ていきます。

こんな感じになっていてややこしいですが、つまりlast.instance_of?(Hash)ということですよね。

lastというのはattributes = [:name, {:length=>{:maximum=>30}}] の最後の要素のことらしいので、{:length=>{:maximum=>30}}という事になります。
そしてこれはHash型なのでtrueを返すという事ですね。

めちゃくちゃややこしいですね。

そしてここまで分かってくると「if last.is_a?(Hash) && last.extractable_options?って同じ事2回確認してない?」と思ってしまいました。

ここでis_a?とinstance_of?の違いが関係してくるんですかね。

is_a?とinstance_of?には明確な違いがあります。それはinstance_of?はレシーバーのみ評価するのに対し、is_a?はスーパークラスの型も評価します。

参照しているのは先程と同じサイトです

例えば、RubyにおいてArrayというクラスはObjectというクラスの中の一つだそうです。
このようにあるクラスの親にあたるクラスの事をスーパークラスと呼ぶようです。

◇ 主な型のスーパークラス

これも先程のサイトから引用しました。

なので例えばaがHash型として、a.is_a?(Object)としてもtrueが返ってくるという事ですよね。

でも今回はis_a?(Hash)なので関係ないですが。

やっぱりまだ「if last.is_a?(Hash) && last.extractable_options?」と、わざわざ「last.is_a?(Hash)かつlast.extractable_options?」としている理由は分かりませんでした。
きっと何か意味があるんでしょう。


こんなふわふわで申し訳ありませんが、だいぶ長くなってしまったので一旦区切ります。
次回は続きをもう少し見ていきます。

ソースコードを読むのって、あっちこっち見ないといけなくてすごく難しいですね。
ただこれもまた、最初は全然意味が分からなかった事が少しずつ分かるようになってくるのが楽しいですね。

そしてRubyの知識もとても重要だということが分かりました。
これを機にひとつずつ学んでいきたいと思います。


この記事が気に入ったらサポートをしてみませんか?