foundries 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cf089585b858ec4b4fb09964e7b8e17d1c2bcc3dcc1236a5a1414afb49810c11
4
- data.tar.gz: 9919d0537dd62b84c9e3b49ccfce1de19778998203aca7e5bff36292e790fb8c
3
+ metadata.gz: d9bda37a762c9a055ca42c5faa685e30a6201fbeccaa5bbb7adffa0ccaef3386
4
+ data.tar.gz: 4fe49113055d53bb2e61df790fa037c849cff8d7b380011e6fd298fab0ec68af
5
5
  SHA512:
6
- metadata.gz: 8a1a3e44f94106f6a4b8ac0702c9ffff6d55e54d57cd4c7e19d8c34eb8ee74cebadadf35267a49269c5e638d2fae9421de3e47af9577377b9d42f9fc21cdd38d
7
- data.tar.gz: f3045a5705fd20d79d0ac77c29b4f104f6c6d09b690e39bb94d93c3f78df8458f883da25ec5efe68aced414d11cb1b879db6b03c205befa308dbf9f4aba1bf75
6
+ metadata.gz: 6e2b1f8a7fbc9107f684883ae9435c8c6cfc004698f7a5316ec47fb3531b9d100be9b12b55a4c097cefbb48e871c653717dbdf4ed5733dcbaf1032f5fed6a8e0
7
+ data.tar.gz: f25ae1307f8f675d4b6d617b13b9102639e49f29bd6f80f90145cc68023725a36eb3169207107c9af6cb1078188f8a0d104c6e591959fc1ee48d992563b9e827
data/CHANGELOG.md CHANGED
@@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](http://keepachangelog.com/)
6
6
  and this project adheres to [Semantic Versioning](http://semver.org/).
7
7
 
8
+ ## [0.1.2] - 2026-03-12
9
+
10
+ ### Added
11
+
12
+ - aliases DSL on Base for shorthand method names (377632f)
13
+ - lookup_order DSL on Blueprint for ancestor traversal (377632f)
14
+ - find_or_create pattern on Blueprint (377632f)
15
+ - Tests for inherited registries, parent-scoped find, collection_find_by kwargs, find_or_create, ascending_find, and parent_present? (2d92945)
16
+
17
+ ### Changed
18
+
19
+ - Collections initialize before blueprints (377632f)
20
+ - Fix find parent scoping and collection_find_by kwargs handling (3ebfc3e)
21
+
8
22
  ## [0.1.1] - 2026-03-06
9
23
 
10
24
  ### Added
@@ -22,5 +36,3 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
22
36
 
23
37
  - Parentless blueprints no longer raise NameError when find calls same_parent? (cfb348d)
24
38
  - Snapshot-cached presets no longer produce false similarity warnings (a5bdf25)
25
-
26
- ## [0.1.0] - 2026-02-25
@@ -59,6 +59,18 @@ module Foundries
59
59
  blueprint_registry.keys.filter_map { |klass| klass.collection_name&.to_s }
60
60
  end
61
61
 
62
+ # Declare shorthand aliases for blueprint methods.
63
+ #
64
+ # aliases member: :enrollment, hva_mod: :skilled_mod
65
+ #
66
+ # Resolves aliases during method_missing before blueprint
67
+ # delegation.
68
+ def aliases(mapping = nil)
69
+ return @aliases || {} unless mapping
70
+
71
+ @aliases = (@aliases || {}).merge(mapping)
72
+ end
73
+
62
74
  # Methods delegated from this foundry to its blueprint instances.
63
75
  def delegations
64
76
  blueprint_registry.select { |_, methods| methods.any? }
@@ -121,16 +133,17 @@ module Foundries
121
133
 
122
134
  def inherited(subclass)
123
135
  super
124
- # Ensure subclasses get their own registries
125
- subclass.instance_variable_set(:@blueprint_registry, {})
126
- subclass.instance_variable_set(:@extra_collections, [])
136
+ # Copy parent registries so subclasses inherit blueprints
137
+ subclass.instance_variable_set(:@blueprint_registry, blueprint_registry.dup)
138
+ subclass.instance_variable_set(:@extra_collections, extra_collections.dup)
139
+ subclass.instance_variable_set(:@aliases, aliases.dup)
127
140
  end
128
141
  end
129
142
 
130
143
  def initialize(&block)
131
144
  @_similarity_recorder = self.class._active_similarity_recorder
132
- instantiate_blueprints
133
145
  initialize_collections
146
+ instantiate_blueprints
134
147
  @current = OpenStruct.new(resource: self)
135
148
  setup
136
149
  instance_exec(&block) if block
@@ -213,6 +226,13 @@ module Foundries
213
226
  end
214
227
  end
215
228
  end
229
+
230
+ # Set up alias methods that delegate to existing blueprint methods
231
+ self.class.aliases.each do |alias_name, target_name|
232
+ define_singleton_method(alias_name) do |*args, **kwargs, &block|
233
+ send(target_name, *args, **kwargs, &block)
234
+ end
235
+ end
216
236
  end
217
237
 
218
238
  def initialize_collections
@@ -103,6 +103,19 @@ module Foundries
103
103
  end
104
104
  end
105
105
 
106
+ # Declare ancestor traversal order for ascending_find.
107
+ #
108
+ # lookup_order %i[evented_mod phase cohort]
109
+ #
110
+ # When no parent is present, ascending_find walks these
111
+ # ancestor types on `current`, checking collection_name
112
+ # on each.
113
+ def lookup_order(ancestors = nil)
114
+ return @lookup_order || [] unless ancestors
115
+
116
+ @lookup_order = ancestors
117
+ end
118
+
106
119
  # Declare which attributes are allowed through to factory_bot.
107
120
  def permitted_attrs(attr_list)
108
121
  define_method(:permitted_attrs) do |attrs|
@@ -195,6 +208,47 @@ module Foundries
195
208
  end
196
209
  end
197
210
 
211
+ # Find or create: when no parent is present, walks ancestors
212
+ # via lookup_order; otherwise finds from parent or creates.
213
+ def find_or_create(name, attrs = {})
214
+ return ascending_find(name) unless parent_present?
215
+
216
+ find_from_parent(name) || create_object(name, attrs)
217
+ end
218
+
219
+ # Walk ancestor types declared in lookup_order, checking
220
+ # collection_name on each ancestor found in current state.
221
+ # Falls back to collection find.
222
+ def ascending_find(name)
223
+ object = nil
224
+ self.class.lookup_order.each do |ancestor_type|
225
+ ancestor = current.send(ancestor_type)
226
+ next unless ancestor
227
+
228
+ col = self.class.collection_name
229
+ object = ancestor.send(col).find_by(name:)
230
+ break if object
231
+ end
232
+
233
+ object || find(name)
234
+ end
235
+
236
+ # Whether a parent is available in the current context.
237
+ def parent_present?
238
+ parent_method = self.class.parent
239
+ return true if parent_method.in?(%i[self none])
240
+
241
+ parent
242
+ end
243
+
244
+ # Find from the parent's association, falling back to
245
+ # collection find.
246
+ def find_from_parent(name, col_name: "name")
247
+ col = self.class.collection_name
248
+ parent.send(col).find_by(col_name => name) ||
249
+ find(name, col_name:)
250
+ end
251
+
198
252
  # Find a record in the collection by name, falling back to the database.
199
253
  def find(name, col_name: "name")
200
254
  raise "#find called with nil :name, for col_name: #{col_name}." unless name
@@ -202,9 +256,13 @@ module Foundries
202
256
  found_record = collection.detect do |object|
203
257
  object.send(col_name).casecmp?(name) && same_parent?(object)
204
258
  end
259
+ return found_record if found_record
205
260
 
206
- found_record ||
207
- record_class.find_by(col_name => name)&.tap { |rec| collection << rec }
261
+ scope = record_class.where(col_name => name)
262
+ if parent_key && parent_id
263
+ scope = scope.where(parent_key => parent_id)
264
+ end
265
+ scope.first&.tap { |rec| collection << rec }
208
266
  end
209
267
 
210
268
  # Find a record in the collection by arbitrary criteria, falling back to the database.
@@ -254,7 +312,8 @@ module Foundries
254
312
  def method_missing(name, *args, **kwargs, &block)
255
313
  if (match = missing_find_by_request?(name))
256
314
  klass_name = match.named_captures["klass"]
257
- return collection_find_by(klass_name, args)
315
+ attrs = kwargs.any? ? kwargs : args.first
316
+ return collection_find_by(klass_name, attrs)
258
317
  end
259
318
 
260
319
  if foundry.respond_to?(name)
@@ -274,8 +333,7 @@ module Foundries
274
333
  method_name.match(/^find_(?<klass>.*)_by$/)
275
334
  end
276
335
 
277
- def collection_find_by(klass_name, args)
278
- attrs = args.first
336
+ def collection_find_by(klass_name, attrs)
279
337
  target_collection_name = "#{klass_name.pluralize}_collection"
280
338
  objects = foundry.send(target_collection_name)
281
339
  objects.detect do |object|
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Foundries
4
- VERSION = "0.1.1"
5
- RELEASE_DATE = "2026-03-06"
4
+ VERSION = "0.1.2"
5
+ RELEASE_DATE = "2026-03-12"
6
6
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: foundries
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Dowd