Skip to content

Commit

Permalink
Merge pull request #642 from rom-rb/lazy-mapper-registry
Browse files Browse the repository at this point in the history
[changelog]

changed: "Mappers are now lazy-resolved at runtime which speeds up setup and finalization (via #642) (@solnic)"
  • Loading branch information
solnic authored Jun 28, 2021
2 parents 4be1d31 + 2f1558f commit 90169c0
Show file tree
Hide file tree
Showing 11 changed files with 87 additions and 67 deletions.
33 changes: 5 additions & 28 deletions lib/rom/command_registry.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,6 @@ class CommandRegistry < Registry
# @return [Relation::Name] The name of a relation
option :relation_name

# @!attribute [r] resolvers
# @return [Hash<Symbol=>Proc>] Item resolvers
option :resolvers, optional: true, default: -> { EMPTY_HASH.dup }

# @!attribute [r] mappers
# @return [MapperRegistry] Optional mapper registry
option :mappers, optional: true
Expand All @@ -45,6 +41,11 @@ def self.element_not_found_error
CommandNotFoundError
end

# @api private
def self.element_already_defined_error
CommandAlreadyDefinedError
end

# Return a command from the registry
#
# If mapper is set command will be turned into a composite command with
Expand Down Expand Up @@ -90,14 +91,6 @@ def fetch(*args)
end
alias_method :[], :fetch

# Custom commands are stored during setup as lazy-loadable
# This method handles resolving a custom command at runtime
#
# @api private
def resolve(key)
add(key, resolvers.delete(key).())
end

# Specify a mapper that should be used for commands from this registry
#
# @example
Expand All @@ -112,21 +105,5 @@ def resolve(key)
def map_with(mapper_name)
with(mapper: mappers[mapper_name])
end

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

if command
elements[key] = command
else
resolvers[key] = block
end
end

# @api private
def key?(key)
elements.key?(key) || resolvers.key?(key)
end
end
end
6 changes: 2 additions & 4 deletions lib/rom/components/relation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,10 @@ def build
#
# @api private
memoize def mappers
mappers = components.mappers(relation_id: id)

registry = constant.mapper_registry(cache: configuration.cache)

mappers.each do |mapper|
registry.add(mapper.id, mapper.build)
components.mappers(relation_id: id).each do |component|
registry.add(component.id, &component)
end

registry
Expand Down
2 changes: 2 additions & 0 deletions lib/rom/constants.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ def set_message(key, registry)
end
end

RelationMissingError = Class.new(ElementNotFoundError)

MapperMissingError = Class.new(ElementNotFoundError)

CommandNotFoundError = Class.new(ElementNotFoundError) do
Expand Down
13 changes: 10 additions & 3 deletions lib/rom/container.rb
Original file line number Diff line number Diff line change
Expand Up @@ -103,12 +103,12 @@ class Container
include Dry::Equalizer(:gateways, :relations, :mappers, :commands)

# @api private
def self.new(gateways, relations, mappers, commands)
def self.new(gateways, relations)
super().tap do |container|
container.register(:gateways, gateways)
container.register(:mappers, mappers)
container.register(:commands, commands)
container.register(:relations, relations)
container.register(:mappers, memoize: true) { relations.to_mapper_registry }
container.register(:commands, memoize: true) { relations.to_command_registry }
end
end

Expand Down Expand Up @@ -161,5 +161,12 @@ def commands
def disconnect
gateways.each_value(&:disconnect)
end

# @api private
def finalize
mappers.finalize
commands.finalize
self
end
end
end
25 changes: 14 additions & 11 deletions lib/rom/mapper_registry.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,27 +12,30 @@ def self.element_not_found_error
MapperMissingError
end

# @api private
def self.element_already_defined_error
MapperAlreadyDefinedError
end

# @!attribute [r] compiler
# @return [MapperCompiler] A mapper compiler instance
option :compiler, default: lambda {
MapperCompiler.new(EMPTY_HASH, cache: cache)
}
option :compiler, default: -> { MapperCompiler.new(EMPTY_HASH, cache: cache) }

