Skip to content
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

Refactor setup/finalization #637

Merged
merged 9 commits into from
Jun 21, 2021
Merged
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
7 changes: 7 additions & 0 deletions lib/rom/command_registry.rb
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,13 @@ def set_mappers(mappers)
options[:mappers] = @mappers = mappers
end

# @api private
def add(key, command)
raise CommandAlreadyDefinedError, "+#{key}+ is already defined" if key?(key)

elements[key] = command
end

private

# Allow checking if a certain command is available using dot-notation
Expand Down
21 changes: 18 additions & 3 deletions lib/rom/compat/setup.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,25 @@ class Setup
# @api public
def auto_registration(directory, **options)
auto_registration = AutoRegistration.new(directory, inflector: inflector, **options)
auto_registration.relations.map { |r| register_relation(r) }
auto_registration.commands.map { |r| register_command(r) }
auto_registration.mappers.map { |r| register_mapper(r) }
auto_registration.relations.each { |r| register_relation(r) }
auto_registration.commands.each { |r| register_command(r) }
auto_registration.mappers.each { |r| register_mapper(r) }
self
end

# @api public
def relation_classes
components.relations.map(&:constant)
end

# @api public
def command_classes
components.commands.map(&:constant)
end

# @api public
def mapper_classes
components.mappers.map(&:constant)
end
end
end
78 changes: 78 additions & 0 deletions lib/rom/components.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# frozen_string_literal: true

require "rom/constants"
require "rom/components/schema"
require "rom/components/command"
require "rom/components/relation"
require "rom/components/mapper"

module ROM
# Setup objects collect component classes during setup/finalization process
#
# @api public
module Components
CORE_TYPES = %i[schemas relations commands mappers].freeze

HANDLERS = {
relations: Relation,
commands: Command,
mappers: Mapper,
schemas: Schema
}.freeze

# @api public
def components
@components ||= Registry.new
end

class Registry
# @api private
attr_reader :types

# @api private
attr_reader :store

# @api private
attr_reader :handlers

# @api private
def initialize(types: CORE_TYPES.dup, handlers: HANDLERS)
@types = types
@store = types.map { |type| [type, EMPTY_ARRAY.dup] }.to_h
@handlers = handlers
end

# @api private
def [](type)
store[type]
end

# @api private
def update(other)
store.each { |type, items| items.concat(other[type]) }
self
end

# @api private
def add(type, **options)
component = handlers.fetch(type).new(**options)

# TODO: this needs a nicer abstraction
# TODO: respond_to? is only needed because auto_register specs use POROs :(
update(component.constant.components) if component.constant.respond_to?(:components)

store[type] << component

component
end

CORE_TYPES.each do |type|
define_method(type) do |**opts|
all = self[type]
return all if opts.empty?
all.select { |el| opts.all? { |key, value| el.public_send(key).eql?(value) } }
end
end
end
end
end
43 changes: 43 additions & 0 deletions lib/rom/components/command.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# frozen_string_literal: true

require_relative "core"

module ROM
module Components
# @api public
class Command < Core
id :command

# Registry id
#
# @return [Symbol]
#
# @api public
def id
constant.register_as || constant.default_name
end

# @return [Symbol]
#
# @api public
def relation_id
constant.relation
end

# @api public
def build(relation:)
trigger(
"commands.class.before_build",
command: constant,
gateway: gateways[relation.gateway],
dataset: relation.dataset,
adapter: adapter
)

constant.extend_for_relation(relation) if constant.restrictable

constant.build(relation)
end
end
end
end
107 changes: 107 additions & 0 deletions lib/rom/components/core.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# frozen_string_literal: true

require "dry/effects"
require "dry/core/class_attributes"
require "dry/core/memoizable"

require "rom/constants"
require "rom/initializer"
require "rom/types"

module ROM
module Components
# Abstract component class
#
# @api public
class Core
extend Initializer
extend Dry::Core::ClassAttributes

include Dry::Core::Memoizable
include Dry::Effects.Reader(:configuration)

defines :id

# @!attribute [r] constant
# @return [Class] Component's target class
# @api public
option :constant, optional: true, type: Types.Interface(:new)
alias_method :relation, :constant

# This method is meant to return a run-time component instance
#
# @api public
def build(**)
raise NotImplementedError
end

# @api public
def trigger(event, payload)
notifications.trigger("configuration.#{event}", payload)
end

# @api public
def relations
configuration.relations
end

# @api public
def command_compiler
configuration.command_compiler
end

# @api public
def notifications
configuration.notifications
end

# @api public
def gateways
configuration.gateways
end

# @api public
def components
configuration.components
end

# @api public
def adapter
relation.adapter or raise(
MissingAdapterIdentifierError, "+#{constant}+ is missing the adapter identifier"
)
end

# @api public
def apply_plugins
plugins.each do |plugin|
plugin.apply_to(constant)
end
end

# @api public
memoize def plugins
configuration.plugins.select { |plugin| plugin.type == self.class.id }
end

# @api public
memoize def plugin_options
plugins.map(&:config).map(&:to_hash).reduce(:merge) || EMPTY_HASH
end

private

# @api private
def gateway
gateways.fetch(gateway_name) do
raise "+#{gateway_name.inspect}+ gateway not found for #{constant}"
end
end

# @api private
def gateway_name
relation.gateway
end
end
end
end
59 changes: 59 additions & 0 deletions lib/rom/components/mapper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# frozen_string_literal: true

require_relative "core"

module ROM
module Components
# @api public
class Mapper < Core
id :mapper

# @!attribute [r] id
# @return [Symbol] Registry key
# @api public
option :id, type: Types.Instance(Symbol), optional: true, reader: false

# @!attribute [r] base_relation
# @return [Symbol] The base relation identifier
# @api public
option :base_relation, type: Types.Instance(Symbol), optional: true, reader: false

# @!attribute [r] object
# @return [Class] Pre-initialized object that should be used instead of the constant
# @api public
option :object, optional: true

# Relation registry id
#
# @return [Symbol]
#
# @api public
def relation_id
options[:base_relation] || constant.base_relation
end

# Registry key
#
# @return [Symbol]
#
# @api public
def id
options[:id] || constant.id
end

# Default container key
#
# @return [String]
#
# @api public
def key
"mappers.#{relation_id}.#{id}"
end

# @api public
def build
object || constant.build
end
end
end
end
Loading