namo 0.24.1 → 0.25.1

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: 017c3006832fa49d8e63c5de42d927e0e2bb661f05ff322b002a3d1ba18758e2
4
- data.tar.gz: 65a1c6b04337361f3b692423e85e8066f598ddbd028e0349e4c85b6ebabc183f
3
+ metadata.gz: fd20f662edb4f305d6c74b5a100c536c20ad57ce31f7044498f76eebe2fbbf68
4
+ data.tar.gz: e242043273cac5866c8dda07aec202c69954d36072f8092b50bb817b6ca24bb8
5
5
  SHA512:
6
- metadata.gz: 989fbb45527739717202af6f6b74df7549488045c41acd3429ea190f7ec5ac4b54e527f684bba86201704ffd3647f2446b282c8f0cc48f2ca68677548e9e1c8b
7
- data.tar.gz: '00494397772996dd29cf099c7c019d0909d5d61eafaa2dec42a827cc79cad7db0c60fc3ab5172a63acab77dd2034b4a6ad7a7a6440c94485870754563ba94e9f'
6
+ metadata.gz: 581cbeae8e0eeca1fb35db857d2acad566ecd21dc8d967e921161620b6a994e26293b17026e806c0c754851fe8da175bf3e260afe98e8f2899853fcb1e20fda3
7
+ data.tar.gz: cdc1687745394bf2931469daf7823d0c724436aea4a0c4657505f39beae1afe8a6224406682f9d630672a12fc6554b9872044ee8d72d93acfd03d7459bb32282
data/CHANGELOG CHANGED
@@ -1,7 +1,29 @@
1
- CHANGELOG
2
- _________
1
+ # CHANGELOG
2
+
3
+ ## 20260703
4
+
5
+ 0.25.1: Fix documentation and reformat CHANGELOG to use Markdown headers.
6
+
7
+ 1. ~ ROADMAP.md: + the missing 0.22.0 entry (Formulae#derive) between 0.21.0 and 0.23.0; normalise 0.20.0–0.24.1 to ### under ## Current state (#### subsections), matching 0.0.0–0.19.0 and the already-correct 0.25.0; relocate ### Summary to close the shipped block, its vocabulary brought current through the formulary tier and attach!; the 1.0.0 "everything through" line → 0.25.0 with an attach! bullet.
8
+ 2. ~ CHANGELOG: reformat to Markdown headers (# CHANGELOG, ## <date>), keeping the extensionless name; entry bodies unchanged.
9
+ 3. ~ docs/: regenerate the print-ready PDFs via rake docs:gen (stale for several releases).
10
+ 4. ~ Namo::VERSION: /0.25.0/0.25.1/
11
+
12
+ ## 20260703
13
+
14
+ 0.25.0: + Namo#attach! — attach a formulary, evicting any data column whose name a formulary method collides with, rather than raising.
15
+
16
+ attach! evicts the colliding data columns first (as []='s proc-over-data assignment does), then attaches — the bang is the explicit consent that attach's raise withholds by default. << stays guarded: a Module still routes to attach.
17
+
18
+ 1. ~ Namo#attach — extract the collision set into a new private attach_collisions(modul) (modul.public_instance_methods(false) & data_dimensions); behaviour unchanged, still raises ArgumentError naming the collisions when the set is non-empty.
19
+ 2. + Namo#attach! — evicts each colliding data column (@data.each{|row| row.delete(name)} per colliding name, the column membership already confirmed by attach_collisions so no key-guard is needed), then forwards to @formulae.attach and returns self. The eviction mirrors []='s proc-branch clear. + private attach_collisions(modul). << is unchanged: a Module still routes to attach (the guarded form), so the operator stays safe and attach! is reached only by explicit call — one safe operator, one explicit forceful method, matching []= having no operator shortcut for its own eviction.
20
+ 3. ~ test/namo_test.rb: + an "#attach!" describe — attach! attaches as attach does for a non-colliding module (returns self, methods resolve); attach! evicts a colliding data column and registers the formulary method in its place, with all access paths (values, Row, selection) agreeing on the formula afterward and the name appearing once in derived_dimensions, not in data_dimensions; attach! over several colliding columns evicts all of them; attach! on a vanilla collision-free module leaves the data untouched; the existing attach collision-raise tests are unchanged, pinning that the guarded form still raises where attach! evicts.
21
+ 4. ~ README.md: ~ the Formularies section — + attach! as the forceful sibling of attach, with the materialised-snapshot refresh as the motivating case and the bang-is-consent rationale; + the self-eviction-recurses caveat (a formulary method that reads a column its own name evicts recurses on access, exclusive storage leaving no shadowed value behind the formula — the one way attach!'s eviction differs in feel from []='s); note that << stays the guarded form.
22
+ 5. ~ ROADMAP.md: + the 0.25.0 release entry; current-state bumped to 0.25.0.
23
+ 6. ~ Namo::VERSION: /0.24.1/0.25.0/
24
+
25
+ ## 20260629
3
26
 
4
- 20260629
5
27
  0.24.1: ~ Namo#add_row — guard row-append against a data/formula name collision.
6
28
 
7
29
  0.24.0 left row-append (the Hash/Row arms of <<) unguarded, on the reasoning that the data/formula exclusivity invariant was []='s to keep. That reasoning had a hole: when an appended row carries a key matching an existing formula name and lands as @data.first (e.g. the first row streamed into an empty formulary-bearing Namo), bulk values reads the raw datum (data_dimensions keys off @data.first.keys) while per-Row access derives the formula (Row#[] checks @formulae first) — the two access paths disagree, the precise split the invariant exists to prevent. So add_row now raises rather than admitting the collision, symmetric with attach's guard: a row is a bulk append you didn't name column-by-column, so refusing is safer than []='s call-site eviction.
@@ -12,7 +34,8 @@ _________
12
34
  4. ~ ROADMAP.md: + the 0.24.1 release entry; current-state bumped to 0.24.1; the 0.24.0 "not guarded" note annotated as guarded at 0.24.1.
13
35
  5. ~ Namo::VERSION: /0.24.0/0.24.1/
14
36
 
15
- 20260629
37
+ ## 20260629
38
+
16
39
  0.24.0: ~ Namo#<<, Namo::Collection#<< — widen from a formulary-only alias into a polymorphic "append a constituent" operator.
17
40
 
18
41
  A base Namo's constituents are formularies and rows: << dispatches a Module to attach (0.23.0 behaviour, unchanged), a Hash to a data-row append, and a Row to a row append of its underlying hash. It does not take a bare callable (formula registration stays with []=) or a whole Namo (combining is +'s job) — both raise. Collection#<< is reconciled to the same verb over its own constituents: a Namo is a member, a Module attaches, and a loose Hash/Row raises (a Collection's rows are derived from its members, so a loose row has no durable home). This Hash/Row divergence — a base Namo appends the row, a Collection refuses it — is deliberate, not an oversight.
@@ -25,7 +48,8 @@ A base Namo's constituents are formularies and rows: << dispatches a Module to a
25
48
  6. ~ ROADMAP.md: + the 0.24.0 release entry; current-state bumped to 0.24.0; pivot/reshaping recorded as a decision (long-form aggregation is in-grain future work, widening-to-a-grid is presentational and belongs at an output edge, not the algebra), and collection formulae noted as needing no new mechanism.
26
49
  7. ~ Namo::VERSION: /0.23.0/0.24.0/
27
50
 
28
- 20260627
51
+ ## 20260627
52
+
29
53
  0.23.0: + Namo::Formulary — A formulary is a reusable body of derived dimensions a Namo instance may draw upon.
30
54
 
31
55
  1. + lib/Namo/Formulary.rb: + Namo::Formulary, an empty marker module. A module includes it to brand itself a formulary; attach raises ArgumentError for an untagged module, so an ordinary module of helpers can't be mistaken for a body of derivations.
@@ -36,341 +60,132 @@ A base Namo's constituents are formularies and rows: << dispatches a Module to a
36
60
  6. ~ README.md: + a "Formularies" subsection under Formulae (attach and the marker, private-hides-helpers, the copy-into-the-store / snapshot model, the raise on a data collision, materialise-and-drop, carry-through, the bare-name/memoisation forward-note); the Polymorphic []= section now says "a callable" (proc, lambda, or Method) rather than "a proc". ~ COMPARISON.md: + a "Formularies" row. ~ ROADMAP.md: + the 0.23.0 release entry; current-state bumped to 0.23.0; the 2.x "module-based formula libraries", "Namo#=== revision", and marker-vs-implicit notes annotated as shipped/decided at 0.23.0, with bare-name bodies remaining 2.x.
37
61
  7. ~ Namo::VERSION: /0.22.0/0.23.0/
38
62
 
39
- 20260626
63
+ ## 20260626
64
+
40
65
  0.22.0: + Formulae#derive — move formula invocation out of Row#[] and into Formulae. [] keeps its meaning (the stored callable, as at 0.21.0); the new derive(name, row, namo, *arguments) resolves a named formula to its value. derive over call: the receiver is the collection, not the callable, so the verb says the collection derives a value via the named rule rather than borrowing Proc#call's "invoke the receiver"; and the result is whatever the proc returns, not necessarily numeric, which a neutral verb leaves open. Indexing a Formulae is storage access, not a Namo operation, so [] returning the callable carries no inconsistency with the dimension-value [] of Namo and Row — different domains, same spelling. Behaviour-preserving at the usage level: row[:revenue], namo[:x] = formula, values, and selections are unchanged.
41
66
 
42
- 1. ~ lib/Namo/Formulae.rb: + derive(name, row, namo, *arguments), the formula resolver —
43
- invocation mechanics lifted whole from Row's formula branch (collection-scoped formulae
44
- are called (row, namo, *arguments), row-only formulae (row)). + required_parameter_count(name),
45
- public, a property of the stored callable. collection_scoped? and raise_unless_namo_context
46
- move in as private; the latter takes namo as a parameter rather than closing over Row's
47
- @namo. [] and []= are untouched — [] still returns the stored callable.
48
- 2. ~ lib/Namo/Row.rb: #[] delegates formula resolution to
49
- @formulae.derive(name, self, @namo, *arguments); Row keeps only the fail-fast argument
50
- guard, the formula-vs-data dispatch (@formulae.key?), and the data fallback (@row[name]).
51
- collection_scoped?, required_parameter_count, and raise_unless_namo_context are gone
52
- (now Formulae's). expected_argument_counts sources the count from
53
- @formulae.required_parameter_count and the fixed-vs-variadic check from
54
- @formulae[name].arity.
55
- 3. + test/Namo/Formulae_test.rb: + #derive tests (row-only to value, collection-scoped with
56
- namo context, missing-namo-context raise, parameterised with arguments) and +
57
- #required_parameter_count tests (row-only, collection-scoped, parameterised, variadic).
58
- The #[] and #[]= blocks are unchanged — [] still returns the callable.
59
- 4. ~ test/Namo/Row_test.rb: the formulae fixture is a Namo::Formulae, not a bare {} — Row
60
- now delegates formula resolution to its collaborator (call, required_parameter_count),
61
- which production always wraps (lib/namo.rb initialize). The data-only equality/match
62
- tests keep their {} literals: they never resolve a formula, so Hash#key? alone answers.
67
+ 1. ~ lib/Namo/Formulae.rb: + derive(name, row, namo, *arguments), the formula resolver — invocation mechanics lifted whole from Row's formula branch (collection-scoped formulae are called (row, namo, *arguments), row-only formulae (row)). + required_parameter_count(name), public, a property of the stored callable. collection_scoped? and raise_unless_namo_context move in as private; the latter takes namo as a parameter rather than closing over Row's @namo. [] and []= are untouched — [] still returns the stored callable.
68
+ 2. ~ lib/Namo/Row.rb: #[] delegates formula resolution to @formulae.derive(name, self, @namo, *arguments); Row keeps only the fail-fast argument guard, the formula-vs-data dispatch (@formulae.key?), and the data fallback (@row[name]). collection_scoped?, required_parameter_count, and raise_unless_namo_context are gone (now Formulae's). expected_argument_counts sources the count from @formulae.required_parameter_count and the fixed-vs-variadic check from @formulae[name].arity.
69
+ 3. + test/Namo/Formulae_test.rb: + #derive tests (row-only to value, collection-scoped with namo context, missing-namo-context raise, parameterised with arguments) and + #required_parameter_count tests (row-only, collection-scoped, parameterised, variadic). The #[] and #[]= blocks are unchanged — [] still returns the callable.
70
+ 4. ~ test/Namo/Row_test.rb: the formulae fixture is a Namo::Formulae, not a bare {} Row now delegates formula resolution to its collaborator (call, required_parameter_count), which production always wraps (lib/namo.rb initialize). The data-only equality/match tests keep their {} literals: they never resolve a formula, so Hash#key? alone answers.
63
71
  5. ~ Namo::VERSION: /0.21.0/0.22.0/
64
72
 
65
- 20260625
73
+ ## 20260625
74
+
66
75
  0.21.0: + Namo::Formulae; @formulae: /Hash/Namo::Formulae/. Behaviour-preserving: no new capability, just an object for the formula collection to accrete behaviour onto later, mirroring the 0.1.0 extraction of Row.
67
76
 
68
- 1. + lib/Namo/Formulae.rb: + Namo::Formulae, a thin class wrapping a Hash @store.
69
- Responds to exactly the subset of Hash that lib/namo.rb and Row send the formula
70
- collection: [], []=, keys, key?, empty?, each, delete, merge, reject, dup; includes
71
- Enumerable (each yields [name, callable] pairs, giving map/select/etc. for free).
72
- Three deliberate choices: to_h returns a copy (@store.dup) so callers cannot mutate
73
- internals; ==/eql?/hash are names-only (keys.sort), matching Namo's own formula
74
- equality where two sets with the same names but different proc objects are equivalent;
75
- merge/reject/dup return a Formulae, not a Hash.
76
- 2. ~ lib/namo.rb: + the require; initialize now normalises the incoming formulae to a
77
- Formulae (Formulae.new(formulae) unless already one). This is the single behavioural
78
- seam — every existing internal construction passes a Hash and is wrapped, while
79
- operator results that now pass a Formulae (merge/dup return Formulae) pass through.
80
- No other call site changed: each sends a message Formulae answers.
81
- 3. ~ test/namo_test.rb: the two assertions comparing the public #formulae accessor to a
82
- literal {} now assert emptiness (must_be_empty). The accessor returns a Formulae, not a
83
- Hash; the formula set being empty is the unchanged behaviour these tests pin. + two
84
- construction tests pinning the normalisation seam: a Hash given by keyword is wrapped in
85
- a Formulae, and a Formulae given by keyword passes through unchanged (same object).
86
- 4. + test/Namo/Formulae_test.rb: + unit test pinning the three deliberate choices (to_h
87
- copy, names-only ==/eql?/hash, the Hash-compatible surface plus Enumerable map/select).
77
+ 1. + lib/Namo/Formulae.rb: + Namo::Formulae, a thin class wrapping a Hash @store. Responds to exactly the subset of Hash that lib/namo.rb and Row send the formula collection: [], []=, keys, key?, empty?, each, delete, merge, reject, dup; includes Enumerable (each yields [name, callable] pairs, giving map/select/etc. for free). Three deliberate choices: to_h returns a copy (@store.dup) so callers cannot mutate internals; ==/eql?/hash are names-only (keys.sort), matching Namo's own formula equality where two sets with the same names but different proc objects are equivalent; merge/reject/dup return a Formulae, not a Hash.
78
+ 2. ~ lib/namo.rb: + the require; initialize now normalises the incoming formulae to a Formulae (Formulae.new(formulae) unless already one). This is the single behavioural seam — every existing internal construction passes a Hash and is wrapped, while operator results that now pass a Formulae (merge/dup return Formulae) pass through. No other call site changed: each sends a message Formulae answers.
79
+ 3. ~ test/namo_test.rb: the two assertions comparing the public #formulae accessor to a literal {} now assert emptiness (must_be_empty). The accessor returns a Formulae, not a Hash; the formula set being empty is the unchanged behaviour these tests pin. + two construction tests pinning the normalisation seam: a Hash given by keyword is wrapped in a Formulae, and a Formulae given by keyword passes through unchanged (same object).
80
+ 4. + test/Namo/Formulae_test.rb: + unit test pinning the three deliberate choices (to_h copy, names-only ==/eql?/hash, the Hash-compatible surface plus Enumerable map/select).
88
81
  5. ~ Namo::VERSION: /0.20.0/0.21.0/
89
82
 
90
- 20260615
83
+ ## 20260615
84
+
91
85
  0.20.0: + group_by(dimension) — splits a Namo into a Namo::Collection, one member per distinct value, completing the Enumerable coherence pass begun at 0.11.0.
92
86
 
93
- 1. ~ lib/Namo/Enumerable.rb: + Namo#group_by(dimension). Returns a Namo::Collection
94
- whose members are the groups — one member per distinct value, each a Namo of the
95
- matching rows, named by its group value, carrying the surviving formulae, preserving
96
- subclass type via self.class.new. The grouping dimension is retained (the split is
97
- along the axis, not consuming it), so namo.group_by(d).as_detail(d) inverts it. A
98
- nil-valued group produces a nil-named member that holds its rows and round-trips, the
99
- use-site naming decision (0.18.0) paying off. A ternary makes the materialise
100
- decision: when dimension is derived, the group source is the projection
101
- (self[*data_dimensions, dimension], 0.16.0) so the grouped-by formula becomes a stored
102
- column and is dropped while every other formula carries through live; when data, the
103
- source is self untouched. A parameterised formula raises the 0.17.0 ArgumentError at
104
- the materialisation site, group_by needing no enforcement of its own.
105
- 2. ~ test/namo_test.rb: + "#group_by" describe — return type, one-member-per-value,
106
- row partitioning, grouping-dimension retention, find-by-name, formula carry-through,
107
- subclass preservation, non-mutation, empty-Namo; the uniform inversion law
108
- (data: exact; formula: against the materialised form); grouping by a formula
109
- (partition by value, materialise-and-drop, only-grouped-formula-altered with a live
110
- dependent, parameterised raise); the nil-valued grouping dimension (nil-named member,
111
- no rows dropped, round-trip); and consistency with assembly (summary/detail on a
112
- partitioned Collection).
113
- 3. ~ README.md: + group_by subsection under Collections — the partition constructor as
114
- the mirror of <<, the round-trip example, the materialise-on-formula behaviour, and
115
- the cross-reference to the assembly side.
116
- 4. ~ ROADMAP.md: Promote 0.20.0 to shipped; Current state -> 0.20.0; + the materialisation
117
- rationale (unified-treatment requires it; materialise-and-drop is the only invertible
118
- choice; the uniform inversion law); Summary folds in group_by; next phase -> 1.0.0.
119
- 5. ~ COMPARISON.md: Aggregation entry — group_by repointed from planned (0.20.0) to
120
- shipped, the partition constructor now realised; + the grouping-by-formula comparison
121
- (computed-key grouping is parity, but only Namo partitions on a named, attached formula
122
- through the data-dimension interface). Date bumped.
87
+ 1. ~ lib/Namo/Enumerable.rb: + Namo#group_by(dimension). Returns a Namo::Collection whose members are the groups — one member per distinct value, each a Namo of the matching rows, named by its group value, carrying the surviving formulae, preserving subclass type via self.class.new. The grouping dimension is retained (the split is along the axis, not consuming it), so namo.group_by(d).as_detail(d) inverts it. A nil-valued group produces a nil-named member that holds its rows and round-trips, the use-site naming decision (0.18.0) paying off. A ternary makes the materialise decision: when dimension is derived, the group source is the projection (self[*data_dimensions, dimension], 0.16.0) so the grouped-by formula becomes a stored column and is dropped while every other formula carries through live; when data, the source is self untouched. A parameterised formula raises the 0.17.0 ArgumentError at the materialisation site, group_by needing no enforcement of its own.
88
+ 2. ~ test/namo_test.rb: + "#group_by" describe return type, one-member-per-value, row partitioning, grouping-dimension retention, find-by-name, formula carry-through, subclass preservation, non-mutation, empty-Namo; the uniform inversion law (data: exact; formula: against the materialised form); grouping by a formula (partition by value, materialise-and-drop, only-grouped-formula-altered with a live dependent, parameterised raise); the nil-valued grouping dimension (nil-named member, no rows dropped, round-trip); and consistency with assembly (summary/detail on a partitioned Collection).
89
+ 3. ~ README.md: + group_by subsection under Collections the partition constructor as the mirror of <<, the round-trip example, the materialise-on-formula behaviour, and the cross-reference to the assembly side.
90
+ 4. ~ ROADMAP.md: Promote 0.20.0 to shipped; Current state -> 0.20.0; + the materialisation rationale (unified-treatment requires it; materialise-and-drop is the only invertible choice; the uniform inversion law); Summary folds in group_by; next phase -> 1.0.0.
91
+ 5. ~ COMPARISON.md: Aggregation entry — group_by repointed from planned (0.20.0) to shipped, the partition constructor now realised; + the grouping-by-formula comparison (computed-key grouping is parity, but only Namo partitions on a named, attached formula through the data-dimension interface). Date bumped.
123
92
  6. ~ Namo::VERSION: /0.19.0/0.20.0/
124
93
 
125
- 20260614
94
+ ## 20260614
95
+
126
96
  0.19.0: ~ row-multiset equality — replace the sorted canonical form with a row multiset, so ==, eql?, hash, and the subset operators compare without sorting: nil/NaN-safe, row- and dimension-order blind, and consistently type-strict.
127
97
 
128
- 1. ~ lib/namo.rb: replace canonical_data (which sorted @data by row.values_at(*data_dimensions.sort))
129
- with row_multiset (@data.tally). ==, eql?, hash, and subset_of_rows? now compare row
130
- multisets keyed by Hash#hash/#eql?. The sort was only ever a mechanism for reaching the
131
- already-decided multiset semantics (0.6.0); a multiset reaches them directly and needs no
132
- ordering, so the latent ArgumentError when a dimension mixed nil (or NaN) with present
133
- values — neither has a <=> against the other — is gone. The relation is now uniformly
134
- eql?-based, so == agrees with the subset operators, which already tallied: {a: 1} and
135
- {a: 1.0} are consistently distinct (== previously called them equal via Hash#==, while <=
136
- called them distinct).
137
- 2. ~ test/namo_test.rb: + four "#==" cases — equality across row order and across dimension
138
- (key) order when a dimension mixes nil and non-nil values; inequality when such rows
139
- differ; and type-strictness on values, consistent with <=.
98
+ 1. ~ lib/namo.rb: replace canonical_data (which sorted @data by row.values_at(*data_dimensions.sort)) with row_multiset (@data.tally). ==, eql?, hash, and subset_of_rows? now compare row multisets keyed by Hash#hash/#eql?. The sort was only ever a mechanism for reaching the already-decided multiset semantics (0.6.0); a multiset reaches them directly and needs no ordering, so the latent ArgumentError when a dimension mixed nil (or NaN) with present values — neither has a <=> against the other — is gone. The relation is now uniformly eql?-based, so == agrees with the subset operators, which already tallied: {a: 1} and {a: 1.0} are consistently distinct (== previously called them equal via Hash#==, while <= called them distinct).
99
+ 2. ~ test/namo_test.rb: + four "#==" cases — equality across row order and across dimension (key) order when a dimension mixes nil and non-nil values; inequality when such rows differ; and type-strictness on values, consistent with <=.
140
100
  3. ~ Namo::VERSION: /0.18.0/0.19.0/
141
101
 
142
- 20260613
102
+ ## 20260613
103
+
143
104
  0.18.0: + Namo::Collection — hierarchical aggregate of named Namos with summary/detail views.
144
105
 
145
- 1. + lib/Namo/Collection.rb: Namo::Collection < Namo, the first family member beyond Namo
146
- itself. Holds @members (an Array of named Namos, attr_reader :members) whose substance is
147
- the collection; the inherited @data is a derived view rebuilt from the members. << accepts a
148
- member or an array (via flatten), replaces by name (last-write-wins, unless member.name.nil?
149
- so nil-named members always append), rebuilds @data = detail.data, and returns self. find(name)
150
- returns the named member or nil (find(nil) is nil, and an unnamed member never matches),
151
- shadowing Enumerable#find on Collections. summary(dimension, by: :member, reducer: :sum)
152
- reduces each member to a {by => member.name, dimension => reduced} row; detail(by: :member)
153
- unions the members' rows under the inject-iff-absent conditional
154
- (row.key?(by) ? row : row.merge(by => member.name)); both return a fresh Namo (non-mutating).
155
- as_summary(...) and as_detail(by = :member) set @data to the chosen view and return self
156
- (mutating); the view persists until the next << re-materialises detail. Private initialize
157
- sets @members = [] before super.
106
+ 1. + lib/Namo/Collection.rb: Namo::Collection < Namo, the first family member beyond Namo itself. Holds @members (an Array of named Namos, attr_reader :members) whose substance is the collection; the inherited @data is a derived view rebuilt from the members. << accepts a member or an array (via flatten), replaces by name (last-write-wins, unless member.name.nil? so nil-named members always append), rebuilds @data = detail.data, and returns self. find(name) returns the named member or nil (find(nil) is nil, and an unnamed member never matches), shadowing Enumerable#find on Collections. summary(dimension, by: :member, reducer: :sum) reduces each member to a {by => member.name, dimension => reduced} row; detail(by: :member) unions the members' rows under the inject-iff-absent conditional (row.key?(by) ? row : row.merge(by => member.name)); both return a fresh Namo (non-mutating). as_summary(...) and as_detail(by = :member) set @data to the chosen view and return self (mutating); the view persists until the next << re-materialises detail. Private initialize sets @members = [] before super.
158
107
  2. ~ lib/namo.rb: + require_relative './Namo/Collection'.
159
- 3. + test/Namo/Collection_test.rb: empty members; lazy detail materialisation and selection;
160
- pure-live reflection of <<; <<' add/replace-by-name/array/unnamed/returns-self; find by name,
161
- absent, nil, and unnamed; summary's labelled rows, custom by:, :mean reducer (local Array#mean),
162
- and non-mutation; detail's plain-Namo return, inject-iff-absent both ways, and non-mutation;
163
- live recomputation after <<; as_summary/as_detail mutate and return self and surface the
164
- summary's columns in dimensions; as_* view persistence until the next <<; assembly round-trip
165
- (as_detail(:assembly) injects and is retained, removed only by contraction); subclass default
166
- by: via a Car < Namo::Collection overriding summary/detail.
167
- 4. ~ README.md: + Collections section after Named Namos — the GT-budget example (Car/SubAssembly,
168
- <<, summary, detail), lazy detail materialisation, the four view methods and the
169
- non-mutating/mutating split, the inject-iff-absent rule, << replace-by-name with use-site
170
- handling of unnamed members, the rebuild-on-<< view lifetime, and forward-notes to group_by
171
- (0.19.0) and freeze-gated memoisation (2.x).
172
- 5. ~ ROADMAP.md: Promote 0.18.0 to shipped, recording the resolved materialisation model
173
- (rebuild-on-<<, as_* persists until the next <<, diverging from the drafted pure-live-per-access
174
- / transient wording) and the find/Enumerable#find shadowing; Current state -> 0.18.0; Summary
175
- folds in Namo::Collection; next phase -> group_by (0.19.0). The upcoming 0.18.0 design block
176
- folded into the shipped entry.
177
- 6. ~ COMPARISON.md: Aggregation entry repointed — Namo::Collection shipped (0.18.0), group_by
178
- constructor still planned (0.19.0); persistent-named-collection retained as Namo's novelty
179
- versus the other tools' transient group-by intermediates.
108
+ 3. + test/Namo/Collection_test.rb: empty members; lazy detail materialisation and selection; pure-live reflection of <<; <<' add/replace-by-name/array/unnamed/returns-self; find by name, absent, nil, and unnamed; summary's labelled rows, custom by:, :mean reducer (local Array#mean), and non-mutation; detail's plain-Namo return, inject-iff-absent both ways, and non-mutation; live recomputation after <<; as_summary/as_detail mutate and return self and surface the summary's columns in dimensions; as_* view persistence until the next <<; assembly round-trip (as_detail(:assembly) injects and is retained, removed only by contraction); subclass default by: via a Car < Namo::Collection overriding summary/detail.
109
+ 4. ~ README.md: + Collections section after Named Namos — the GT-budget example (Car/SubAssembly, <<, summary, detail), lazy detail materialisation, the four view methods and the non-mutating/mutating split, the inject-iff-absent rule, << replace-by-name with use-site handling of unnamed members, the rebuild-on-<< view lifetime, and forward-notes to group_by (0.19.0) and freeze-gated memoisation (2.x).
110
+ 5. ~ ROADMAP.md: Promote 0.18.0 to shipped, recording the resolved materialisation model (rebuild-on-<<, as_* persists until the next <<, diverging from the drafted pure-live-per-access / transient wording) and the find/Enumerable#find shadowing; Current state -> 0.18.0; Summary folds in Namo::Collection; next phase -> group_by (0.19.0). The upcoming 0.18.0 design block folded into the shipped entry.
111
+ 6. ~ COMPARISON.md: Aggregation entry repointed — Namo::Collection shipped (0.18.0), group_by constructor still planned (0.19.0); persistent-named-collection retained as Namo's novelty versus the other tools' transient group-by intermediates.
180
112
  7. ~ Namo::VERSION: /0.17.0/0.18.0/
181
113
 
182
- 20260613
114
+ ## 20260613
115
+
183
116
  0.17.0: + parameterised formulae — formulae with required parameters beyond (row, namo) receive arguments at access time through Row#[].
184
117
 
185
- 1. ~ lib/Namo/Row.rb: Row#[] gains a trailing splat and forwards call-site arguments. Dispatch
186
- generalises from exact arity 2 to required-parameter count via the new private
187
- collection_scoped? and required_parameter_count — one required parameter or none stays
188
- row-scoped, two or more call formula.call(self, @namo, *arguments). Settles the 0.15.0
189
- negative-arity deferral: |row, *rest| and ->(row, namo = nil){} stay row-scoped;
190
- |row, namo, *fields| is collection-scoped with optional arguments.
191
- 2. ~ lib/Namo/Row.rb: + argument-count enforcement the new private expected_argument_counts
192
- and raise_unless_expected_arguments raise ArgumentError ("wrong number of arguments for
193
- :sma (given 0, expected 2)") on the wrong count for any dimension: too few or too many for
194
- a fixed-arity formula, fewer than required for a splatted one, any at all for a data
195
- dimension, a row-scoped formula, or a two-arity formula. Checked before the Namo-context
196
- guard, whose message generalises from "two-arity formula" to "collection-scoped formula".
197
- 3. ~ lib/namo.rb: the no-arg values (and so coordinates and to_h) materialise via the new
198
- private materialisable_dimensions, omitting formulae that require arguments (private
199
- requires_arguments? and required_parameter_count); explicit asks — values(:dim),
200
- coordinates(:dim), naming the dimension in a projection, selecting on it — raise through
201
- Row#[]. dimensions and derived_dimensions still list the name; the empty-Namo case returns
202
- [] without invoking the formula.
203
- 4. ~ test/Namo/Row_test.rb: + "#[] parameterised formulae" describe — arity 3 and 4 receive
204
- (row, namo, args...), trailing-splat forwarding (arity -3 and -4), one-required-parameter
205
- procs stay row-scoped, a row-scoped formula calling a parameterised one with arguments,
206
- missing-Namo-context raise; + "#[] argument-count enforcement" describe — the message
207
- matrix for too few, too many, splat minimum, and arguments handed to data dimensions,
208
- row-scoped, two-arity, and missing dimensions.
209
- 5. ~ test/namo_test.rb: + "#[]= parameterised formulae" describe — resolution with arguments
210
- through yielded Rows, one definition serving different fields and periods, Enumerable
211
- predicates, a one-arity formula referencing a parameterised one, dimension listing, bulk
212
- views omitting, explicit values/coordinates/projection/selection raises, carry through
213
- contraction, selection (windowing over the filtered rows), and set operators, the
214
- one-arity wrapper materialisation idiom, namo-plus-splat formulae materialising in the
215
- bulk views, and the empty-Namo case.
216
- 6. ~ README.md: + Parameterised formulae subsection under Formulae — access-time arguments,
217
- the required-parameter scope rule, argument-count enforcement, materialisation behaviour,
218
- and the wrapper idiom.
219
- 7. ~ ROADMAP.md: Promote 0.17.0 to shipped with the dispatch-rule, enforcement, and
220
- materialisation rationale; Current state -> 0.17.0; Summary folds in parameterised
221
- formulae; next phase -> Namo::Collection (0.18.0). Date bumped.
222
- 8. ~ COMPARISON.md: Parameterised formulae -> shipped (0.17.0), the entry noting access-time
223
- arguments and argument-count enforcement. Date bumped.
224
- 9. ~ EXAMPLES.md: The finance section's composition blocks (all three Namo stages) corrected
225
- from max_by, which returns a Row, to sort_by + last(1), which returns the Namo the 0.14.0
226
- block contract requires — the 1.x stage, parameterised sma included, now runs end to end
227
- against this release. The finance highlight notes the parameterised formula as shipped
228
- (0.17.0). Date bumped.
118
+ 1. ~ lib/Namo/Row.rb: Row#[] gains a trailing splat and forwards call-site arguments. Dispatch generalises from exact arity 2 to required-parameter count via the new private collection_scoped? and required_parameter_count — one required parameter or none stays row-scoped, two or more call formula.call(self, @namo, *arguments). Settles the 0.15.0 negative-arity deferral: |row, *rest| and ->(row, namo = nil){} stay row-scoped; |row, namo, *fields| is collection-scoped with optional arguments.
119
+ 2. ~ lib/Namo/Row.rb: + argument-count enforcement — the new private expected_argument_counts and raise_unless_expected_arguments raise ArgumentError ("wrong number of arguments for :sma (given 0, expected 2)") on the wrong count for any dimension: too few or too many for a fixed-arity formula, fewer than required for a splatted one, any at all for a data dimension, a row-scoped formula, or a two-arity formula. Checked before the Namo-context guard, whose message generalises from "two-arity formula" to "collection-scoped formula".
120
+ 3. ~ lib/namo.rb: the no-arg values (and so coordinates and to_h) materialise via the new private materialisable_dimensions, omitting formulae that require arguments (private requires_arguments? and required_parameter_count); explicit asks values(:dim), coordinates(:dim), naming the dimension in a projection, selecting on it — raise through Row#[]. dimensions and derived_dimensions still list the name; the empty-Namo case returns [] without invoking the formula.
121
+ 4. ~ test/Namo/Row_test.rb: + "#[] parameterised formulae" describe — arity 3 and 4 receive (row, namo, args...), trailing-splat forwarding (arity -3 and -4), one-required-parameter procs stay row-scoped, a row-scoped formula calling a parameterised one with arguments, missing-Namo-context raise; + "#[] argument-count enforcement" describe — the message matrix for too few, too many, splat minimum, and arguments handed to data dimensions, row-scoped, two-arity, and missing dimensions.
122
+ 5. ~ test/namo_test.rb: + "#[]= parameterised formulae" describe — resolution with arguments through yielded Rows, one definition serving different fields and periods, Enumerable predicates, a one-arity formula referencing a parameterised one, dimension listing, bulk views omitting, explicit values/coordinates/projection/selection raises, carry through contraction, selection (windowing over the filtered rows), and set operators, the one-arity wrapper materialisation idiom, namo-plus-splat formulae materialising in the bulk views, and the empty-Namo case.
123
+ 6. ~ README.md: + Parameterised formulae subsection under Formulae — access-time arguments, the required-parameter scope rule, argument-count enforcement, materialisation behaviour, and the wrapper idiom.
124
+ 7. ~ ROADMAP.md: Promote 0.17.0 to shipped with the dispatch-rule, enforcement, and materialisation rationale; Current state -> 0.17.0; Summary folds in parameterised formulae; next phase -> Namo::Collection (0.18.0). Date bumped.
125
+ 8. ~ COMPARISON.md: Parameterised formulae -> shipped (0.17.0), the entry noting access-time arguments and argument-count enforcement. Date bumped.
126
+ 9. ~ EXAMPLES.md: The finance section's composition blocks (all three Namo stages) corrected from max_by, which returns a Row, to sort_by + last(1), which returns the Namo the 0.14.0 block contract requires the 1.x stage, parameterised sma included, now runs end to end against this release. The finance highlight notes the parameterised formula as shipped (0.17.0). Date bumped.
229
127
  10. ~ Namo::VERSION: /0.16.0/0.17.0/
230
128
 
231
- 20260612
129
+ ## 20260612
130
+
232
131
  0.16.0: ~ data/formula exclusivity — projection drops the formulae it materialises; * and ** raise on a data/formula name collision.
233
132
 
234
- 1. ~ lib/namo.rb: Namo#[]'s positive-projection branch carries @formulae minus the projected
235
- derived names naming a derived dimension materialises it (stored values, computed against
236
- the yielding Namo, windowed over any same-call selection) and drops the formula; omitted
237
- formulae carry live and recompute from the result's own rows. Contraction and selection-only
238
- calls are unchanged. The projection list is the materialise/live selector.
239
- 2. ~ lib/namo.rb: Namo#* and Namo#** raise ArgumentError via the new private
240
- raise_unless_data_formula_exclusivity when one operand's data dimension is the other's
241
- derived dimension, block and no-block forms alike. Formula-vs-formula stays left-wins; the
242
- set operators need no guard (matching-data-dimensions blocks the asymmetric case); the
243
- constructor stays unguarded.
244
- 3. ~ test/namo_test.rb: + "data/formula exclusivity" describe — access-path agreement on a
245
- materialised dimension, dimension-listing, dependent-formula carry, omitted-formula
246
- liveness, live-without-inputs caveat, two-arity windowing at materialisation,
247
- contraction/selection unchanged, subclass type, composition collision raises (both
248
- directions, both operators, block forms), left-wins formula merge, contraction-first
249
- resolution.
250
- 4. ~ test/namo_test.rb: + "range selection" context under #[] — basic range, beginless and
251
- endless forms, range composed with projection, range on a formula-defined dimension
252
- (Row_test holds the predicate matrix; these pin the Namo-level wiring).
253
- 5. ~ README.md: + Projection of derived dimensions subsection under Formulae (naming
254
- materialises, omitting carries live); data/formula collision sentences under Composition
255
- and Cartesian product.
256
- 6. ~ ROADMAP.md: Promote 0.16.0 to shipped; Current state -> 0.16.0; Summary folds in
257
- exclusivity; next phase -> 0.17.0. window.length -> window.count and n.length -> n.count
258
- in the remaining future-release examples (0.17.0, 2.x, 4.x) — Namo has no #length,
259
- extending the 0.15.0 correction.
260
- 7. ~ COMPARISON.md: Repoint the pre-renumbering planned markers to what shipped — proc-based,
261
- regex-based, and mixed selection -> shipped (0.8.0); Enumerable methods return Namos ->
262
- shipped (0.11.0), with the entry summary's parity sentence and the Sorting entry's
263
- "as of 0.14.0" corrected to 0.11.0; values and to_h -> shipped (0.7.0). Aspect classes ->
264
- not planned, the entry rewritten to record 0.7.0's plain-return-types decision (Namo#===
265
- and subclassing cover case dispatch; a Matcher factory can serve a finer split later).
266
- Aggregation repointed from 2.x to group_by returning a Namo::Collection at 0.19.0 (gated
267
- on Collection at 0.18.0), with summary/members examples; bare names stay 2.x. Parameterised
268
- formulae stays planned (0.17.0), its example's window.length -> window.count.
269
- 8. ~ EXAMPLES.md: + Epidemiology / public health section — a cross-row (two-arity) rolling
270
- weekly average in the four-stage format (Polars, then Namo 1.x/2.x/3.x), with the
271
- (row, namo) window over the yielding Namo and a one-arity formula referencing the
272
- two-arity one. window.length -> window.count in the finance 1.x and 2.x stages. Date
273
- bumped to 20260612.
133
+ 1. ~ lib/namo.rb: Namo#[]'s positive-projection branch carries @formulae minus the projected derived names — naming a derived dimension materialises it (stored values, computed against the yielding Namo, windowed over any same-call selection) and drops the formula; omitted formulae carry live and recompute from the result's own rows. Contraction and selection-only calls are unchanged. The projection list is the materialise/live selector.
134
+ 2. ~ lib/namo.rb: Namo#* and Namo#** raise ArgumentError via the new private raise_unless_data_formula_exclusivity when one operand's data dimension is the other's derived dimension, block and no-block forms alike. Formula-vs-formula stays left-wins; the set operators need no guard (matching-data-dimensions blocks the asymmetric case); the constructor stays unguarded.
135
+ 3. ~ test/namo_test.rb: + "data/formula exclusivity" describe — access-path agreement on a materialised dimension, dimension-listing, dependent-formula carry, omitted-formula liveness, live-without-inputs caveat, two-arity windowing at materialisation, contraction/selection unchanged, subclass type, composition collision raises (both directions, both operators, block forms), left-wins formula merge, contraction-first resolution.
136
+ 4. ~ test/namo_test.rb: + "range selection" context under #[] — basic range, beginless and endless forms, range composed with projection, range on a formula-defined dimension (Row_test holds the predicate matrix; these pin the Namo-level wiring).
137
+ 5. ~ README.md: + Projection of derived dimensions subsection under Formulae (naming materialises, omitting carries live); data/formula collision sentences under Composition and Cartesian product.
138
+ 6. ~ ROADMAP.md: Promote 0.16.0 to shipped; Current state -> 0.16.0; Summary folds in exclusivity; next phase -> 0.17.0. window.length -> window.count and n.length -> n.count in the remaining future-release examples (0.17.0, 2.x, 4.x) — Namo has no #length, extending the 0.15.0 correction.
139
+ 7. ~ COMPARISON.md: Repoint the pre-renumbering planned markers to what shipped — proc-based, regex-based, and mixed selection -> shipped (0.8.0); Enumerable methods return Namos -> shipped (0.11.0), with the entry summary's parity sentence and the Sorting entry's "as of 0.14.0" corrected to 0.11.0; values and to_h -> shipped (0.7.0). Aspect classes -> not planned, the entry rewritten to record 0.7.0's plain-return-types decision (Namo#=== and subclassing cover case dispatch; a Matcher factory can serve a finer split later). Aggregation repointed from 2.x to group_by returning a Namo::Collection at 0.19.0 (gated on Collection at 0.18.0), with summary/members examples; bare names stay 2.x. Parameterised formulae stays planned (0.17.0), its example's window.length -> window.count.
140
+ 8. ~ EXAMPLES.md: + Epidemiology / public health section — a cross-row (two-arity) rolling weekly average in the four-stage format (Polars, then Namo 1.x/2.x/3.x), with the (row, namo) window over the yielding Namo and a one-arity formula referencing the two-arity one. window.length -> window.count in the finance 1.x and 2.x stages. Date bumped to 20260612.
274
141
  9. ~ Namo::VERSION: /0.15.0/0.16.0/
275
142
 
276
- 20260612
143
+ ## 20260612
144
+
277
145
  0.15.0: + two-arity formulae — procs with arity 2 receive (row, namo) for cross-row computation.
278
146
 
279
- 1. ~ lib/Namo/Row.rb: Row's constructor gains an optional third parameter, namo (default nil),
280
- stored in @namo the Namo that yielded the Row; the two-argument form keeps working.
281
- Row#[] dispatches on formula arity: exactly 2 calls formula.call(self, @namo), raising
282
- ArgumentError via the new private raise_unless_namo_context when @namo is nil; every
283
- other arity (0, 1, negative) keeps the existing formula.call(self) unchanged.
284
- 2. ~ lib/Namo/Enumerable.rb: every Row construction (each, select and its aliases, reject,
285
- sort_by, first, last, take_while, drop_while, uniq's block form, partition) passes self
286
- as the Row's namo, so two-arity formulae resolve during enumeration and predicate
287
- evaluation, with the yielding Namo as the window.
288
- 3. ~ lib/namo.rb: values_for's derived branch and the * and ** block paths pass self as the
289
- Row's namo — values/coordinates/to_h resolve two-arity dimensions, and composition-block
290
- rows can resolve self's two-arity formulae (extension of the 0.14.0 block contract; the
291
- (Row, Namo) -> Namo contract and result-formulae rule are unchanged).
292
- 4. ~ test/Namo/Row_test.rb: + constructor third-argument and backward-compatibility tests,
293
- arity-dispatch tests (1, 2, 0, negative), the nil-namo ArgumentError, and match? on a
294
- two-arity derived dimension.
295
- 5. ~ test/namo_test.rb: + "#[]= two-arity formulae" describe — SMA cross-row resolution via
296
- values/coordinates/to_h, selection and subset-predicate resolution, first/last Rows,
297
- yielding-Namo (filtered-window) semantics, liveness, operator carry-through, composition-
298
- block resolution, mixed-arity chains, and the empty-Namo case.
299
- 6. ~ README.md: + Cross-row formulae subsection under Formulae, with the SMA example, the
300
- yielding-Namo semantic, and the no-context error.
301
- 7. ~ ROADMAP.md: Promote 0.15.0 to shipped; Current state -> 0.15.0; Summary folds in
302
- two-arity formulae; the example's window.length corrected to window.count (Namo has
303
- no length); "Live computation objects" phrasing updated. + upcoming 0.16.0 section,
304
- Data/formula exclusivity — projection drops the formulae it materialises, * and **
305
- raise on a data/formula name collision — with parameterised formulae renumbered to
306
- 0.17.0, Namo::Collection to 0.18.0, and group_by to 0.19.0; next phase -> 0.16.0.
307
- 8. ~ COMPARISON.md: Two-arity formulae -> shipped (0.15.0) with the same window.count
308
- correction; Parameterised formulae repointed to planned (0.17.0). Date bumped.
147
+ 1. ~ lib/Namo/Row.rb: Row's constructor gains an optional third parameter, namo (default nil), stored in @namo — the Namo that yielded the Row; the two-argument form keeps working. Row#[] dispatches on formula arity: exactly 2 calls formula.call(self, @namo), raising ArgumentError via the new private raise_unless_namo_context when @namo is nil; every other arity (0, 1, negative) keeps the existing formula.call(self) unchanged.
148
+ 2. ~ lib/Namo/Enumerable.rb: every Row construction (each, select and its aliases, reject, sort_by, first, last, take_while, drop_while, uniq's block form, partition) passes self as the Row's namo, so two-arity formulae resolve during enumeration and predicate evaluation, with the yielding Namo as the window.
149
+ 3. ~ lib/namo.rb: values_for's derived branch and the * and ** block paths pass self as the Row's namo values/coordinates/to_h resolve two-arity dimensions, and composition-block rows can resolve self's two-arity formulae (extension of the 0.14.0 block contract; the (Row, Namo) -> Namo contract and result-formulae rule are unchanged).
150
+ 4. ~ test/Namo/Row_test.rb: + constructor third-argument and backward-compatibility tests, arity-dispatch tests (1, 2, 0, negative), the nil-namo ArgumentError, and match? on a two-arity derived dimension.
151
+ 5. ~ test/namo_test.rb: + "#[]= two-arity formulae" describe — SMA cross-row resolution via values/coordinates/to_h, selection and subset-predicate resolution, first/last Rows, yielding-Namo (filtered-window) semantics, liveness, operator carry-through, composition- block resolution, mixed-arity chains, and the empty-Namo case.
152
+ 6. ~ README.md: + Cross-row formulae subsection under Formulae, with the SMA example, the yielding-Namo semantic, and the no-context error.
153
+ 7. ~ ROADMAP.md: Promote 0.15.0 to shipped; Current state -> 0.15.0; Summary folds in two-arity formulae; the example's window.length corrected to window.count (Namo has no length); "Live computation objects" phrasing updated. + upcoming 0.16.0 section, Data/formula exclusivity — projection drops the formulae it materialises, * and ** raise on a data/formula name collision — with parameterised formulae renumbered to 0.17.0, Namo::Collection to 0.18.0, and group_by to 0.19.0; next phase -> 0.16.0.
154
+ 8. ~ COMPARISON.md: Two-arity formulae -> shipped (0.15.0) with the same window.count correction; Parameterised formulae repointed to planned (0.17.0). Date bumped.
309
155
  9. ~ Namo::VERSION: /0.14.0/0.15.0/
310
156
 
311
- 20260608
157
+ ## 20260608
158
+
312
159
  0.14.0: + blocks on the composition operators (*, **) for custom match refinement.
313
160
 
314
- 1. ~ lib/namo.rb: Namo#* takes an optional block. Without a block, behaviour is unchanged.
315
- With a block, the right rows matched on the shared data dimensions are passed as a Namo
316
- (candidates, carrying other's formulae) alongside the left Row (carrying self's
317
- formulae); the block returns a Namo of the rows to pair, each merged into the left row.
318
- An empty returned Namo drops the left row (inner-join semantics). Result formulae are
319
- other.formulae.merge(@formulae) as before — the block does not affect result formulae.
320
- The shared-dimension precondition still applies with a block present.
321
- 2. ~ lib/namo.rb: Namo#** takes an optional block, same contract, with candidates being all
322
- of other's rows (no shared-dimension pre-filter). The disjoint-dimensions precondition
323
- still applies with a block present.
324
- 3. ~ test/namo_test.rb: + "with a block" context under #* (single-match one-row return, empty-return
325
- drop, multi-row return, selection on other's derived dimension, reference to self's
326
- derived dimension, result-formulae parity with no-block, derived-not-stored-but-resolves,
327
- subclass type, preconditions still raise) and under #** (selector filtering one-to-many,
328
- empty-return drop, full-candidates reproduces no-block, selection on other's derived
329
- dimension, result-formulae parity, disjoint precondition still raises, subclass type).
330
- Existing no-block #* and #** tests unchanged and green.
331
- 4. ~ README.md: + block subsections under Composition and Cartesian product, with the
332
- price/quarterly matching and orders/tiers worked examples and the (row, candidates) ->
333
- Namo contract.
334
- 5. ~ ROADMAP.md: Promote 0.14.0 to shipped (composition blocks only); Current state -> 0.14.0;
335
- Summary folds in composition blocks; next phase -> 0.15.0. + the governing principle
336
- (a block form is warranted iff the operation gives consideration to a dimension in
337
- isolation), tied to the orthogonality/efficiency rationale for why set-operator/`/`
338
- blocks were not added. The now-redundant upcoming 0.14.0 section removed.
161
+ 1. ~ lib/namo.rb: Namo#* takes an optional block. Without a block, behaviour is unchanged. With a block, the right rows matched on the shared data dimensions are passed as a Namo (candidates, carrying other's formulae) alongside the left Row (carrying self's formulae); the block returns a Namo of the rows to pair, each merged into the left row. An empty returned Namo drops the left row (inner-join semantics). Result formulae are other.formulae.merge(@formulae) as before — the block does not affect result formulae. The shared-dimension precondition still applies with a block present.
162
+ 2. ~ lib/namo.rb: Namo#** takes an optional block, same contract, with candidates being all of other's rows (no shared-dimension pre-filter). The disjoint-dimensions precondition still applies with a block present.
163
+ 3. ~ test/namo_test.rb: + "with a block" context under #* (single-match one-row return, empty-return drop, multi-row return, selection on other's derived dimension, reference to self's derived dimension, result-formulae parity with no-block, derived-not-stored-but-resolves, subclass type, preconditions still raise) and under #** (selector filtering one-to-many, empty-return drop, full-candidates reproduces no-block, selection on other's derived dimension, result-formulae parity, disjoint precondition still raises, subclass type). Existing no-block #* and #** tests unchanged and green.
164
+ 4. ~ README.md: + block subsections under Composition and Cartesian product, with the price/quarterly matching and orders/tiers worked examples and the (row, candidates) -> Namo contract.
165
+ 5. ~ ROADMAP.md: Promote 0.14.0 to shipped (composition blocks only); Current state -> 0.14.0; Summary folds in composition blocks; next phase -> 0.15.0. + the governing principle (a block form is warranted iff the operation gives consideration to a dimension in isolation), tied to the orthogonality/efficiency rationale for why set-operator/`/` blocks were not added. The now-redundant upcoming 0.14.0 section removed.
339
166
  6. ~ COMPARISON.md: "Conditional join with block" -> shipped (0.14.0).
