【Rails】Railsで暗黙的にクラスが読み込まれる魔法の裏側

Ruby Ruby
Ruby

はじめに

Railsで作成したクラスがrequireでインポートされることなく魔法のようにそこらかしこで使われていることに疑問を持ったため調べました。

一般的にPythonを例にすると、フレームワークに関わらず外部から読み込むクラスはimportする必要があります。

当然のことながらRuby単体であればrequireで対象のオブジェクトを指定する必要があります。

しかし、Railsになると突然これまで読み込んでいたクラスを暗黙的に使えるようになるのです。

例えば下記のような例。

class BooksController < ApplicationController
  before_action :set_book, only: %i[ show edit update destroy ]

  # GET /books or /books.json 
  def index 
    @books = Book.all 
  end

  <省略>
end

この例はよくRailsの教本に乗っているbook_appのコントローラファイルの一部ですが、重要なのが@books = Book.all end の部分です。

このBookクラスはモデルディレクトリの別ファイルで定義されています。

class Book < ApplicationRecord
end

しかし、突然何の前触れもなくコントローラファイルから読み込まれているのです…!

これはRailsの魔法なのでしょうか!?

今回はこの現象についてざっくり解説していきます。

暗黙的に読み込まれる理由

答えはRailsのドキュメントにありました。

今回のポイントは3つ。

  1. Rubyではクラス・モジュールの定義=定数定義
  2. Rubyの定数はモジュール内にで保存される
  3. autoload_paths配下ディレクトリから定数を参照する

Rubyではクラス・オブジェクトの定義=定数定義

それでは一つひとつ見ていきましょう。

一つ目はRubyの特性です。

Rubyはオブジェクト思考に則った代表的なプログラミング言語です。

このRubyの特徴の一つに定数の取り扱い方があります。

それが「クラス・モジュールの定義=定数定義」ということです。

 

これについて具体的なコードを交えて説明していきます。

まずはRubyにおけるクラス定義は下記の2パターンあるかと思います。

class A
end
class Project < ActiveRecord::Base
end

これらのクラス定義は実は下記と同義になります。

A = Object.new
Project = Class.new(ActiveRecord::Base)

Ruby内では定義したいクラス名の”C”や”Project”という定数にクラスオブジェクトを保存する意味になります。

そのため定義された”C”や”Project”は、メソッドなどを定義しなくても定義元のクラスの振る舞いをすることができます。

クラスだけでなく、モジュールでもど同様の定義になります。

またこの動きを担保する上で一つの特殊ルールがあります。

Railsドキュメントではこのように説明されています。

代入されるオブジェクトが無名のクラスまたはモジュールである場合、Rubyはそれらのオブジェクトの名前をその定数の名前から引用して命名します

無名クラスや無名モジュールにひとたび名前が与えられた後は、定数とインスタンスで何が起きるかは問題ではありません。たとえば、定数を削除することもできますし、クラスオブジェクトを別の定数に代入したりどの定数にも保存しないでおくこともできます。名前はいったん設定された後は変化しなくなります。

<省略>

何かが非公式に “Stringクラス” と呼ばれる場合、その本当の意味はこういうことです。”Stringクラス” とは、Objectという定数があり、その中にクラスオブジェクトが保存されていて、さらにその中に”String”と呼ばれる定数があり、さらにその中に保存されているクラスオブジェクトのことなのです。

面白いですね。

Rubyの基底クラスはObjectクラスになり、それらが継承される全てのクラスがインスタンスとして定数定義される。

これがRailsでクラスを明示的にインポートしなくても使える謎への一つ目の道標です。

 

さらにこの文言「”Stringクラス” とは、Objectという定数があり、その中にクラスオブジェクトが保存されていて、さらにその中に”String”と呼ばれる定数があり、さらにその中に保存されているクラスオブジェクトのこと」

つまり、あるメモリ上のObjectクラスから見てそのメモリ上で定義されたクラスは定数としてObjectから認識される、ということなのです。

この話は次の章にも関わることですので、ここではあまり深く言及しません。

しかし、Rubyにおいてはクラス・モジュール定義=定数定義については理解できたと思います。

 

Rubyの定数はモジュール内で保存される

続いてのポイントはRubyの定数はモジュール内で保存されるという点です。

これが今回の謎をもたらす最も重要なRubyの特徴です。

具体的にはクラスとモジュールには定数テーブルというものが存在します。

例えば下記のようにモジュールが定義されたとします。

module Test
  class A
  end
end

この状態で処理されると、Rubyインタプリタ(Ruby言語のバイナリへの翻訳)により基底のObjectの定数テーブルに”Test”と、新しく作られたモジュールのメモリアドレスを関連づける情報がエントリーされ保存されます。

この時このエントリーにより新しく定義されたモジュールオブジェクトの名前をTestとして登録します。

こうしてTestモジュールは”Test”として同一メモリ上でグローバルに参照される定数として定義されることになります。

 

さらに class A 部分では、先ほど登録されたTestモジュールオブジェクト内の定数テーブルに、Aという名前でクラスのメモリアドレスが紐付けられます。

こうしネスト的にAもTestモジュールオブジェクト内で定数定義されることになります。

この関係を下記のように表します。

Test::A

このような仕組みでRuby内では定義されたモジュールやクラスを定数として保存し、モジュール内からの参照を可能にしています。

他のプログラミング言語における定数の取り扱いと、Rubyにおける定数の取り扱いは一線を画しているようですね。

仮に他のクラスオブジェクトやモジュールオブジェクトの中に”Test::A”という同一定義が存在していても、今回の”Test::A”とは全くの無関係になります。

<参考> 親の名前空間からの参照

先ほどまでのRubyのモジュールとクラスと定数定義の関係性から分かると思います。

子クラス、子モジュールは親の定数テーブルに関連づけられ、親の名前空間を利用して定数パスが定義されます。

これにより親子定義されたクラス、モジュールを容易に呼び出し可能になるのです。

 

autoload_paths配下ディレクトリから定数を参照する

今回の謎の解明にたどり着く最後のポイントです。

Rails内のどこかで定義したクラスオブジェクトやモジュールオブジェクトをインポートなしで使える理由がこれです。

autoload_pathsからの参照

これが唯一の”Rails”における特徴になります。

通常、Rubyでは$LOAD_PATHという環境変数で定義されたディレクトリは以下でrequire要求のファイルを探します。

しかしRailsではautoload_pathsというものが定義されることにより独自の動きをします。

autoload_pathsとはRailsのappディレクトリ配下にある全てのサブディレクトリのことです。

Railsではこのautoload_pathsがデフォルトで登録されており、Rubyインタプリタがオブジェクトを探すときに自動でこのパス配下を探索します。

このautoload_pathsのおかげでRailsのコントローラから別のどこかで定義したオブジェクトをインポートなしに使うことができるのです。

・autoload_pathsのおかげでRails内でのオブジェクト参照が暗黙的に可能になる。

・前提としてはクラス・モジュールの定義や定数テーブルの存在がある

 

おわりに

いかがだったでしょうか。

Pythonに慣れていた筆者が最初にRailsの中身を見たときに「いろんなものが省略されすぎじゃね?」と思った記憶があります。

省略されすぎて初見でのハードルは上がりますが、使いこなすことでかなり便利に使えるのではとも思いました。

ぜひ、この辺りのRailsの裏の動きについては今後も勉強を続けていきたいものです。

 

ご質問やご意見はこちらからお願いします。

タイトルとURLをコピーしました