From 3d5cecbe8dc36261172856c42dcf10fb56388f01 Mon Sep 17 00:00:00 2001 From: Neil Carvalho Date: Tue, 6 Oct 2026 16:19:27 +0200 Subject: [PATCH] Fold definition hierarchy into Factory Resolving a factory's callbacks, constructor and to_create through its parent chain built one anonymous subclass of DefinitionHierarchy per factory and relied on Ruby method lookup and super() to perform the inheritance. The chain was hard to follow, had to be rebuilt on every clone, and NullFactory existed partly to terminate it. Replace the class chain with three methods on Factory that fold over the parent: callbacks are the parent's callbacks followed by the definition's own, and the constructor and to_create are the definition's own or, failing that, the parent's. NullFactory ends the recursion with the global values from Internal, which is exactly what the root DefinitionHierarchy delegated to before. This deletes DefinitionHierarchy, the hierarchy_class, hierarchy_instance and build_hierarchy methods on Factory, and the hierarchy-related delegations on NullFactory. Behavior is unchanged: callbacks still run in global, ancestor, then own order, and to_create and initialize_with still resolve from the nearest definition outward. This is the first step of a sequence of refactors toward a compiled, non-cloning core. --- lib/factory_bot.rb | 1 - lib/factory_bot/definition_hierarchy.rb | 38 ------------------------- lib/factory_bot/factory.rb | 21 ++------------ lib/factory_bot/null_factory.rb | 15 +++++++--- spec/factory_bot/null_factory_spec.rb | 18 ++++++++---- 5 files changed, 26 insertions(+), 67 deletions(-) delete mode 100644 lib/factory_bot/definition_hierarchy.rb diff --git a/lib/factory_bot.rb b/lib/factory_bot.rb index 74a7fdc43..293bfa039 100644 --- a/lib/factory_bot.rb +++ b/lib/factory_bot.rb @@ -6,7 +6,6 @@ require "active_support/notifications" require "factory_bot/internal" -require "factory_bot/definition_hierarchy" require "factory_bot/configuration" require "factory_bot/errors" require "factory_bot/factory_runner" diff --git a/lib/factory_bot/definition_hierarchy.rb b/lib/factory_bot/definition_hierarchy.rb deleted file mode 100644 index 90e37d214..000000000 --- a/lib/factory_bot/definition_hierarchy.rb +++ /dev/null @@ -1,38 +0,0 @@ -module FactoryBot - class DefinitionHierarchy - delegate :callbacks, :constructor, :to_create, to: Internal - - def self.build_from_definition(definition) - build_to_create(&definition.to_create) - build_constructor(&definition.constructor) - add_callbacks definition.callbacks - end - - def self.add_callbacks(callbacks) - if callbacks.any? - define_method :callbacks do - super() + callbacks - end - end - end - private_class_method :add_callbacks - - def self.build_constructor(&block) - if block - define_method(:constructor) do - block - end - end - end - private_class_method :build_constructor - - def self.build_to_create(&block) - if block - define_method(:to_create) do - block - end - end - end - private_class_method :build_to_create - end -end diff --git a/lib/factory_bot/factory.rb b/lib/factory_bot/factory.rb index 3e34107c0..44c4c1504 100644 --- a/lib/factory_bot/factory.rb +++ b/lib/factory_bot/factory.rb @@ -93,7 +93,6 @@ def compile parent.compile inherit_parent_traits @definition.compile(build_class) - build_hierarchy @compiled = true end end @@ -121,28 +120,16 @@ def attributes end end - def hierarchy_class - @hierarchy_class ||= Class.new(parent.hierarchy_class) - end - - def hierarchy_instance - @hierarchy_instance ||= hierarchy_class.new - end - - def build_hierarchy - hierarchy_class.build_from_definition definition - end - def callbacks - hierarchy_instance.callbacks + parent.callbacks + definition.callbacks end def compiled_to_create - hierarchy_instance.to_create + definition.to_create || parent.compiled_to_create end def compiled_constructor - hierarchy_instance.constructor + definition.constructor || parent.compiled_constructor end private @@ -170,8 +157,6 @@ def initialize_copy(source) super @definition = @definition.clone @evaluator_class = nil - @hierarchy_class = nil - @hierarchy_instance = nil @compiled = false end end diff --git a/lib/factory_bot/null_factory.rb b/lib/factory_bot/null_factory.rb index d66d01443..1eae097df 100644 --- a/lib/factory_bot/null_factory.rb +++ b/lib/factory_bot/null_factory.rb @@ -7,8 +7,7 @@ def initialize @definition = Definition.new(:null_factory) end - delegate :defined_traits, :callbacks, :attributes, :constructor, - :to_create, to: :definition + delegate :defined_traits, :attributes, to: :definition def compile end @@ -20,8 +19,16 @@ def evaluator_class FactoryBot::Evaluator end - def hierarchy_class - FactoryBot::DefinitionHierarchy + def callbacks + Internal.callbacks + end + + def compiled_constructor + Internal.constructor + end + + def compiled_to_create + Internal.to_create end end end diff --git a/spec/factory_bot/null_factory_spec.rb b/spec/factory_bot/null_factory_spec.rb index c492fe54a..9f3764112 100644 --- a/spec/factory_bot/null_factory_spec.rb +++ b/spec/factory_bot/null_factory_spec.rb @@ -5,22 +5,28 @@ expect(null_factory).to delegate(:defined_traits).to(:definition) end - it "delegates callbacks to its definition" do + it "delegates attributes to its definition" do null_factory = FactoryBot::NullFactory.new - expect(null_factory).to delegate(:callbacks).to(:definition) + expect(null_factory).to delegate(:attributes).to(:definition) end - it "delegates attributes to its definition" do + it "has the global callbacks as its callbacks" do null_factory = FactoryBot::NullFactory.new - expect(null_factory).to delegate(:attributes).to(:definition) + expect(null_factory.callbacks).to eq FactoryBot::Internal.callbacks + end + + it "has the global constructor as its compiled constructor" do + null_factory = FactoryBot::NullFactory.new + + expect(null_factory.compiled_constructor).to eq FactoryBot::Internal.constructor end - it "delegates constructor to its definition" do + it "has the global to_create as its compiled to_create" do null_factory = FactoryBot::NullFactory.new - expect(null_factory).to delegate(:constructor).to(:definition) + expect(null_factory.compiled_to_create).to eq FactoryBot::Internal.to_create end it "has a nil value for its compile attribute" do