Class: Foundries::Base
- Inherits:
-
Object
- Object
- Foundries::Base
- Includes:
- FactoryBot::Syntax::Methods
- Defined in:
- lib/foundries/base.rb
Overview
Base is the orchestrator that composes multiple Blueprints into a single declarative builder for trees of related records.
Subclass Base and declare which blueprints it uses:
class MyFoundry < Foundries::Base
blueprint UserBlueprint
blueprint ProjectBlueprint
# Optional: additional collections beyond what blueprints declare
collection :tags
end
MyFoundry.new do
user "Alice" do
project "Widget" do
# ...
end
end
end
Class Attribute Summary collapse
-
._active_similarity_recorder ⇒ Object
Returns the value of attribute _active_similarity_recorder.
Instance Attribute Summary collapse
-
#current ⇒ Object
Returns the value of attribute current.
Class Method Summary collapse
-
.aliases(mapping = nil) ⇒ Object
Declare shorthand aliases for blueprint methods.
-
.blueprint(klass) ⇒ Object
Register a blueprint class with this foundry.
-
.blueprint_collection_names ⇒ Object
Collection names derived from blueprint declarations.
-
.blueprint_registry ⇒ Object
All registered blueprint classes and their handled methods.
-
.collection(*names) ⇒ Object
Declare additional collection names beyond those from blueprints.
-
.collection_accessors ⇒ Object
All collection accessor names (e.g. "users_collection").
-
.delegations ⇒ Object
Methods delegated from this foundry to its blueprint instances.
- .extra_collections ⇒ Object
- .inherited(subclass) ⇒ Object
-
.preset(name, &block) ⇒ Object
Define presets — named class methods that build a preconfigured foundry.
- .with_similarity_recording(preset_name, recording) ⇒ Object
Instance Method Summary collapse
-
#ancestors_for(type, path = nil, path_arr: nil, &block) ⇒ Object
Build a hierarchy from a path string or array.
-
#blueprint_for(method_name) ⇒ Object
Find the blueprint instance that handles a given method.
- #execute_and_restore_state ⇒ Object
-
#from(objects, &block) ⇒ Object
Build within the context of existing objects.
-
#initialize(&block) ⇒ Base
constructor
A new instance of Base.
- #load_existing_objects(objects) ⇒ Object
- #load_state(object) ⇒ Object (also: #update_current)
-
#reopen(&block) ⇒ Object
Reopen the foundry to add more records.
Constructor Details
#initialize(&block) ⇒ Base
Returns a new instance of Base.
143 144 145 146 147 148 149 150 151 152 |
# File 'lib/foundries/base.rb', line 143 def initialize(&block) @_similarity_recorder = self.class._active_similarity_recorder initialize_collections instantiate_blueprints @current = OpenStruct.new(resource: self) setup instance_exec(&block) if block teardown @current.resource = nil end |
Class Attribute Details
._active_similarity_recorder ⇒ Object
Returns the value of attribute _active_similarity_recorder.
31 32 33 |
# File 'lib/foundries/base.rb', line 31 def _active_similarity_recorder @_active_similarity_recorder end |
Instance Attribute Details
#current ⇒ Object
Returns the value of attribute current.
154 155 156 |
# File 'lib/foundries/base.rb', line 154 def current @current end |
Class Method Details
.aliases(mapping = nil) ⇒ Object
Declare shorthand aliases for blueprint methods.
aliases member: :enrollment, hva_mod: :skilled_mod
Resolves aliases during method_missing before blueprint delegation.
68 69 70 71 72 |
# File 'lib/foundries/base.rb', line 68 def aliases(mapping = nil) return @aliases || {} unless mapping @aliases = (@aliases || {}).merge(mapping) end |
.blueprint(klass) ⇒ Object
Register a blueprint class with this foundry.
34 35 36 |
# File 'lib/foundries/base.rb', line 34 def blueprint(klass) blueprint_registry[klass] = klass.handled_methods end |
.blueprint_collection_names ⇒ Object
Collection names derived from blueprint declarations.
58 59 60 |
# File 'lib/foundries/base.rb', line 58 def blueprint_collection_names blueprint_registry.keys.filter_map { |klass| klass.collection_name&.to_s } end |
.blueprint_registry ⇒ Object
All registered blueprint classes and their handled methods.
39 40 41 |
# File 'lib/foundries/base.rb', line 39 def blueprint_registry @blueprint_registry ||= {} end |
.collection(*names) ⇒ Object
Declare additional collection names beyond those from blueprints.
44 45 46 |
# File 'lib/foundries/base.rb', line 44 def collection(*names) extra_collections.concat(names.map(&:to_s)) end |
.collection_accessors ⇒ Object
All collection accessor names (e.g. "users_collection").
53 54 55 |
# File 'lib/foundries/base.rb', line 53 def collection_accessors (blueprint_collection_names + extra_collections).map { |name| "#{name}_collection" } end |
.delegations ⇒ Object
Methods delegated from this foundry to its blueprint instances.
75 76 77 |
# File 'lib/foundries/base.rb', line 75 def delegations blueprint_registry.select { |_, methods| methods.any? } end |
.extra_collections ⇒ Object
48 49 50 |
# File 'lib/foundries/base.rb', line 48 def extra_collections @extra_collections ||= [] end |
.inherited(subclass) ⇒ Object
134 135 136 137 138 139 140 |
# File 'lib/foundries/base.rb', line 134 def inherited(subclass) super # Copy parent registries so subclasses inherit blueprints subclass.instance_variable_set(:@blueprint_registry, blueprint_registry.dup) subclass.instance_variable_set(:@extra_collections, extra_collections.dup) subclass.instance_variable_set(:@aliases, aliases.dup) end |
.preset(name, &block) ⇒ Object
90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 |
# File 'lib/foundries/base.rb', line 90 def preset(name, &block) define_singleton_method(name) do if defined?(Foundries::Snapshot) && Foundries::Snapshot.enabled? store = Foundries::Snapshot::Store.new(name) if store.cached? store.restore next new # hollow — no block, data already in DB end store.record_empty_tables foundry = with_similarity_recording(name, Similarity.enabled?) do new(&block) end store.capture foundry else with_similarity_recording(name, Similarity.enabled?) do new(&block) end end end end |
.with_similarity_recording(preset_name, recording) ⇒ Object
114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 |
# File 'lib/foundries/base.rb', line 114 def with_similarity_recording(preset_name, recording) if recording self._active_similarity_recorder = Similarity::Recorder.new foundry = yield tree = _active_similarity_recorder.normalized_tree self._active_similarity_recorder = nil key = "#{name}.#{preset_name}" warnings = Similarity::Comparator.compare(key, tree, Similarity.registry) warnings.each do |w| next unless Similarity.warned_pairs.add?(w[:pair]) warn w[:message] end Similarity.registry[key] = tree foundry else yield end end |
Instance Method Details
#ancestors_for(type, path = nil, path_arr: nil, &block) ⇒ Object
Build a hierarchy from a path string or array.
Finds the blueprint that handles type and calls
ancestors on it to recursively create the chain.
ancestors_for :task, "org/block/template/phase/mod/event" do
task "my_task"
end
211 212 213 214 |
# File 'lib/foundries/base.rb', line 211 def ancestors_for(type, path = nil, path_arr: nil, &block) path_arr ||= path.split("/") blueprint_for(type).ancestors(path_arr, &block) end |
#blueprint_for(method_name) ⇒ Object
Find the blueprint instance that handles a given method.
217 218 219 220 221 222 223 224 225 226 |
# File 'lib/foundries/base.rb', line 217 def blueprint_for(method_name) resolved = self.class.aliases[method_name] || method_name self.class.delegations.each do |klass, methods| if methods.include?(resolved) ivar = :"@#{ivar_name_for(klass)}" return instance_variable_get(ivar) end end raise "No blueprint handles :#{method_name}" end |
#execute_and_restore_state ⇒ Object
188 189 190 191 |
# File 'lib/foundries/base.rb', line 188 def execute_and_restore_state initial_state = @current.dup yield.tap { @current = initial_state } end |
#from(objects, &block) ⇒ Object
Build within the context of existing objects.
166 167 168 169 170 171 172 |
# File 'lib/foundries/base.rb', line 166 def from(objects, &block) execute_and_restore_state do load_existing_objects(objects) instance_exec(&block) if block teardown end end |
#load_existing_objects(objects) ⇒ Object
174 175 176 177 178 179 180 181 182 183 184 185 186 |
# File 'lib/foundries/base.rb', line 174 def load_existing_objects(objects) return if objects.nil? || (objects.respond_to?(:empty?) && objects.empty?) Array(objects).each do |object| load_state(object) klass_name = object.class.name blueprint_class = find_blueprint_class_for(klass_name) next unless blueprint_class blueprint_class.load_state_from(object, self) end end |
#load_state(object) ⇒ Object Also known as: update_current
193 194 195 196 197 198 199 200 |
# File 'lib/foundries/base.rb', line 193 def load_state(object) klass_name = object.class.name.underscore.tr("/", "_") current.send(:"#{klass_name}=", object) collection_name = "#{klass_name.pluralize}_collection" return unless respond_to?(collection_name) send(collection_name) << object end |
#reopen(&block) ⇒ Object
Reopen the foundry to add more records.
157 158 159 160 161 162 163 |
# File 'lib/foundries/base.rb', line 157 def reopen(&block) @current = OpenStruct.new(resource: self) instance_exec(&block) if block teardown @current.resource = nil self end |