Skip to content

Commit

Permalink
Add support for Rails::Engine
Browse files Browse the repository at this point in the history
  • Loading branch information
zlw committed Dec 23, 2021
1 parent 4fdaec5 commit 2a0259b
Show file tree
Hide file tree
Showing 170 changed files with 1,505 additions and 117 deletions.
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ end

group :test do
gem "rspec-rails"
gem "super_engine", path: "spec/dummies/with-engine/dummy/engines/super_engine", require: false
end

group :tools do
Expand Down
3 changes: 3 additions & 0 deletions dry-rails.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,7 @@ Gem::Specification.new do |spec|
spec.add_development_dependency "bundler"
spec.add_development_dependency "rake"
spec.add_development_dependency "rspec"

# our super engine used in specs
spec.add_development_dependency "super_engine"
end
27 changes: 16 additions & 11 deletions lib/dry/rails.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# frozen_string_literal: true

require "dry/rails/railtie"
require "dry/rails/engine"
require "dry/rails/finalizer"
require "dry/rails/container"
require "dry/rails/components"

Expand All @@ -20,14 +22,24 @@ module Dry
#
# @api public
module Rails
extend Configurable
# Set to true to turn off dry-system for main application
# Meant to be used in setup where dry-system is only used within Rails::Engine(s)
#
# @api public
setting :main_app_disabled, default: false

# This is being injected by main app Railtie
# @api private
setting :main_app_name

# Set container block that will be evaluated in the context of the container
#
# @return [self]
#
# @api public
def self.container(&block)
_container_blocks << block
self
Engine.container(config.main_app_name, &block)
end

# Create a new container class
Expand All @@ -40,19 +52,12 @@ def self.container(&block)
#
# @api private
def self.create_container(options = {})
Class.new(Container) { config.update(options) }
Engine.create_container(options)
end

# @api private
def self.evaluate_initializer(container)
_container_blocks.each do |block|
container.class_eval(&block)
end
end

# @api private
def self._container_blocks
@_container_blocks ||= []
Engine.evaluate_initializer(config.main_app_name, container)
end
end
end
2 changes: 1 addition & 1 deletion lib/dry/rails/boot/safe_params.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
end

start do
ApplicationController.include(Dry::Rails::Features::SafeParams)
ActionController::Base.include(Dry::Rails::Features::SafeParams)

if defined?(ActionController::API)
ActionController::API.include(Dry::Rails::Features::SafeParams)
Expand Down
44 changes: 44 additions & 0 deletions lib/dry/rails/engine.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# frozen_string_literal: true

module Dry
module Rails
module Engine
# Set container block that will be evaluated in the context of the container
#
# @param name [Symbol]
# @return [self]
#
# @api public
def self.container(name, &block)
_container_blocks[name] << block
self
end

# Create a new container class
#
# This is used during booting and reloading
#
# @param name [Symbol]
# @param options [Hash] Container configuration settings
#
# @return [Class]
#
# @api private
def self.create_container(options = {})
Class.new(Container) { config.update(options) }
end

# @api private
def self.evaluate_initializer(name, container)
_container_blocks[name].each do |block|
container.class_eval(&block)
end
end

# @api private
def self._container_blocks
@_container_blocks ||= Hash.new { |h, k| h[k] = [] }
end
end
end
end
172 changes: 172 additions & 0 deletions lib/dry/rails/finalizer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
# frozen_string_literal: true

module Dry
module Rails
class Finalizer
def self.app_namespace_to_name(app_namespace)
app_namespace.name.underscore.to_sym
end

# rubocop:disable Metrics/ParameterLists
def initialize(
railtie:,
app_namespace:,
root_path:,
name: Dry::Rails.config.main_app_name,
container_const_name: Dry::Rails::Container.container_constant,
default_inflector: ActiveSupport::Inflector
)
@railtie = railtie
@app_namespace = app_namespace
@root_path = root_path
@name = name
@container_const_name = container_const_name
@default_inflector = default_inflector
end
# rubocop:enable Metrics/ParameterLists

attr_reader :railtie,
:root_path,
:container_const_name

# Infer the default application namespace
#
# TODO: we had to rename namespace=>app_namespace because
# Rake::DSL's Kernel#namespace *sometimes* breaks things.
# Currently we are missing specs verifying that rake tasks work
# correctly and those must be added!
#
# @return [Module]
#
# @api public
attr_reader :app_namespace

# Code-reloading-aware finalization process
#
# This sets up `Container` and `Deps` constants, reloads them if this is in reloading mode,
# and registers default components like the railtie itself or the inflector
#
# @api public
#
# rubocop:disable Metrics/AbcSize
def finalize!
stop_features if reloading?

container = Dry::Rails::Engine.create_container(
root: root_path,
inflector: default_inflector,
system_dir: root_path.join("config/system"),
bootable_dirs: [root_path.join("config/system/boot")]
)

# Enable :env plugin by default because it is a very common requirement
container.use :env, inferrer: -> { ::Rails.env }

container.register(:railtie, railtie)
container.register(:inflector, default_inflector)

# Remove previously defined constants, if any, so we don't end up with
# unsused constants in app's namespace when a name change happens.
remove_constant(container.auto_inject_constant)
remove_constant(container.container_constant)

Dry::Rails::Engine.evaluate_initializer(name, container)

@container_const_name = container.container_constant

set_or_reload(container.container_constant, container)
set_or_reload(container.auto_inject_constant, container.injector)

container.features.each do |feature|
container.boot(feature, from: :rails)
end

container.refresh_boot_files if reloading?

container.finalize!(freeze: !::Rails.env.test?)
end
# rubocop:enable Metrics/AbcSize

# Stops all configured features (bootable components)
#
# This is *crucial* when reloading code in development mode. Every bootable component
# should be able to clear the runtime from any constants that it created in its `stop`
# lifecycle step
#
# @api public
def stop_features
container.features.each do |feature|
container.stop(feature) if container.booted?(feature)
end
end

# Exposes the container constant
#
# @return [Dry::Rails::Container]
#
# @api public
def container
app_namespace.const_get(container_const_name, false)
end

# Return true if we're in code-reloading mode
#
# @api private
def reloading?
app_namespace.const_defined?(container_const_name, false)
end

# Return the default system name
#
# In the dry-system world containers are explicitly named using symbols, so that you can
# refer to them easily when ie importing one container into another
#
# @return [Symbol]
#
# @api private
attr_reader :name

# Sets or reloads a constant within the application namespace
#
# @api private
attr_reader :default_inflector

# @api private
def set_or_reload(const_name, const)
remove_constant(const_name)
app_namespace.const_set(const_name, const)
end

# @api private
def remove_constant(const_name)
if app_namespace.const_defined?(const_name, false)
app_namespace.__send__(:remove_const, const_name)
end
end
end

module Engine
class Finalizer
# rubocop:disable Metrics/ParameterLists
def self.new(
railtie:,
app_namespace:,
root_path:,
name: nil,
container_const_name: Dry::Rails::Container.container_constant,
default_inflector: ActiveSupport::Inflector
)
Dry::Rails::Finalizer.new(
railtie: railtie,
app_namespace: app_namespace,
root_path: root_path,
name: name || ::Dry::Rails::Finalizer.app_namespace_to_name(app_namespace),
container_const_name: container_const_name,
default_inflector: default_inflector
)
end
# rubocop:enable Metrics/ParameterLists
end
end
end
end
Loading

0 comments on commit 2a0259b

Please sign in to comment.