Skip to content

Commit c1f3fe2

Browse files
committed
Add support for Rails::Engine
Step 1: Make it (sort of) work Add bunch of methods back by delegating to finalizer Extract Dry::Rails::Engine to allow building separate Containers Fix CI error Fix specs 🤓 (FIXME: main-app name still hardcoded) Make main_app name configurable Extract Dry::Rails::Engine Do not load Main App container if its disabled Inject SafeParams everywhere Clean-up codes
1 parent 3f787c3 commit c1f3fe2

File tree

6 files changed

+262
-120
lines changed

6 files changed

+262
-120
lines changed

lib/dry/rails.rb

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
# frozen_string_literal: true
22

33
require "dry/rails/railtie"
4-
require "dry/rails/container"
5-
require "dry/rails/components"
4+
require "dry/rails/engine"
65

76
module Dry
87
# Initializer interface
@@ -18,14 +17,17 @@ module Dry
1817
#
1918
# @api public
2019
module Rails
20+
extend Dry::Configurable
21+
setting :main_app_name
22+
setting :main_app_enabled, default: true
23+
2124
# Set container block that will be evaluated in the context of the container
2225
#
2326
# @return [self]
2427
#
2528
# @api public
2629
def self.container(&block)
27-
_container_blocks << block
28-
self
30+
Engine.container(config.main_app_name, &block)
2931
end
3032

3133
# Create a new container class
@@ -38,19 +40,12 @@ def self.container(&block)
3840
#
3941
# @api private
4042
def self.create_container(options = {})
41-
Class.new(Container) { config.update(options) }
43+
Engine.create_container(options)
4244
end
4345

4446
# @api private
4547
def self.evaluate_initializer(container)
46-
_container_blocks.each do |block|
47-
container.class_eval(&block)
48-
end
49-
end
50-
51-
# @api private
52-
def self._container_blocks
53-
@_container_blocks ||= []
48+
Engine.evaluate_initializer(config.main_app_name, container)
5449
end
5550
end
5651
end

lib/dry/rails/boot/safe_params.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
end
77

88
start do
9-
ApplicationController.include(Dry::Rails::Features::SafeParams)
9+
ActionController::Base.include(Dry::Rails::Features::SafeParams)
1010

1111
if defined?(ActionController::API)
1212
ActionController::API.include(Dry::Rails::Features::SafeParams)

lib/dry/rails/engine.rb

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# frozen_string_literal: true
2+
3+
require "dry/rails/container"
4+
require "dry/rails/components"
5+
6+
module Dry
7+
module Rails
8+
module Engine
9+
# Set container block that will be evaluated in the context of the container
10+
#
11+
# @param name [Symbol]
12+
# @return [self]
13+
#
14+
# @api public
15+
def self.container(name, &block)
16+
_container_blocks[name] << block
17+
self
18+
end
19+
20+
# Create a new container class
21+
#
22+
# This is used during booting and reloading
23+
#
24+
# @param name [Symbol]
25+
# @param options [Hash] Container configuration settings
26+
#
27+
# @return [Class]
28+
#
29+
# @api private
30+
def self.create_container(options = {})
31+
Class.new(Container) { config.update(options) }
32+
end
33+
34+
# @api private
35+
def self.evaluate_initializer(name, container)
36+
_container_blocks[name].each do |block|
37+
container.class_eval(&block)
38+
end
39+
end
40+
41+
# @api private
42+
def self._container_blocks
43+
@_container_blocks ||= Hash.new { |h, k| h[k] = [] }
44+
end
45+
end
46+
end
47+
end