# @see Registry
# @api public
def [](*args)
if args[0].is_a?(Symbol)
key = args.first

if resolvers.key?(key)
resolve(key)
return fetch(*args)
end

if key.is_a?(Symbol)
super
else
cache.fetch_or_store(args.hash) { compiler.(*args) }
end
end

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

elements[key] = mapper
end
end
end
37 changes: 35 additions & 2 deletions lib/rom/registry.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ class Registry
# @return [Hash] Internal hash for storing registry objects
param :elements

# @!attribute [r] resolvers
# @return [Hash<Symbol=>Proc>] Item resolvers
option :resolvers, optional: true, default: -> { EMPTY_HASH.dup }

# @!attribute [r] cache
# @return [Cache] local cache instance
option :cache, default: -> { Cache.new }
Expand Down Expand Up @@ -101,8 +105,14 @@ def values
end

# @api private
def key?(name)
!name.nil? && elements.key?(name.to_sym)
def add(key, element = nil, &block)
raise self.class.element_already_defined_error, "+#{key}+ is already defined" if key?(key)

if element
elements[key] = element
else
resolvers[key] = block
end
end

# @api private
Expand All @@ -119,6 +129,29 @@ def fetch(key)
end
alias_method :[], :fetch

# This method handles resolving components at run-time
#
# @api private
def resolve(key)
add(key, resolvers.delete(key).())
end

# @api private
def finalize
resolvers.each_key do |key|
resolve(key)
end

each_value { |element| element.finalize if element.respond_to?(:finalize) }

self
end

# @api private
def key?(key)
!key.nil? && (elements.key?(key) || resolvers.key?(key))
end

# @api private
def type
self.class.name
Expand Down
18 changes: 11 additions & 7 deletions lib/rom/relation_registry.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
# frozen_string_literal: true

require "rom/constants"
require "rom/registry"

module ROM
# @api private
class RelationRegistry < Registry
# @api private
def self.element_not_found_error
RelationMissingError
end

# @api private
def self.element_already_defined_error
RelationAlreadyDefinedError
end

# @api private
def initialize(elements = {}, **options)
super
Expand All @@ -20,12 +31,5 @@ def to_mapper_registry
def to_command_registry
Registry.new(elements.map { |key, relation| [key, relation.commands] }.to_h)
end

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

elements[key] = relation
end
end
end
8 changes: 1 addition & 7 deletions lib/rom/setup/finalize.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,7 @@ def initialize(configuration)
def run!
load_relations

container = Container.new(
configuration.gateways,
relations,
relations.to_mapper_registry,
relations.to_command_registry
)

container = Container.new(configuration.gateways, relations)
container.freeze
container
end
Expand Down
4 changes: 2 additions & 2 deletions spec/integration/multi_env_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@

let(:container) do
{
one: ROM.container(configuration[:one]),
two: ROM.container(configuration[:two])
one: ROM.container(configuration[:one]).finalize,
two: ROM.container(configuration[:two]).finalize
}
end

Expand Down
6 changes: 4 additions & 2 deletions spec/integration/relations/reading_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ def sorted
end
end

container
container.mappers[:users][:users]
container.mappers[:users][:with_tasks]

Test::User.send(:include, Dry::Equalizer(:name, :email))
Test::UserWithTasks.send(:include, Dry::Equalizer(:name, :email, :tasks))
Expand Down Expand Up @@ -100,7 +101,8 @@ def sorted
end
end

container
container.mappers[:users][:users]
container.mappers[:users][:with_task]

Test::User.send(:include, Dry::Equalizer(:name, :email))
Test::UserWithTask.send(:include, Dry::Equalizer(:name, :email, :task))
Expand Down
2 changes: 1 addition & 1 deletion spec/shared/container.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

RSpec.shared_context "container" do
let(:container) { ROM.container(configuration) }
let(:container) { ROM.container(configuration).finalize }
let(:configuration) { ROM::Configuration.new(:memory) }
let(:gateway) { configuration.gateways[:default] }
let(:users_relation) { container.relations[:users] }
Expand Down

0 comments on commit 90169c0

Please sign in to comment.