普段仕事で使っているRuby on Railsですが、ソースコードを読む機会もなかなかないので、試しにやってみることにしました
読めるようにするまで
以前書いた記事で読めるようにするまでの設定を画像キャプチャ付きで解説しましたので、よろしければこちらをご参照下さい
shitake4.hatenablog.com
読んだ箇所
subclasses
を今日は読んでみようと思います
どんな使い方だっけ?
読んでみる前にまずは使い方を調べてみます
RAILS GUIDESの日本語ドキュメントを見てみると
subclassesメソッドはレシーバのサブクラスを返します。
class C; end C.subclasses # => [] class B < C; end C.subclasses # => [B] class A < B; end C.subclasses # => [B] class D < C; end C.subclasses # => [B, D]返されるクラスの順序は一定ではありません。
ソースコードを読んでみる
1. railsプロジェクトのactivesupportにある機能ですので、activesupportディレクトリのlib配下で def subclasses
を探してみます
2. 該当箇所あったので、みてみます
1. activesupport > lib > active_support > core_ext > class > subclasses.rb
class Class 省略 # Returns an array with the direct children of +self+. # # class Foo; end # class Bar < Foo; end # class Baz < Bar; end # # Foo.subclasses # => [Bar] def subclasses subclasses, chain = [], descendants chain.each do |k| subclasses << k unless chain.any? { |c| c > k } end subclasses end end
まず、一行目から見ていきます
def subclasses subclasses, chain = [], descendants
多重代入を行い、2つの変数の初期化をしています
Rubyリファレンスの多重代入を見てみます
多重代入
例:
foo, bar, baz = 1, 2, 3 foo, = list() foo, *rest = list2()文法:
式 [`,' [式 `,' ... ] [`*' [式]]] = 式 [, 式 ... ][`*' 式] `*' [式] = 式 [, 式 ... ][`*' 式]</p>
今回の場合だと、 変数 subclasses
は空の配列で初期化、 変数 chain
は descendants
メソッドの戻り値で初期化されています
descendants
というメソッド名から推測すると、 descendant
は子孫という意味、複数形の s
が付く為、 継承チェインのオブジェクトの配列が戻り値では無いかと思われます
次の処理を見ていきます
chain
は継承チェイン上にあるオブジェクトの配列を each
で1つずつ取り出しています
chain.each do |k| subclasses << k unless chain.any? { |c| c > k } end
まずは、unless 修飾子を読んでみます
unless chain.any? { |c| c > k }
any?
は真である要素があれば、trueを戻り値とし、無ければfalseを返します
今回はブロックがある為、ブロックの条件を満たす場合に、trueを返し、満たさない場合は、falseになります
つまり、条件を1度も満たさない場合のみ、左辺の式を評価するということです
ブロック内の処理を見ると、配列 chain
と eachで取り出した、 k
を比較しています
例えば、class Cを継承した B, D がある場合
class C; end C.subclasses # => [] class B < C; end C.subclasses # => [B] class D < C; end C.subclasses # => [B, D]
Cに対して、 descendants
を実行すると以下になります
つまり、変数 chain
は [D, B]
です
改めて、eachの処理を見ます
chain.each do |k| subclasses << k unless chain.any? { |c| c > k } end
ブロック引数 k
が D
となります
chain.any? { |c| c > k }
を見ると、
c
はD
の場合は、D > D で false
c
はB
の場合は、B > D で false
です
そのため、空配列の変数 subclasses
に ブロック引数 k
が追加されます
次のループでは、
ブロック引数 k
が B
となります
chain.any? { |c| c > k }
を見ると、
c
はD
の場合は、D > B で false
c
はB
の場合は、B > B で false
です
そのため、空配列の変数 subclasses
に ブロック引数 k
が追加されます
すると変数 subclasses
は [D, B]
です
メソッドの処理全体を見ていくと
def subclasses subclasses, chain = [], descendants chain.each do |k| subclasses << k unless chain.any? { |c| c > k } end subclasses end
変数 subclasses
を戻り値としています