Skip to content

Commit 7773474

Browse files
committed
Add support for Rails::Engine
1 parent d3a0f67 commit 7773474

File tree

170 files changed

+1505
-117
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

170 files changed

+1505
-117
lines changed

Gemfile

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ end
1414

1515
group :test do
1616
gem "rspec-rails"
17+
gem "super_engine", path: 'spec/dummies/with-engine/dummy/engines/super_engine', require: false
1718
end
1819

1920
group :tools do

dry-rails.gemspec

+3
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,7 @@ Gem::Specification.new do |spec|
3636
spec.add_development_dependency "bundler"
3737
spec.add_development_dependency "rake"
3838
spec.add_development_dependency "rspec"
39+
40+
# our super engine used in specs
41+
spec.add_development_dependency "super_engine"
3942
end

lib/dry/rails.rb

+16-11
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# frozen_string_literal: true
22

33
require "dry/rails/railtie"
4+
require "dry/rails/engine"
5+
require "dry/rails/finalizer"
46
require "dry/rails/container"
57
require "dry/rails/components"
68

@@ -20,14 +22,24 @@ module Dry
2022
#
2123
# @api public
2224
module Rails
25+
extend Configurable
26+
# Set to true to turn off dry-system for main application
27+
# Meant to be used in setup where dry-system is only used within Rails::Engine(s)
28+
#
29+
# @api public
30+
setting :main_app_disabled, default: false
31+
32+
# This is being injected by main app Railtie
33+
# @api private
34+
setting :main_app_name
35+
2336
# Set container block that will be evaluated in the context of the container
2437
#
2538
# @return [self]
2639
#
2740
# @api public
2841
def self.container(&block)
29-
_container_blocks << block
30-
self
42+
Engine.container(config.main_app_name, &block)
3143
end
3244

3345
# Create a new container class
@@ -40,19 +52,12 @@ def self.container(&block)
4052
#
4153
# @api private
4254
def self.create_container(options = {})
43-
Class.new(Container) { config.update(options) }
55+
Engine.create_container(options)
4456
end
4557

4658
# @api private
4759
def self.evaluate_initializer(container)
48-
_container_blocks.each do |block|
49-
container.class_eval(&block)
50-
end
51-
end
52-
53-
# @api private
54-
def self._container_blocks
55-
@_container_blocks ||= []
60+
Engine.evaluate_initializer(config.main_app_name, container)
5661
end
5762
end
5863
end

lib/dry/rails/boot/safe_params.rb

+1-1
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

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

lib/dry/rails/finalizer.rb

+172
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
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+
# rubocop:disable Metrics/ParameterLists
11+
def initialize(
12+
railtie:,
13+
app_namespace:,
14+
root_path:,
15+
name: Dry::Rails.config.main_app_name,
16+
container_const_name: Dry::Rails::Container.container_constant,
17+
default_inflector: ActiveSupport::Inflector
18+
)
19+
@railtie = railtie
20+
@app_namespace = app_namespace
21+
@root_path = root_path
22+
@name = name
23+
@container_const_name = container_const_name
24+
@default_inflector = default_inflector
25+
end
26+
# rubocop:enable Metrics/ParameterLists
27+
28+
attr_reader :railtie,
29+
:root_path,
30+
:container_const_name
31+
32+
# Infer the default application namespace
33+
#
34+
# TODO: we had to rename namespace=>app_namespace because
35+
# Rake::DSL's Kernel#namespace *sometimes* breaks things.
36+
# Currently we are missing specs verifying that rake tasks work
37+
# correctly and those must be added!
38+
#
39+
# @return [Module]
40+
#
41+
# @api public
42+
attr_reader :app_namespace
43+
44+
# Code-reloading-aware finalization process
45+
#
46+
# This sets up `Container` and `Deps` constants, reloads them if this is in reloading mode,
47+
# and registers default components like the railtie itself or the inflector
48+
#
49+
# @api public
50+
#
51+
# rubocop:disable Metrics/AbcSize
52+
def finalize!
53+
stop_features if reloading?
54+
55+
container = Dry::Rails::Engine.create_container(
56+
root: root_path,
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+
# rubocop:disable Metrics/ParameterLists
151+
def self.new(
152+
railtie:,
153+
app_namespace:,
154+
root_path:,
155+
name: nil,
156+
container_const_name: Dry::Rails::Container.container_constant,
157+
default_inflector: ActiveSupport::Inflector
158+
)
159+
Dry::Rails::Finalizer.new(
160+
railtie: railtie,
161+
app_namespace: app_namespace,
162+
root_path: root_path,
163+
name: name || ::Dry::Rails::Finalizer.app_namespace_to_name(app_namespace),
164+
container_const_name: container_const_name,
165+
default_inflector: default_inflector
166+
)
167+
end
168+
# rubocop:enable Metrics/ParameterLists
169+
end
170+
end
171+
end
172+
end

0 commit comments

Comments
 (0)