普段仕事で使っているRuby on Railsですが、ソースコードを読む機会もなかなかないので、試しにやってみることにしました
読めるようにするまで
以前書いた記事で読めるようにするまでの設定を画像キャプチャ付きで解説しましたので、よろしければこちらをご参照ください
shitake4.hatenablog.com
読んだ箇所
parents
を今日は読んでみようと思います
どんな使い方だっけ?
読んでみる前にまずは使い方を調べてみます
RAILS GUIDESの日本語ドキュメントを見てみると
parentsメソッドは、レシーバに対してparentを呼び出し、Objectに到着するまでパスをさかのぼります。連鎖したモジュールは、階層の下から上の順に配列として返されます。
module X module Y module Z end end end M = X::Y::Z X::Y::Z.parents # => [X::Y, X, Object] M.parents # => [X::Y, X, Object]
ソースコードを読んでみる
1. railsプロジェクトのactivesupportにある機能ですので、activesupportディレクトリのlib配下で def parents
を探してみます
2. 該当箇所が1箇所あったので、みてみます
1. activesupport > lib > active_support > core_ext > module > introspection.rb
# frozen_string_literal: true require "active_support/inflector" class Module 省略 # Returns all the parents of this module according to its name, ordered from # nested outwards. The receiver is not contained within the result. # # module M # module N # end # end # X = M::N # # M.parents # => [Object] # M::N.parents # => [M, Object] # X.parents # => [M, Object] def parents parents = [] if parent_name parts = parent_name.split("::") until parts.empty? parents << ActiveSupport::Inflector.constantize(parts * "::") parts.pop end end parents << Object unless parents.include? Object parents end end
まず、parents
変数を空の配列で初期化しています
次に、if文内をみてみます
if parent_name parts = parent_name.split("::") until parts.empty? parents << ActiveSupport::Inflector.constantize(parts * "::") parts.pop end end
分岐条件はparent_nameメソッドの戻り値が真の場合に処理が実行されるようです
※ parent_nameメソッドのコードリーディングについてはparentメソッドを読んだときに書きましたので、こちらを参照ください :Railsのソースコード読んでみる | Active Support parent編 - そういうこともある
parent_nameの戻り値は、親のモジュールの定数を戻り値とします
たとえば、このような戻り値となります
X::Y::Z.parent_name # => "X::Y" M.parent_name # => "X::Y"
真になるパターンは親のモジュールがある場合です
偽になるパターンは無名またはトップレベルの場合になります
parent_nameメソッドは無名またはトップレベルの場合に、戻り値がnilになるためです
さらに読み進めていきます
parts = parent_name.split("::")
はparent_nameの戻り値の文字列を'::'を区切り文字として分割し、parts変数へ格納するという処理になります
parent_nameの戻り値が、"X::Y"だとするとsplit後の戻り値はこちらになります
"X::Y".split("::") => ['X', 'Y']
※ Ruby 2.5.0 リファレンスマニュアル#split
次にunless内の処理をみていきます
until parts.empty? parents << ActiveSupport::Inflector.constantize(parts * "::") parts.pop end
until parts.empty?
でparts配列が空になるまでuntil … end
を繰り返し処理を行っています
※ Ruby 2.5.0 リファレンスマニュアル#until
※ Ruby 2.5.0 リファレンスマニュアル#empty
until内の処理をみてみます
parents << ActiveSupport::Inflector.constantize(parts * "::") parts.pop
constantizeメソッドの引数 parts * "::"
ですが、
配列partsの文字列要素の間に"::"を連結し、文字列を生成しています
Arry#*
を調べてみると
self * sep -> String[permalink][rdoc]
指定された sep を間にはさんで連結した文字列を生成して返します。Array#join(sep) と同じ動作をします。
[PARAM] sep:
文字列を指定します。 文字列以外のオブジェクトを指定した場合は to_str メソッドによる暗黙の型変換を試みます。
p [1,2,3] * "," # => "1,2,3"
ActiveSupport::Inflector.constantizeは引数の文字列を定数に変換します
※ constantizeメソッドのコードリーディングについては、こちらを参照ください:Railsのソースコード読んでみる | Active Support constantize編 - そういうこともある
そしてその戻り値をメソッドの最初行で初期化した変数に入れています
parents << ActiveSupport::Inflector.constantize(parts * "::")
そして parts.pop
で配列の末尾の要素を取り除いています
ここまでuntil、if文の処理が読み終わりました
最後にparents変数にObjectが含まれているか確認し、なかった場合はObjectを追加しています
そして戻り値はレシーバの親の定数が含まれた配列を戻り値としています
parents << Object unless parents.include? Object parents