340
167
  7. ~ Namo::VERSION: /0.13.2/0.14.0/
341
168
 
342
- 20260608
169
+ ## 20260608
170
+
343
171
  0.13.2: Narrow the planned 0.14.0 block-form scope to the composition operators and document the rationale.
344
172
 
345
- 1. ~ ROADMAP.md: Rewrite the 0.14.0 section from "blocks on comparison, composition, and
346
- set operators" to "blocks on composition operators (*, **)". State the governing
347
- principle (a block belongs only where the operator's matching is underdetermined).
348
- Remove the comparison-operator and set-operator block subsections; note that their use
349
- cases are expressed by comparing projections and by projection + proc selection
350
- respectively. State explicitly that / takes no block. Replace "The unifying pattern"
351
- with the composition-only rationale.
173
+ 1. ~ ROADMAP.md: Rewrite the 0.14.0 section from "blocks on comparison, composition, and set operators" to "blocks on composition operators (*, **)". State the governing principle (a block belongs only where the operator's matching is underdetermined). Remove the comparison-operator and set-operator block subsections; note that their use cases are expressed by comparing projections and by projection + proc selection respectively. State explicitly that / takes no block. Replace "The unifying pattern" with the composition-only rationale.
352
174
  2. ~ ROADMAP.md: Rescope the Summary's "next phase" pointer to composition-only block forms.
353
- 3. ~ COMPARISON.md: Replace the "Blocks on set operators" subsection (feature no longer
354
- planned) with a shipped "Keyed anti-join" entry expressing keyed exclusion via projection
355
- + proc selection; repoint the composition "Conditional join with block" entry to planned
356
- (0.14.0). Date bumped to 20260608.
175
+ 3. ~ COMPARISON.md: Replace the "Blocks on set operators" subsection (feature no longer planned) with a shipped "Keyed anti-join" entry expressing keyed exclusion via projection + proc selection; repoint the composition "Conditional join with block" entry to planned (0.14.0). Date bumped to 20260608.
357
176
  4. ~ Namo::VERSION: /0.13.1/0.13.2/
358
177
 
359
- 20260604
178
+ ## 20260604
179
+
360
180
  0.13.1: Correct the row-order claim across the docs and document the same-dimension-order precondition.
361
181
 
362
- 1. ~ README.md: Scope the "row order is ignored" statement to the comparison operators
363
- (==, eql?, ===, <, <=, >, >=) in the Equality section; drop "accident of ingestion".
364
- + Positive note that row order is preserved through to_h/values, each, first, last,
365
- take, drop, and +.
366
- 2. ~ README.md: At Concatenation, state that the set operators require the same dimension
367
- names in the same order, with guidance to normalise inputs to a common column order
368
- before combining.
369
- 3. ~ ROADMAP.md: Scope the 0.6.0 section's "row order is an accident of ingestion" to the
370
- comparison operators; drop "accident of ingestion". Date bumped to 20260604.
182
+ 1. ~ README.md: Scope the "row order is ignored" statement to the comparison operators (==, eql?, ===, <, <=, >, >=) in the Equality section; drop "accident of ingestion". + Positive note that row order is preserved through to_h/values, each, first, last, take, drop, and +.
183
+ 2. ~ README.md: At Concatenation, state that the set operators require the same dimension names in the same order, with guidance to normalise inputs to a common column order before combining.
184
+ 3. ~ ROADMAP.md: Scope the 0.6.0 section's "row order is an accident of ingestion" to the comparison operators; drop "accident of ingestion". Date bumped to 20260604.
371
185
  4. ~ Namo::VERSION: /0.13.0/0.13.1/
372
186
 
373
- 20260601
187
+ ## 20260601
188
+
374
189
  0.13.0: Polymorphic []= — proc registers a formula, scalar broadcasts to every row.
375
190
 
376
191
  1. ~ lib/namo.rb: Namo#[]= now dispatches on value type. Parameter renamed `proc` -> `value`. A Proc registers a formula and clears any data column of the same name (`@data.each{|row| row.delete(name)} if @data.first&.key?(name)`); anything else broadcasts the value to every row and clears any formula of the same name (`@formulae.delete(name)`). Enforces the exclusive-storage invariant — a name is data or derived, never both — at the assignment site. Existing proc-registration behaviour preserved.
@@ -379,7 +194,8 @@ A base Namo's constituents are formularies and rows: << dispatches a Module to a
379
194
  4. ~ ROADMAP.md: Promote 0.13.0 from upcoming to shipped — `## Current state` bumped to 0.13.0, a `### 0.13.0 (2026-06-01)` entry added in the shipped section, the now-redundant `## 0.13.0` upcoming section removed, polymorphic []= folded into the Summary's completed vocabulary, "next phase" pointed at 0.14.0+.
380
195
  5. ~ Namo::VERSION: /0.12.0/0.13.0/
381
196
 
382
- 20260601
197
+ ## 20260601
198
+
383
199
  0.12.0: Constructor widening — keyword data: and name:.
384
200
 
385
201
  1. ~ lib/namo.rb: Namo#initialize widened from `(data = [], formulae: {})` to `(positional_data = nil, data: [], formulae: {}, name: nil)`. Data may now be passed positionally or by the `data:` keyword; positional wins when both are given (`@data = positional_data || data`). The positional default changes from `[]` to `nil` so it acts as a "not supplied" sentinel that lets `|| data` fall through to the keyword path; an explicit `Namo.new([])` still yields `@data == []` because the truthy empty array short-circuits. + `name:` keyword, stored in `@name`. Every pre-existing call site (positional data, positional data + keyword formulae, no-arg, formulae-only, and the operators' internal `self.class.new(rows, formulae: ...)`) is unaffected.
@@ -389,7 +205,8 @@ A base Namo's constituents are formularies and rows: << dispatches a Module to a
389
205
  5. ~ ROADMAP.md: Promote 0.12.0 from upcoming to shipped — `## Current state` bumped to 0.12.0, a `### 0.12.0 (2026-06-01)` entry added in the shipped section (written in shipped voice), and the now-redundant `## 0.12.0` upcoming section removed; fold the widened constructor into the Summary's completed vocabulary and point "next phase" at 0.13.0+.
390
206
  6. ~ Namo::VERSION: /0.11.1/0.12.0/
391
207
 
392
- 20260601
208
+ ## 20260601
209
+
393
210
  0.11.1: Extract the subset Enumerable methods into a Namo::Enumerable module.
394
211
 
395
212
  1. + lib/Namo/Enumerable.rb: New module Namo::Enumerable holding each and the subset-returning Enumerable methods (select plus its filter/find_all aliases, reject, sort_by, first, last, take, drop, take_while, drop_while, uniq, partition), moved verbatim from Namo. The module does `include ::Enumerable` — the leading :: is required, because under the nested `class Namo; module Enumerable` scope a bare `Enumerable` resolves to Namo::Enumerable itself and raises a cyclic-include error.
@@ -398,7 +215,8 @@ A base Namo's constituents are formularies and rows: << dispatches a Module to a
398
215
  4. ~ ROADMAP.md: + note in the 0.11.0 section that, as of 0.11.1, these methods live in the Namo::Enumerable module; bump Date to 20260601.
399
216
  5. ~ Namo::VERSION: /0.11.0/0.11.1/
400
217
 
401
- 20260531
218
+ ## 20260531
219
+
402
220
  0.11.0: ~ Subset Enumerable methods (select, reject, sort_by, first, last, take, drop, take_while, drop_while, uniq, partition) return Namos
403
221
 
404
222
  1. + Namo#select, Namo#reject, Namo#sort_by, Namo#take_while, Namo#drop_while: Predicate and ordering subset methods that wrap the @data Array result in `self.class.new(..., formulae: @formulae.dup)`, shadowing Enumerable's Array-returning defaults. Blocks receive a Row, so formulae resolve inside the predicate, and formulae carry through to the returned Namo. Namo#select is aliased as filter and find_all (the only overridden method with Enumerable aliases), so the aliases return Namos too.
@@ -411,7 +229,8 @@ A base Namo's constituents are formularies and rows: << dispatches a Module to a
411
229
  8. ~ ROADMAP.md: Promote 0.11.0 from upcoming to shipped under "Current state: 0.11.0"; fold the subset Enumerable methods into the Summary's completed vocabulary and point "next phase" at 0.12.0+; bump Date to 20260531.
412
230
  9. ~ Namo::VERSION: /0.10.0/0.11.0/
413
231
 
414
- 20260528
232
+ ## 20260528
233
+
415
234
  0.10.0: + Row value semantics: ==, eql?, hash
416
235
 
417
236
  1. + Namo::Row#==: Data equality. Two Rows are equal iff the other is a Row and their `@row` hashes are `==`. Class identity not gated (Row has no subclass hierarchy with included modules); formulae ignored (a formula is a property of the surrounding Namo, not of the row's identity). Mirrors `Namo#==` one level down.
@@ -422,13 +241,15 @@ A base Namo's constituents are formularies and rows: << dispatches a Module to a
422
241
  6. ~ ROADMAP.md: Promote 0.10.0 from upcoming to shipped under "Current state: 0.10.0"; revise Summary to point "next phase" at 0.11.0+ and note Row value semantics among the algebra-completing pieces.
423
242
  7. ~ Namo::VERSION: /0.9.2/0.10.0/
424
243
 
425
- 20260525
244
+ ## 20260525
245
+
426
246
  0.9.2: ~ Auto-load Namo::VERSION
427
247
 
428
248
  1. ~ lib/namo.rb: + `require_relative './Namo/VERSION'`. Namo::VERSION is now defined when `namo` is required, without needing a separate require.
429
249
  2. ~ Namo::VERSION: /0.9.1/0.9.2/
430
250
 
431
- 20260525
251
+ ## 20260525
252
+
432
253
  0.9.1: ~ Namo#initialize: default data to [], ~ Namo#data_dimensions to handle empty data
433
254
 
434
255
  1. ~ Namo#initialize: Default `data` parameter changed from `nil` to `[]`. Construction with no arguments now produces a usable empty Namo rather than one whose introspection methods crash on `nil`.
@@ -437,7 +258,8 @@ A base Namo's constituents are formularies and rows: << dispatches a Module to a
437
258
  4. ~ test/namo_test.rb: + Empty-Namo tests (dimensions, data_dimensions, formula surfacing on a Namo with no data).
438
259
  5. ~ Namo::VERSION: /0.9.0/0.9.1/
439
260
 
440
- 20260521
261
+ ## 20260521
262
+
441
263
  0.9.0: + composition operators: equi-join (*), Cartesian product (**), decomposition (/)
442
264
 
443
265
  1. + Namo#*: Equi-join on shared data dimensions. Inner-join semantics — unmatched rows from both sides are dropped. Raises ArgumentError ("no shared dimensions, need to have shared dimensions") when operands have no overlap. Preserves duplicates multiplicatively. Formulae merge with self winning on conflict.
@@ -450,7 +272,8 @@ A base Namo's constituents are formularies and rows: << dispatches a Module to a
450
272
  8. ~ COMPARISON.md: /planned (0.9.0)/shipped (0.9.0)/ for Equi-join, Cartesian product, and Decomposition. + Paragraph in the Decomposition entry on the combining-vs-projecting distinction. Date bumped to 20260521.
451
273
  9. ~ Namo::VERSION: /0.8.0/0.9.0/
452
274
 
453
- 20260521
275
+ ## 20260521
276
+
454
277
  0.8.0: + proc and regex-based selection
455
278
 
456
279
  1. ~ Namo::Row#match?: + `when Proc` branch — calls the proc with the dimension value; truthy result selects the row. Predicate receives nil for missing or nil-valued dimensions and decides.
@@ -462,7 +285,8 @@ A base Namo's constituents are formularies and rows: << dispatches a Module to a
462
285
  7. ~ CHANGELOG: Update with 0.8.0's changes and retroactively log the 0.7.0 changes to README (+ Coordinates and values section), COMPARISON.md, and EXAMPLES.md (2 sections relocated from EXAMPLES.md to COMPARISON.md).
463
286
  8. ~ Namo::VERSION: /0.7.0/0.8.0/
464
287
 
465
- 20260520
288
+ ## 20260520
289
+
466
290
  0.7.0: + derived-dimension surfacing, lazy single-column access, live views
467
291
 
468
292
  1. + Namo#data_dimensions: Returns the storage dimensions as a plain Array (keys of the first row).
@@ -482,7 +306,8 @@ A base Namo's constituents are formularies and rows: << dispatches a Module to a
482
306
  15. ~ Rakefile: + -V mainfont=Charter -V monofont=Menlo on pandoc invocation in docs:md2pdf, for a cleaner serif body font and so code spans containing Unicode math glyphs (e.g. ∅) render correctly under xelatex.
483
307
  16. ~ Namo::VERSION: /0.6.0/0.7.0/
484
308
 
485
- 20260511
309
+ ## 20260511
310
+
486
311
  0.6.0: + equality, pattern-match, and subset/superset operators
487
312
 
488
313
  1. + Namo#==: Multiset equality on row data, ignoring class and formulae.
@@ -497,7 +322,8 @@ A base Namo's constituents are formularies and rows: << dispatches a Module to a
497
322
  10. + script/md4print, ~ Rakefile: + rake docs:print, docs:pdf, docs:all for regenerating docs/*.print.md and docs/*.print.pdf.
498
323
  11. ~ Namo::VERSION: /0.5.0/0.6.0/
499
324
 
500
- 20260416
325
+ ## 20260416
326
+
501
327
  0.5.0: + row-axis set operations: intersection (&), union (|), symmetric difference (^)
502
328
 
503
329
  1. + Namo#&: Intersection. Returns rows present in both Namo objects. Requires matching dimensions.
@@ -507,7 +333,8 @@ A base Namo's constituents are formularies and rows: << dispatches a Module to a
507
333
  5. ~ README.md: + Intersection section, + Union section, + Symmetric difference section.
508
334
  6. ~ Namo::VERSION: /0.4.0/0.5.0/
509
335
 
510
- 20260415
336
+ ## 20260415
337
+
511
338
  0.4.0: + concatenation (+) and row removal (-)
512
339
 
513
340
  1. + Namo#+: Concatenate two Namo instances with matching dimensions. Appends rows from the second to the first. Raises ArgumentError when dimensions differ.
@@ -516,7 +343,8 @@ A base Namo's constituents are formularies and rows: << dispatches a Module to a
516
343
  4. ~ README.md: + Concatenation section, + Row removal section.
517
344
  5. ~ Namo::VERSION: /0.3.0/0.4.0/
518
345
 
519
- 20260415
346
+ ## 20260415
347
+
520
348
  0.3.0: + contraction
521
349
 
522
350
  1. + Namo::NegatedDimension: Wrapper for negated dimension names used in contraction.
@@ -527,7 +355,8 @@ A base Namo's constituents are formularies and rows: << dispatches a Module to a
527
355
  6. ~ README.md: + Contraction section
528
356
  7. ~ Namo::VERSION: /0.2.0/0.3.0/
529
357
 
530
- 20260414
358
+ ## 20260414
359
+
531
360
  0.2.0: Include Enumerable.
532
361
 
533
362
  1. ~ Namo: include Enumerable
@@ -540,7 +369,8 @@ A base Namo's constituents are formularies and rows: << dispatches a Module to a
540
369
  8. ~ README.md: + Enumerable section
541
370
  9. ~ Namo::VERSION: /0.1.0/0.2.0/; /module/class/ to match Namo's definition.
542
371
 
543
- 20260328
372
+ ## 20260328
373
+
544
374
  0.1.0: Add formulae via []=. Formulae are procs which receive a Row object, compose via named references, and carry through selection.
545
375
 
546
376
  1. ~ Namo#[]: Refactor to accept *names for projection alongside **selections. Always returns a Namo instance.
@@ -553,14 +383,16 @@ A base Namo's constituents are formularies and rows: << dispatches a Module to a
553
383
  8. ~ namo_test.rb: Add tests for projection, formulae, composition, chained and single-call selection with projection.
554
384
  9. ~ Namo::VERSION: /0.0.1/0.1.0/
555
385
 
556
- 20260315
386
+ ## 20260315
387
+
557
388
  0.0.1: Fix README.md and CHANGELOG.
558
389
 
559
390
  1. ~ README.md: Fix Contributing secton.
560
391
  2. ~ CHANGELOG: Use the same description in the summary as in the commit message.
561
392
  3. ~ Namo::VERSION: /0.0.0/0.0.1/
562
393
 
563
- 20260315
394
+ ## 20260315
395
+
564
396
  0.0.0: The initial release has instantiation from hashes, automatic inferral of dimension names and co-ordinates, and selection of data by a single value, an array, or a range.
565
397
 
566
398
  1. + Namo
data/README.md CHANGED
@@ -768,6 +768,10 @@ Because each method is copied into the store, the rules already governing `[]=`
768
768
  - **Materialise-and-drop on projection.** Naming a formulary method in a projection snapshots its values into a data column *and drops the formula*, exactly as for a `[]=` formula: `flows[:date, :signed_volume]` returns a Namo reporting `:signed_volume` as data, not derived, with all access paths agreeing on the stored snapshot.
769
769
  - **Carry-through.** Selection, the set and composition operators, and a projection that *omits* the name all keep the formula live, computing against the result's own rows — the carry-through is free, because the formulae are ordinary store entries.
770
770
 
771
+ `attach!` is the forceful sibling of `attach`. Where `attach` refuses a data collision, `attach!` **evicts** the colliding data columns first — deleting each one exactly as assigning a proc through `[]=` clears a same-named data column — then attaches. The bang is consent: `attach` won't destroy a column you never named, but `attach!` is you saying you meant to. The motivating case is refreshing a formulary over a *materialised snapshot* — once a derived dimension has been projected into a stored column (materialise-and-drop, above), re-attaching the formulary that defined it collides with the now-data column, and `attach!` re-attaches over it in one step where `attach` would require you to contract the column away first. `<<` stays the guarded form: a module through `<<` routes to `attach`, so the operator never evicts — only an explicit `attach!` call does.
772
+
773
+ Note that there is no counterpart in `[]=`'s scalar-over-formula eviction and is worth naming: a formulary method that reads a column its own name evicts will **recurse** on access. The eviction removes the datum, and exclusive storage leaves no shadowed value behind the formula, so `Row#[]` re-enters the formula rather than falling back to data — a `def close(row); row[:close] * ...; end` attached with `attach!` over a `:close` data column has no `:close` data left to read. `[]=` rarely bites this way because you write the proc at the call site, beside the name it replaces; a formulary is authored elsewhere, so a method reading its own evicted column is easy to attach without noticing. Self-reference is unguarded here as everywhere in the formula mechanism — keep a formulary's inputs and outputs under distinct names, or `attach` (not `attach!`) and contract deliberately.
774
+
771
775
  Two Namos that expose the same names — one via `[]=`, one via a formulary — are `===`; a Namo with a formulary attached is not `===` to a vanilla Namo of the same data dimensions.
772
776
 
773
777
  Formulary methods take `row` explicitly and index it (`row[:buys]`), the same as `[]=` formulae. Resolving bare names inside a method body (`buys` for `row[:buys]`) and memoising derived values on a frozen Namo arrive in a later release; until then a formulary method is a plain function of its `row` (and `namo`).
data/lib/Namo/VERSION.rb CHANGED
@@ -2,5 +2,5 @@
2
2
  # Namo::VERSION
3
3
 
4
4
  class Namo
5
- VERSION = '0.24.1'
5
+ VERSION = '0.25.1'
6
6
  end
data/lib/namo.rb CHANGED
@@ -89,7 +89,7 @@ class Namo
89
89
  end
90
90
 
91
91
  def attach(modul)
92
- collisions = modul.public_instance_methods(false) & data_dimensions
92
+ collisions = attach_collisions(modul)
93
93
  unless collisions.empty?
94
94
  raise ArgumentError, "formulary methods collide with data dimensions: #{collisions.inspect}"
95
95
  end
@@ -97,6 +97,12 @@ class Namo
97
97
  self
98
98
  end
99
99
 
100
+ def attach!(modul)
101
+ attach_collisions(modul).each{|name| @data.each{|row| row.delete(name)}}
102
+ @formulae.attach(modul)
103
+ self
104
+ end
105
+
100
106
  def <<(constituent)
101
107
  case constituent
102
108
  when Module then attach(constituent)
@@ -268,6 +274,10 @@ class Namo
268
274
  self
269
275
  end
270
276
 
277
+ def attach_collisions(modul)
278
+ modul.public_instance_methods(false) & data_dimensions
279
+ end
280
+
271
281
  def attach_included_formularies
272
282
  self.class.ancestors.reverse.each do |modul|
273
283
  next if modul.is_a?(Class) || !modul.include?(Namo::Formulary)
data/test/namo_test.rb CHANGED
@@ -747,6 +747,58 @@ describe Namo do
747
747
  end
748
748
  end
749
749
 
750
+ describe "#attach!" do
751
+ it "attaches as attach does for a non-colliding module" do
752
+ namo = Namo.new(flow_data)
753
+ _(namo.attach!(delta)).must_be_same_as namo
754
+ _(namo.values(:signed_volume)).must_equal [20, -40, 0]
755
+ end
756
+
757
+ it "evicts a colliding data column and registers the formulary method in its place" do
758
+ widened = Namo.new(flow_data.map{|row| row.merge(signed_volume: 999)})
759
+ widened.attach!(delta)
760
+ _(widened.data_dimensions).wont_include :signed_volume
761
+ _(widened.derived_dimensions).must_include :signed_volume
762
+ _(widened.dimensions.count(:signed_volume)).must_equal 1
763
+ end
764
+
765
+ it "agrees across all access paths on the evicted-then-derived name" do
766
+ widened = Namo.new(flow_data.map{|row| row.merge(signed_volume: 999)})
767
+ widened.attach!(delta)
768
+ _(widened.values(:signed_volume)).must_equal [20, -40, 0]
769
+ _(widened.entries.first[:signed_volume]).must_equal 20
770
+ _(widened[signed_volume: 20].values(:date)).must_equal [1]
771
+ end
772
+
773
+ it "evicts every colliding column when several collide" do
774
+ clash = Module.new do
775
+ include Namo::Formulary
776
+ def buys(row); row[:date] * 2; end
777
+ def sells(row); row[:date] * 3; end
778
+ end
779
+ namo = Namo.new(flow_data)
780
+ namo.attach!(clash)
781
+ _(namo.data_dimensions).wont_include :buys
782
+ _(namo.data_dimensions).wont_include :sells
783
+ _(namo.derived_dimensions).must_include :buys
784
+ _(namo.derived_dimensions).must_include :sells
785
+ _(namo.values(:buys)).must_equal [2, 4, 6]
786
+ _(namo.values(:sells)).must_equal [3, 6, 9]
787
+ end
788
+
789
+ it "leaves the data untouched when the module does not collide" do
790
+ namo = Namo.new(flow_data)
791
+ namo.attach!(delta)
792
+ _(namo.values(:buys)).must_equal [60, 80, 75]
793
+ _(namo.values(:sells)).must_equal [40, 120, 75]
794
+ end
795
+
796
+ it "raises through attach (the guarded form) where attach! evicts" do
797
+ widened = Namo.new(flow_data.map{|row| row.merge(signed_volume: 999)})
798
+ _(proc{widened.attach(delta)}).must_raise ArgumentError
799
+ end
800
+ end
801
+
750
802
  describe "#<<" do
751
803
  it "attaches a formulary, the same as #attach" do
752
804
  namo = Namo.new(flow_data)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: namo
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.24.1
4
+ version: 0.25.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - thoran