Railsにおいてrequireがなぜ要らないのかを追ってみた(Gemfile編)
はじめに
こんにちは。現役でフルスタックエンジニアをしています。
Rails
はインストールした①Gemもですが、自ら定義した②クラス・モジュールもrequire
をする必要がない、利便性に優れたフレームワークです。
日々使う中でその恩恵にあやかっていますが、どのような内部実装でそれが可能になっているか気になったので調べて、記事にしてみました。
①Gem, ②クラス・モジュールの別記事に分けてつらつら書いていきます。
TL;DR
-
config/application.rb
に書かれている設定のおかげ - その設定で
Bundler
が呼び出されていて、そこでGemfile
内のGem及び依存関係を列挙してくれてる
きっかけ
「暇やし前からやってみたかった技術課題とかやってみるか」
「Ruby
使えるやん、ほなRuby
でと」
「Rails
ちゃうからrequire
書かなあかんわけか」
「前にRails
なんでrequire
いらんか調べたことあったけな、あれなんでやったかな」
「そうだ、内部実装を見よう」
つらつら書きましたが、要は、暇だったからです
Rails
内の設定箇所
設定箇所はconfig/application.rb
に記載されています。
Bundler.require(*Rails.groups)
が設定箇所になります。
require_relative "boot"
require "rails"
# Pick the frameworks you want:
require "active_model/railtie"
require "active_job/railtie"
require "active_record/railtie"
require "active_storage/engine"
require "action_controller/railtie"
require "action_mailer/railtie"
require "action_mailbox/engine"
require "action_text/engine"
require "action_view/railtie"
require "action_cable/engine"
# require "sprockets/railtie"
require "rails/test_unit/railtie"
# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups) # これが設定箇所
module RailsApp
class Application < Rails::Application
# Initialize configuration defaults for originally generated Rails version.
config.load_defaults 6.1
# Configuration for the application, engines, and railties goes here.
#
# These settings can be overridden in specific environments using the files
# in config/environments, which are processed later.
#
# config.time_zone = "Central Time (US & Canada)"
# config.eager_load_paths << Rails.root.join("extras")
# Only loads a smaller set of middleware suitable for API only apps.
# Middleware like session, flash, cookies can be added back manually.
# Skip views, helpers and assets when generating a new resource.
config.api_only = true
end
end
さて、設定箇所の上のコメント部分を見ると「Gemfile
にかかれているGemをrequire
する」と書かれているようです。
処理はBundler
が実行しているみたいなので、Bundler
を見にいってみましょう。
Bundler
のロジック
Ruby on Rails
を使っている開発者にとってBundler
というのは耳馴染みしかない言葉だとは思いますが、まずはBundler
の役割を確認しておきます。
公式リポジトリのREADMEの説明を抜粋すると、Bundler
は以下のことを行っているみたいです。
Ruby
を使ったアプリケーションがどのマシンでも同じコードを実行できるようにサポートする- Gemのリスト(
Gemfile
)から、リストに載っているGemだけでなく、それらのGemと依存関係にあるGemもインストールする- インストール前には、すべてのGemのバージョンをチェックし、互換性とダウンロードできるかを確認する
- Gemインストール後、新しいバージョンが利用できるようになれば、それを更新するようサポートする
- インストールされたGemのバージョンを記録し、他の開発者が同じGemをダウンロードできるようにする
列挙した役割の通り、パッケージ管理ツールですね。
どんなものかはわかったので、早速Bundler.require(*Rails.groups)
の実装を見ていきましょう。
コードが有るのはrubygems/bundle/lib/bundler.rb
です(ソースコード)
module Bundler
def setup(*groups)
# Return if all groups are already loaded
return @setup if defined?(@setup) && @setup
definition.validate_runtime!
SharedHelpers.print_major_deprecations!
if groups.empty?
# Load all groups, but only once
@setup = load.setup
else
load.setup(*groups)
end
end
def require(*groups)
setup(*groups).require(*groups)
end
def load
@load ||= Runtime.new(root, definition)
end
Rails
のソースコード - rails/railties/lib/rails.rb
module Rails
def groups(*groups)
hash = groups.extract_options!
env = Rails.env
groups.unshift(:default, env)
groups.concat ENV["RAILS_GROUPS"].to_s.split(",")
groups.concat hash.map { |k, v| k if v.map(&:to_s).include?(env) }
groups.compact!
groups.uniq!
groups
end
どうやら、Rails
の環境変数production
などを引数にして、必要なGemを判別しているようです。
Bundler
による、Gemfile
のGemの依存関係の抽出(下記のような配列型)に関しては、Bundler.load
内のRuntime.setup
が行っています。
ここでの処理は複雑な話になる(自分の理解出来たかも怪しい...)ので割愛しますが、Bundler.require(*Rails.groups)
の実行結果を見てみましょう。
[1] pry(main)> Bundler.require(*Rails.groups)
=> [Gem::Dependency.new("rails", Gem::Requirement.new(["~> 6.1.6"]), :runtime),
Gem::Dependency.new("dotenv-rails", Gem::Requirement.new([">= 0"]), :runtime),
... 中略,
Gem::Dependency.new("tzinfo-data", Gem::Requirement.new([">= 0"]), :runtime)]
Gemfile
に載っているGemとその依存関係を管理した配列を作成し一括で書いてくれているようです。
それが他のrequire "rails"
同様にapplication.rb
で呼び出されているためGemfile
のGemはrequire
がいらない、ということになるようです。
おわりに
1記事にまとめて2トピック書くつもりでしたが、長くなりそうなので分けることにしました。
フレームワークの便利さに頼ってコーディングを進めていると、時にそれが牙を向いて「よく分からんエラー」に変貌をとげることはしばしばあります。
フレームワークの内部実装を理解する、っていうのは非現実的ですが、そのフレームワークの長所足り得る部分(今回のrequire
不要は他フレームワークと比較するとRails
の長所)に関しては、フレームワークのロジックの根幹が集まったところ(私見)なので、そのフレームワークの開発者を使う者であるなら見ておくべきだと感じました。
2つ目の記事の「おわりに」に書くようなことが内容となってしまいましたw
まだまだジュニアで未熟ですが、内部実装も理解してフレームワーク・言語を使いこなせる開発者になれるよう精進します。
拙い文章でしたがお読みいただきありがとうございました。
Discussion