Railsのソースコード読んでみる | Active Support subclasses編

f:id:sktktk1230:20190921180106p:plain

普段仕事で使っている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]

返されるクラスの順序は一定ではありません。

引用:RAILS GUIDES:subclasses

ソースコードを読んでみる

1. railsプロジェクトのactivesupportにある機能ですので、activesupportディレクトリのlib配下で def subclasses を探してみます

f:id:sktktk1230:20190806150812p:plain

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>

引用:Ruby 2.6.0 リファレンスマニュアル:多重代入

今回の場合だと、 変数 subclasses は空の配列で初期化、 変数 chaindescendants メソッドの戻り値で初期化されています

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を返します

参照:instance method Enumerable#any? (Ruby 2.6.0)

今回はブロックがある為、ブロックの条件を満たす場合に、trueを返し、満たさない場合は、falseになります
つまり、条件を1度も満たさない場合のみ、左辺の式を評価するということです

ブロック内の処理を見ると、配列 chain と eachで取り出した、 k を比較しています

参照:module Comparable (Ruby 2.6.0)

例えば、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 を実行すると以下になります
f:id:sktktk1230:20190806150936p:plain

つまり、変数 chain[D, B] です

改めて、eachの処理を見ます

chain.each do |k|
  subclasses << k unless chain.any? { |c| c > k }
end

ブロック引数 kD となります
chain.any? { |c| c > k } を見ると、

  1. cD の場合は、D > D で false
  2. cB の場合は、B > D で false

です
そのため、空配列の変数 subclasses に ブロック引数 k が追加されます

次のループでは、 ブロック引数 kB となります
chain.any? { |c| c > k } を見ると、

  1. cD の場合は、D > B で false
  2. cB の場合は、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 を戻り値としています