lib/dry/rails/finalizer.rb

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
# frozen_string_literal: true
2+
3+
module Dry
4+
module Rails
5+
class Finalizer
6+
def self.app_namespace_to_name(app_namespace)
7+
app_namespace.name.underscore.to_sym
8+
end
9+
10+
def initialize(
11+
railtie:,
12+
app_namespace:,
13+
root_path:,
14+
name: Dry::Rails.config.main_app_name,
15+
container_const_name: Dry::Rails::Container.container_constant,
16+
default_inflector: ActiveSupport::Inflector
17+
)
18+
@railtie = railtie
19+
@app_namespace = app_namespace
20+
@root_path = root_path
21+
@name = name
22+
@container_const_name = container_const_name
23+
@default_inflector = default_inflector
24+
end
25+
26+
attr_reader :railtie,
27+
:root_path,
28+
:container_const_name
29+
30+
# Infer the default application namespace
31+
#
32+
# TODO: we had to rename namespace=>app_namespace because
33+
# Rake::DSL's Kernel#namespace *sometimes* breaks things.
34+
# Currently we are missing specs verifying that rake tasks work
35+
# correctly and those must be added!
36+
#
37+
# @return [Module]
38+
#
39+
# @api public
40+
attr_reader :app_namespace
41+
42+
# Code-reloading-aware finalization process
43+
#
44+
# This sets up `Container` and `Deps` constants, reloads them if this is in reloading mode,
45+
# and registers default components like the railtie itself or the inflector
46+
#
47+
# @api public
48+
#
49+
# rubocop:disable Metrics/AbcSize
50+
def finalize!
51+
stop_features if reloading?
52+
53+
container = Dry::Rails::Engine.create_container(
54+
root: root_path,
55+
name: name,
56+
default_namespace: name.to_s,
57+
inflector: default_inflector,
58+
system_dir: root_path.join("config/system"),
59+
bootable_dirs: [root_path.join("config/system/boot")]
60+
)
61+
62+
# Enable :env plugin by default because it is a very common requirement
63+
container.use :env, inferrer: -> { ::Rails.env }
64+
65+
container.register(:railtie, railtie)
66+
container.register(:inflector, default_inflector)
67+
68+
# Remove previously defined constants, if any, so we don't end up with
69+
# unsused constants in app's namespace when a name change happens.
70+
remove_constant(container.auto_inject_constant)
71+
remove_constant(container.container_constant)
72+
73+
Dry::Rails::Engine.evaluate_initializer(name, container)
74+
75+
@container_const_name = container.container_constant
76+
77+
set_or_reload(container.container_constant, container)
78+
set_or_reload(container.auto_inject_constant, container.injector)
79+
80+
container.features.each do |feature|
81+
container.boot(feature, from: :rails)
82+
end
83+
84+
container.refresh_boot_files if reloading?
85+
86+
container.finalize!(freeze: !::Rails.env.test?)
87+
end
88+
# rubocop:enable Metrics/AbcSize
89+
90+
# Stops all configured features (bootable components)
91+
#
92+
# This is *crucial* when reloading code in development mode. Every bootable component
93+
# should be able to clear the runtime from any constants that it created in its `stop`
94+
# lifecycle step
95+
#
96+
# @api public
97+
def stop_features
98+
container.features.each do |feature|
99+
container.stop(feature) if container.booted?(feature)
100+
end
101+
end
102+
103+
# Exposes the container constant
104+
#
105+
# @return [Dry::Rails::Container]
106+
#
107+
# @api public
108+
def container
109+
app_namespace.const_get(container_const_name, false)
110+
end
111+
112+
# Return true if we're in code-reloading mode
113+
#
114+
# @api private
115+
def reloading?
116+
app_namespace.const_defined?(container_const_name, false)
117+
end
118+
119+
# Return the default system name
120+
#
121+
# In the dry-system world containers are explicitly named using symbols, so that you can
122+
# refer to them easily when ie importing one container into another
123+
#
124+
# @return [Symbol]
125+
#
126+
# @api private
127+
attr_reader :name
128+
129+
# Sets or reloads a constant within the application namespace
130+
#
131+
# @api private
132+
attr_reader :default_inflector
133+
134+
# @api private
135+
def set_or_reload(const_name, const)
136+
remove_constant(const_name)
137+
app_namespace.const_set(const_name, const)
138+
end
139+
140+
# @api private
141+
def remove_constant(const_name)
142+
if app_namespace.const_defined?(const_name, false)
143+
app_namespace.__send__(:remove_const, const_name)
144+
end
145+
end
146+
end
147+
148+
module Engine
149+
class Finalizer
150+
def self.new(
151+
railtie:,
152+
app_namespace:,
153+
root_path:,
154+
name: nil,
155+
container_const_name: Dry::Rails::Container.container_constant,
156+
default_inflector: ActiveSupport::Inflector
157+
)
158+
Dry::Rails::Finalizer.new(
159+
railtie: railtie,
160+
app_namespace: app_namespace,
161+
root_path: root_path,
162+
name: name || ::Dry::Rails::Finalizer.app_namespace_to_name(app_namespace),
163+
container_const_name: container_const_name,
164+
default_inflector: default_inflector
165+
)
166+
end
167+
end
168+
end
169+
end
170+
end

0 commit comments

Comments
 (0)