foundries 0.1.0 → 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 +4 -4
- data/CHANGELOG.md +31 -1
- data/lib/foundries/base.rb +40 -18
- data/lib/foundries/blueprint.rb +63 -5
- data/lib/foundries/similarity/comparator.rb +0 -12
- data/lib/foundries/version.rb +2 -2
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d9bda37a762c9a055ca42c5faa685e30a6201fbeccaa5bbb7adffa0ccaef3386
|
|
4
|
+
data.tar.gz: 4fe49113055d53bb2e61df790fa037c849cff8d7b380011e6fd298fab0ec68af
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6e2b1f8a7fbc9107f684883ae9435c8c6cfc004698f7a5316ec47fb3531b9d100be9b12b55a4c097cefbb48e871c653717dbdf4ed5733dcbaf1032f5fed6a8e0
|
|
7
|
+
data.tar.gz: f25ae1307f8f675d4b6d617b13b9102639e49f29bd6f80f90145cc68023725a36eb3169207107c9af6cb1078188f8a0d104c6e591959fc1ee48d992563b9e827
|
data/CHANGELOG.md
CHANGED
|
@@ -5,4 +5,34 @@ 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.
|
|
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
|
+
|
|
22
|
+
## [0.1.1] - 2026-03-06
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
- Blueprint base class with collection tracking, parent scoping, and factory_bot integration (0002336)
|
|
27
|
+
- Snapshot caching system with Postgres and SQLite adapters for test suite acceleration (0002336)
|
|
28
|
+
- Source file content hashing in snapshot fingerprint for automatic cache invalidation (cfb348d)
|
|
29
|
+
|
|
30
|
+
### Changed
|
|
31
|
+
|
|
32
|
+
- Snapshot caching is opt-in via ARMATURE_CACHE=1 environment variable (0002336)
|
|
33
|
+
- Similarity detection only warns on identical structure, not containment (a5bdf25)
|
|
34
|
+
|
|
35
|
+
### Fixed
|
|
36
|
+
|
|
37
|
+
- Parentless blueprints no longer raise NameError when find calls same_parent? (cfb348d)
|
|
38
|
+
- Snapshot-cached presets no longer produce false similarity warnings (a5bdf25)
|
data/lib/foundries/base.rb
CHANGED
|
@@ -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? }
|
|
@@ -77,20 +89,22 @@ module Foundries
|
|
|
77
89
|
#
|
|
78
90
|
def preset(name, &block)
|
|
79
91
|
define_singleton_method(name) do
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
92
|
+
if defined?(Foundries::Snapshot) && Foundries::Snapshot.enabled?
|
|
93
|
+
store = Foundries::Snapshot::Store.new(name)
|
|
94
|
+
|
|
95
|
+
if store.cached?
|
|
96
|
+
store.restore
|
|
97
|
+
next new # hollow — no block, data already in DB
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
store.record_empty_tables
|
|
101
|
+
foundry = with_similarity_recording(name, Similarity.enabled?) do
|
|
102
|
+
new(&block)
|
|
103
|
+
end
|
|
104
|
+
store.capture
|
|
105
|
+
foundry
|
|
106
|
+
else
|
|
107
|
+
with_similarity_recording(name, Similarity.enabled?) do
|
|
94
108
|
new(&block)
|
|
95
109
|
end
|
|
96
110
|
end
|
|
@@ -119,16 +133,17 @@ module Foundries
|
|
|
119
133
|
|
|
120
134
|
def inherited(subclass)
|
|
121
135
|
super
|
|
122
|
-
#
|
|
123
|
-
subclass.instance_variable_set(:@blueprint_registry,
|
|
124
|
-
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)
|
|
125
140
|
end
|
|
126
141
|
end
|
|
127
142
|
|
|
128
143
|
def initialize(&block)
|
|
129
144
|
@_similarity_recorder = self.class._active_similarity_recorder
|
|
130
|
-
instantiate_blueprints
|
|
131
145
|
initialize_collections
|
|
146
|
+
instantiate_blueprints
|
|
132
147
|
@current = OpenStruct.new(resource: self)
|
|
133
148
|
setup
|
|
134
149
|
instance_exec(&block) if block
|
|
@@ -211,6 +226,13 @@ module Foundries
|
|
|
211
226
|
end
|
|
212
227
|
end
|
|
213
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
|
|
214
236
|
end
|
|
215
237
|
|
|
216
238
|
def initialize_collections
|
data/lib/foundries/blueprint.rb
CHANGED
|
@@ -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
|
-
|
|
207
|
-
|
|
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
|
-
|
|
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,
|
|
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|
|
|
@@ -17,18 +17,6 @@ module Foundries
|
|
|
17
17
|
":#{preset_name(existing_key)} have identical structure " \
|
|
18
18
|
"(#{display_tree(new_tree)})"
|
|
19
19
|
}
|
|
20
|
-
elsif existing_tree.contains?(new_tree)
|
|
21
|
-
warnings << {
|
|
22
|
-
pair: pair_key,
|
|
23
|
-
message: "[Foundries] Preset :#{preset_name(new_key)} is " \
|
|
24
|
-
"structurally contained within :#{preset_name(existing_key)}"
|
|
25
|
-
}
|
|
26
|
-
elsif new_tree.contains?(existing_tree)
|
|
27
|
-
warnings << {
|
|
28
|
-
pair: pair_key,
|
|
29
|
-
message: "[Foundries] Preset :#{preset_name(existing_key)} is " \
|
|
30
|
-
"structurally contained within :#{preset_name(new_key)}"
|
|
31
|
-
}
|
|
32
20
|
end
|
|
33
21
|
end
|
|
34
22
|
|
data/lib/foundries/version.rb
CHANGED
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.
|
|
4
|
+
version: 0.1.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- John Dowd
|
|
@@ -87,7 +87,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
87
87
|
- !ruby/object:Gem::Version
|
|
88
88
|
version: '0'
|
|
89
89
|
requirements: []
|
|
90
|
-
rubygems_version:
|
|
90
|
+
rubygems_version: 4.0.3
|
|
91
91
|
specification_version: 4
|
|
92
92
|
summary: Declarative trees of related data using factory_bot
|
|
93
93
|
test_files: []
|