Railsであるクラスを継承しているクラスを取得する方法(再帰する方法と、もっとシンプルなメソッドを使う方法)

夏は海より山派の @RKTM です。 鈴鹿山脈神崎川(愛知川)でじゃぶじゃぶ泳いでは滝壺に飛び込む夏休みでした。

f:id:RKTM:20150819110840j:plain

Controllerの一覧を取得したい!

とある事情*1により、ApplicationControllerを継承したControllerの一覧を取得したいと思いました。 名前空間などを使って加工もしたいので、ファイル名ではなく、Classとして取得したいという要件。

ActiveSupportのsubclassesメソッド

調べてみると、ActiveSupportsubclassesというメソッドが実装されているようです。

subclasses (Class) - APIdock

このメソッドは便利なのですが、直接の子どものクラスまでしか取得できず、 孫クラスやひ孫クラスまで取得できません。

再帰させて子孫までクラスを辿る

ということで再帰させてみました。

rails console

Rails.application.eager_load! # 通常は名前が参照されるとautoloadされる。今回は強制的にロードする

def list_descendants(*klasses)
  return klasses if klasses.empty?
  return klasses.map { |klass| list_descendants(*klass.subclasses) << klass }.flatten
end

list_descendants ApplicationController

=> [ApplicationController,
 ApiConnectorController,
 AbstractDocumentsController,
 DeliverySlipsController,
 EstimatesController,
 InvoicesController,
 AccountReceivableGuaranteeRequestsController,
 Accounts::AddonsController,....]

これでApplicationControllerの子どもや孫たちをClassとして取得できました。

再帰なんて面倒だ!もっとシンプルなActiveSupport::DescendantsTracker.descendants

上記のことを一撃で実現してくれるのが、

Module: ActiveSupport::DescendantsTracker — Documentation for rails (3.0.0) です。

同じように実行してみましょう。

rails console

Rails.application.eager_load!

ActiveSupport::DescendantsTracker.descendants(ApplicationController)

# (結果は省略)

最後に

あるクラスを継承する子孫クラスを取得するために、再帰する方法と、一撃で実現する方法を紹介しました。 条件分岐する必要がなければ、再帰ではなく、ActiveSupport::DescendantsTracker.descendantsを使うと良いのではないでしょうか。

ActiveSupportは色々と便利な機能がありますね!

*1:コントローラ名や名前空間を変更したかった