Skip to content

Commit 3ad6358

Browse files
committed
Lazily boot the rails app during tapioca gem
1 parent d90ca33 commit 3ad6358

File tree

2 files changed

+25
-114
lines changed

2 files changed

+25
-114
lines changed

lib/tapioca/loaders/loader.rb

Lines changed: 2 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,11 @@ def load; end
2929
def load_bundle(gemfile, initialize_file, require_file, halt_upon_load_error)
3030
require_helper(initialize_file)
3131

32-
load_rails_application(halt_upon_load_error: halt_upon_load_error)
33-
3432
gemfile.require_bundle
3533

36-
require_helper(require_file)
34+
load_rails_application(environment_load: true, halt_upon_load_error: halt_upon_load_error)
3735

38-
load_rails_engines
36+
require_helper(require_file)
3937
end
4038

4139
sig do
@@ -85,116 +83,6 @@ def load_rails_application(environment_load: false, eager_load: false, app_root:
8583
say("Continuing RBI generation without loading the Rails application.")
8684
end
8785

88-
sig { void }
89-
def load_rails_engines
90-
return if engines.empty?
91-
92-
with_rails_application do
93-
run_initializers
94-
95-
if zeitwerk_mode?
96-
load_engines_in_zeitwerk_mode
97-
else
98-
load_engines_in_classic_mode
99-
end
100-
end
101-
end
102-
103-
def run_initializers
104-
engines.each do |engine|
105-
engine.instance.initializers.tsort_each do |initializer|
106-
initializer.run(Rails.application)
107-
rescue ScriptError, StandardError
108-
nil
109-
end
110-
end
111-
end
112-
113-
sig { void }
114-
def load_engines_in_zeitwerk_mode
115-
# Collect all the directories that are already managed by all existing Zeitwerk loaders.
116-
managed_dirs = Zeitwerk::Registry.loaders.flat_map(&:dirs).to_set
117-
# We use a fresh loader to load the engine directories, so that we don't interfere with
118-
# any of the existing loaders.
119-
autoloader = Zeitwerk::Loader.new
120-
121-
engines.each do |engine|
122-
eager_load_paths(engine).each do |path|
123-
# Zeitwerk only accepts existing directories in `push_dir`.
124-
next unless File.directory?(path)
125-
# We should not add directories that are already managed by a Zeitwerk loader.
126-
next if managed_dirs.member?(path)
127-
128-
autoloader.push_dir(path)
129-
end
130-
end
131-
132-
autoloader.setup
133-
end
134-
135-
sig { void }
136-
def load_engines_in_classic_mode
137-
# This is code adapted from `Rails::Engine#eager_load!` in
138-
# https://github.com/rails/rails/blob/d9e188dbab81b412f73dfb7763318d52f360af49/railties/lib/rails/engine.rb#L489-L495
139-
#
140-
# We can't use `Rails::Engine#eager_load!` directly because it will raise as soon as it encounters
141-
# an error, which is not what we want. We want to try to load as much as we can.
142-
engines.each do |engine|
143-
eager_load_paths(engine).each do |load_path|
144-
Dir.glob("#{load_path}/**/*.rb").sort.each do |file|
145-
require_dependency file
146-
end
147-
rescue ScriptError, StandardError
148-
nil
149-
end
150-
end
151-
end
152-
153-
sig { returns(T::Boolean) }
154-
def zeitwerk_mode?
155-
Rails.respond_to?(:autoloaders) &&
156-
Rails.autoloaders.respond_to?(:zeitwerk_enabled?) &&
157-
Rails.autoloaders.zeitwerk_enabled?
158-
end
159-
160-
sig { params(blk: T.proc.void).void }
161-
def with_rails_application(&blk)
162-
# Store the current Rails.application object so that we can restore it
163-
rails_application = T.unsafe(Rails.application)
164-
165-
# Create a new Rails::Application object, so that we can load the engines.
166-
# Some engines and the `Rails.autoloaders` call might expect `Rails.application`
167-
# to be set, so we need to create one here.
168-
unless rails_application
169-
Rails.application = Class.new(Rails::Application)
170-
end
171-
172-
blk.call
173-
ensure
174-
Rails.app_class = Rails.application = rails_application
175-
end
176-
177-
T::Sig::WithoutRuntime.sig { returns(T::Array[T.class_of(Rails::Engine)]) }
178-
def engines
179-
return [] unless defined?(Rails::Engine)
180-
181-
safe_require("active_support/core_ext/class/subclasses")
182-
183-
project_path = Bundler.default_gemfile.parent.expand_path
184-
# We can use `Class#descendants` here, since we know Rails is loaded
185-
Rails::Engine
186-
.descendants
187-
.reject(&:abstract_railtie?)
188-
.reject { |engine| gem_in_app_dir?(project_path, engine.config.root.to_path) }
189-
end
190-
191-
sig { params(path: String).void }
192-
def safe_require(path)
193-
require path
194-
rescue LoadError
195-
nil
196-
end
197-
19886
sig { void }
19987
def eager_load_rails_app
20088
application = Rails.application

spec/tapioca/cli/gem_spec.rb

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1286,6 +1286,19 @@ class Post
12861286
RB
12871287
end
12881288

1289+
@project.write!("config/application.rb", <<~RB)
1290+
module Tapioca
1291+
class Application < Rails::Application
1292+
config.load_defaults(#{ActiveSupport.gem_version.to_s[0..2]})
1293+
end
1294+
end
1295+
RB
1296+
1297+
@project.write!("config/environment.rb", <<~RB)
1298+
require_relative "application"
1299+
Rails.application.initialize!
1300+
RB
1301+
12891302
@project.require_real_gem("rails", ActiveSupport.gem_version.to_s)
12901303
@project.require_mock_gem(foo)
12911304
@project.bundle_install!
@@ -1343,6 +1356,11 @@ class Application < Rails::Application
13431356
end
13441357
RB
13451358

1359+
@project.write!("config/environment.rb", <<~RB)
1360+
require_relative "application"
1361+
Rails.application.initialize!
1362+
RB
1363+
13461364
response = @project.tapioca("gem turbo-rails")
13471365

13481366
assert_includes(response.out, "Compiled turbo-rails")
@@ -1374,6 +1392,11 @@ class Application < Rails::Application
13741392
end
13751393
RB
13761394

1395+
@project.write!("config/environment.rb", <<~RB)
1396+
require_relative "application"
1397+
Rails.application.initialize!
1398+
RB
1399+
13771400
response = @project.tapioca("gem turbo-rails")
13781401

13791402
assert_includes(response.out, "Compiled turbo-rails")

0 commit comments

Comments
 (0)