diff --git a/core/lib/rom/plugins/relation/registry_reader.rb b/core/lib/rom/plugins/relation/registry_reader.rb index 36e4ab82d..b5861cdd8 100644 --- a/core/lib/rom/plugins/relation/registry_reader.rb +++ b/core/lib/rom/plugins/relation/registry_reader.rb @@ -14,12 +14,8 @@ class RegistryReader < ::Module EMPTY_REGISTRY = RelationRegistry.build(EMPTY_HASH).freeze # @api private - attr_reader :relations - - # @api private - def initialize(relations:) - @relations = relations - define_readers! + def initialize(klass:, relation_readers_module:) + klass.include relation_readers_module end # @api private @@ -29,15 +25,6 @@ def included(klass) klass.option :__registry__, default: -> { EMPTY_REGISTRY } end - - private - - # @api private - def define_readers! - relations.each do |name| - define_method(name) { __registry__[name] } - end - end end end end diff --git a/core/lib/rom/setup/finalize/finalize_relations.rb b/core/lib/rom/setup/finalize/finalize_relations.rb index 1e2d88063..a402024be 100644 --- a/core/lib/rom/setup/finalize/finalize_relations.rb +++ b/core/lib/rom/setup/finalize/finalize_relations.rb @@ -9,6 +9,18 @@ class Finalize class FinalizeRelations attr_reader :notifications + module BuildRelationReaders + def self.build(relations) + Module.new do + relations.each do |name| + define_method(name) do + __registry__[name] + end + end + end + end + end + # Build relation registry of specified descendant classes # # This is used by the setup @@ -30,6 +42,7 @@ def initialize(gateways, relation_classes, notifications:, mappers: nil, plugins # @api private def run! relation_registry = RelationRegistry.new do |registry, relations| + relation_readers_module = BuildRelationReaders.build(relation_names) @relation_classes.each do |klass| unless klass.adapter raise MissingAdapterIdentifierError, @@ -43,7 +56,7 @@ def run! "Relation with name #{key.inspect} registered more than once" end - klass.use(:registry_reader, relations: relation_names) + klass.use(:registry_reader, klass: klass, relation_readers_module: relation_readers_module) notifications.trigger('configuration.relations.class.ready', relation: klass, adapter: klass.adapter) diff --git a/repository/lib/rom/repository/relation_reader.rb b/repository/lib/rom/repository/relation_reader.rb index dee25911f..c72a161f2 100644 --- a/repository/lib/rom/repository/relation_reader.rb +++ b/repository/lib/rom/repository/relation_reader.rb @@ -4,27 +4,50 @@ module ROM class Repository # @api private class RelationReader < Module + extend Dry::Core::ClassAttributes + # @api private attr_reader :klass # @api private attr_reader :relations + defines :relation_readers + + defines :mutex + mutex(Mutex.new) + + defines :relation_cache + relation_cache(Concurrent::Hash.new) + module InstanceMethods # @api private def set_relation(name) container .relations[name] - .with(auto_struct: auto_struct) - .struct_namespace(struct_namespace) + .with(auto_struct: auto_struct, struct_namespace: struct_namespace) + end + + def relation_reader(name, relation_cache) + key = [name, auto_struct, struct_namespace] + relation_cache[key] ||= set_relation(name) end end + # @api private + def mutex + ROM::Repository::RelationReader.mutex + end + # @api private def initialize(klass, relations) - @klass = klass @relations = relations - define_readers! + mutex.synchronize do + unless self.class.relation_readers + self.class.relation_readers(build_relation_readers(relations, self.class.relation_cache)) + end + end + klass.include self.class.relation_readers end # @api private @@ -33,13 +56,16 @@ def included(klass) klass.include(InstanceMethods) end + private # @api private - def define_readers! - relations.each do |name| - define_method(name) do - @relations[name] ||= set_relation(name) + def build_relation_readers(relations, relation_cache) + Module.new do + relations.each do |name| + define_method(name) do + relation_reader(name, relation_cache) + end end end end