Skip to content

[WIP] Add support for Rails::Engine #47

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 8 additions & 13 deletions lib/dry/rails.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
# frozen_string_literal: true

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

module Dry
# Initializer interface
Expand All @@ -18,14 +17,17 @@ module Dry
#
# @api public
module Rails
extend Dry::Configurable
setting :main_app_name
setting :main_app_enabled, default: true

# 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 @@ -38,19 +40,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
8 changes: 4 additions & 4 deletions lib/dry/rails/container.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class Container < System::Container
#
# @api public
# @!scope class
setting :features, %i[application_contract safe_params controller_helpers], reader: true
setting :features, default: %i[application_contract safe_params controller_helpers], reader: true

# @overload config.auto_register_paths=(paths)
# Set an array of path/block pairs for auto-registration
Expand All @@ -39,7 +39,7 @@ class Container < System::Container
#
# @api public
# @!scope class
setting :auto_register_paths, [].freeze, reader: true
setting :auto_register_paths, default: [].freeze, reader: true

# @overload config.auto_inject_constant=(auto_inject_constant)
# Set a custom import constant name
Expand All @@ -48,7 +48,7 @@ class Container < System::Container
#
# @api public
# @!scope class
setting :auto_inject_constant, "Deps", reader: true
setting :auto_inject_constant, default: "Deps", reader: true

# @overload config.container_constant=(container_constant)
# Set a custom container constant
Expand All @@ -57,7 +57,7 @@ class Container < System::Container
#
# @api public
# @!scope class
setting :container_constant, "Container", reader: true
setting :container_constant, default: "Container", reader: true

# @!endgroup

Expand Down
47 changes: 47 additions & 0 deletions lib/dry/rails/engine.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# frozen_string_literal: true

require "dry/rails/container"
require "dry/rails/components"

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
170 changes: 170 additions & 0 deletions lib/dry/rails/finalizer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
# 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

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

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,
name: name,
default_namespace: name.to_s,
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
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
end
end
end
end
Loading