namo 0.12.0 → 0.13.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 +4 -4
- data/CHANGELOG +23 -0
- data/README.md +38 -2
- data/lib/Namo/VERSION.rb +1 -1
- data/lib/namo.rb +9 -2
- data/test/namo_test.rb +87 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9dfca5e33dc24464a1e109e6b2a32ff5d85fd171c24497832ae5bda3f91e07dc
|
|
4
|
+
data.tar.gz: d6f4fc0cf6db0dfd349e77588d186c42ebeb0fffea32e55771549e4114f224c2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 46a4fcb9ec264f6ef718225995b4e83aeac410a0ba3c5a2c8591640ea89e6b9af4ee2a25e4ec91d739e54e159eea192a592dae2732166339206f66a93307631d
|
|
7
|
+
data.tar.gz: 867849106103a6cdeaea5c1a67eba12d4d4b4d2c5ea439b7c669fd723847998668d33834c0f5cf58305aeb01016fd875d064d149141e8e9efba355897e2855da
|
data/CHANGELOG
CHANGED
|
@@ -1,6 +1,29 @@
|
|
|
1
1
|
CHANGELOG
|
|
2
2
|
_________
|
|
3
3
|
|
|
4
|
+
20260604
|
|
5
|
+
0.13.1: Correct the row-order claim across the docs and document the same-dimension-order precondition.
|
|
6
|
+
|
|
7
|
+
1. ~ README.md: Scope the "row order is ignored" statement to the comparison operators
|
|
8
|
+
(==, eql?, ===, <, <=, >, >=) in the Equality section; drop "accident of ingestion".
|
|
9
|
+
+ Positive note that row order is preserved through to_h/values, each, first, last,
|
|
10
|
+
take, drop, and +.
|
|
11
|
+
2. ~ README.md: At Concatenation, state that the set operators require the same dimension
|
|
12
|
+
names in the same order, with guidance to normalise inputs to a common column order
|
|
13
|
+
before combining.
|
|
14
|
+
3. ~ ROADMAP.md: Scope the 0.6.0 section's "row order is an accident of ingestion" to the
|
|
15
|
+
comparison operators; drop "accident of ingestion". Date bumped to 20260604.
|
|
16
|
+
4. ~ Namo::VERSION: /0.13.0/0.13.1/
|
|
17
|
+
|
|
18
|
+
20260601
|
|
19
|
+
0.13.0: Polymorphic []= — proc registers a formula, scalar broadcasts to every row.
|
|
20
|
+
|
|
21
|
+
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.
|
|
22
|
+
2. ~ test/namo_test.rb: + "#[]= polymorphic dispatch" describe — proc registration (regression), data-column clear on proc assign, scalar broadcast, formula clear on scalar assign, array-broadcasts-as-value, both last-write-wins orderings, derived/data surfacing exactly once in `dimensions`, the no-data-column clear-guard short-circuit, and the empty-Namo case. Existing "#[]= formulae" describe unchanged.
|
|
23
|
+
3. ~ README.md: + Polymorphic []= section (two branches, exclusivity rule, last-write-wins, symmetry with []), with the scalar-broadcast-vs-formula contrast and a note tying exclusivity to data_dimensions/derived_dimensions.
|
|
24
|
+
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+.
|
|
25
|
+
5. ~ Namo::VERSION: /0.12.0/0.13.0/
|
|
26
|
+
|
|
4
27
|
20260601
|
|
5
28
|
0.12.0: Constructor widening — keyword data: and name:.
|
|
6
29
|
|
data/README.md
CHANGED
|
@@ -226,7 +226,7 @@ all_sales = q1_sales + q2_sales
|
|
|
226
226
|
# ]>
|
|
227
227
|
```
|
|
228
228
|
|
|
229
|
-
The dimensions must match —
|
|
229
|
+
The dimensions must match — the same dimension names in the same order — or the operator raises an `ArgumentError`. Two Namos holding the same columns in a different order must be normalised to a common column order before they can be combined. Formulae carry through from the left-hand side.
|
|
230
230
|
|
|
231
231
|
### Row Removal
|
|
232
232
|
|
|
@@ -449,7 +449,7 @@ This is the same pattern that makes `Array#-` useful with arrays that aren't sub
|
|
|
449
449
|
|
|
450
450
|
### Equality
|
|
451
451
|
|
|
452
|
-
Comparison on Namos is **multiset-theoretic on rows**: row order
|
|
452
|
+
Comparison on Namos is **multiset-theoretic on rows**: the comparison operators — `==`, `eql?`, `===`, `<`, `<=`, `>`, `>=` — ignore row order, so two Namos holding the same rows in a different order compare equal, while row multiplicities count (they *are* data). That stance is shared across the equality, pattern-match, and subset/superset operators documented below. Row order is otherwise preserved: `to_h` and `values` depend on it for columnar alignment, and `each`, `first`, `last`, `take`, `drop`, and `+` all observe it. It is the comparison operators alone that treat the sequence of rows as a multiset.
|
|
453
453
|
|
|
454
454
|
`==` is multiset equality on rows. Class and formulae are ignored; row order is ignored; row multiplicities are not.
|
|
455
455
|
|
|
@@ -585,6 +585,42 @@ sales[product: 'Widget'][:revenue, :quarter]
|
|
|
585
585
|
|
|
586
586
|
Formulae carry through selection — a filtered Namo instance remembers its formulae.
|
|
587
587
|
|
|
588
|
+
### Polymorphic `[]=`
|
|
589
|
+
|
|
590
|
+
`[]=` dispatches on the type of the value assigned. A proc registers a formula, as above. Anything else broadcasts the value to every row:
|
|
591
|
+
|
|
592
|
+
```ruby
|
|
593
|
+
sales[:status] = 'active'
|
|
594
|
+
sales.values(:status)
|
|
595
|
+
# => ['active', 'active', 'active', 'active']
|
|
596
|
+
|
|
597
|
+
sales[:revenue] = proc{|row| row[:price] * row[:quantity]}
|
|
598
|
+
sales.values(:revenue)
|
|
599
|
+
# => [1000.0, 1500.0, 1000.0, 1500.0]
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
The two branches mirror the polymorphism `[]` already has on the selection side, where a single bracket call dispatches over exact values, arrays, ranges, procs, and regexes. Rather than introduce a separate `broadcast` or `set_all` method for the scalar case, `[]=` reads the same way for both: `sales[:status] = 'active'` says "set status to active across this Namo," and `sales[:revenue] = proc{…}` says "derive revenue from each row."
|
|
603
|
+
|
|
604
|
+
The two branches enforce **exclusive storage**: a name is either a data dimension or a derived dimension, never both. Assigning a proc clears any data column of that name; assigning anything else clears any formula of that name. The last write wins, and there is no shadowing:
|
|
605
|
+
|
|
606
|
+
```ruby
|
|
607
|
+
sales[:x] = 5 # :x is a broadcast data value
|
|
608
|
+
sales[:x] = proc{|row| row[:price]} # :x is now a formula — the broadcast value is gone
|
|
609
|
+
|
|
610
|
+
sales[:x] = proc{|row| row[:price]} # :x is a formula
|
|
611
|
+
sales[:x] = 5 # :x is now a broadcast data value — the formula is gone
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
Exclusivity ties directly to the inspection vocabulary: a name assigned a scalar shows up in `data_dimensions`; a name assigned a proc shows up in `derived_dimensions`; never in both, so it appears in `dimensions` exactly once.
|
|
615
|
+
|
|
616
|
+
Only a `Proc` takes the formula branch. An array is a value like any other, so it broadcasts as the per-row value rather than registering as a formula:
|
|
617
|
+
|
|
618
|
+
```ruby
|
|
619
|
+
sales[:weights] = [1, 2, 3]
|
|
620
|
+
sales.values(:weights)
|
|
621
|
+
# => [[1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3]]
|
|
622
|
+
```
|
|
623
|
+
|
|
588
624
|
### Coordinates and values
|
|
589
625
|
|
|
590
626
|
`dimensions` covers the *queryable namespace* — every name you can ask for, whether it lives in the row data or is computed by a formula. Once formulae are defined, they appear alongside data dimensions:
|
data/lib/Namo/VERSION.rb
CHANGED
data/lib/namo.rb
CHANGED
|
@@ -74,8 +74,15 @@ class Namo
|
|
|
74
74
|
self.class.new(projected, formulae: @formulae.dup)
|
|
75
75
|
end
|
|
76
76
|
|
|
77
|
-
def []=(name,
|
|
78
|
-
|
|
77
|
+
def []=(name, value)
|
|
78
|
+
case value
|
|
79
|
+
when Proc
|
|
80
|
+
@data.each{|row| row.delete(name)} if @data.first&.key?(name)
|
|
81
|
+
@formulae[name] = value
|
|
82
|
+
else
|
|
83
|
+
@formulae.delete(name)
|
|
84
|
+
@data.each{|row| row[name] = value}
|
|
85
|
+
end
|
|
79
86
|
end
|
|
80
87
|
|
|
81
88
|
def +(other)
|
data/test/namo_test.rb
CHANGED
|
@@ -560,6 +560,93 @@ describe Namo do
|
|
|
560
560
|
end
|
|
561
561
|
end
|
|
562
562
|
|
|
563
|
+
describe "#[]= polymorphic dispatch" do
|
|
564
|
+
it "registers a formula when assigned a proc (existing behaviour preserved)" do
|
|
565
|
+
namo = Namo.new([{price: 10.0, quantity: 100}])
|
|
566
|
+
namo[:revenue] = proc{|r| r[:price] * r[:quantity]}
|
|
567
|
+
_(namo.derived_dimensions).must_include :revenue
|
|
568
|
+
_(namo.values(:revenue)).must_equal [1000.0]
|
|
569
|
+
end
|
|
570
|
+
|
|
571
|
+
it "clears any data column of the same name when assigned a proc" do
|
|
572
|
+
namo = Namo.new([{x: 1}, {x: 2}])
|
|
573
|
+
_(namo.data_dimensions).must_include :x
|
|
574
|
+
namo[:x] = proc{|r| 99}
|
|
575
|
+
_(namo.data_dimensions).wont_include :x
|
|
576
|
+
_(namo.derived_dimensions).must_include :x
|
|
577
|
+
_(namo.values(:x)).must_equal [99, 99]
|
|
578
|
+
end
|
|
579
|
+
|
|
580
|
+
it "broadcasts a scalar to every row" do
|
|
581
|
+
namo = Namo.new([{a: 1}, {a: 2}, {a: 3}])
|
|
582
|
+
namo[:status] = 'active'
|
|
583
|
+
_(namo.values(:status)).must_equal ['active', 'active', 'active']
|
|
584
|
+
end
|
|
585
|
+
|
|
586
|
+
it "clears any formula of the same name when assigned a scalar" do
|
|
587
|
+
namo = Namo.new([{price: 10.0, quantity: 100}])
|
|
588
|
+
namo[:revenue] = proc{|r| r[:price] * r[:quantity]}
|
|
589
|
+
namo[:revenue] = 0
|
|
590
|
+
_(namo.derived_dimensions).wont_include :revenue
|
|
591
|
+
_(namo.data_dimensions).must_include :revenue
|
|
592
|
+
_(namo.values(:revenue)).must_equal [0]
|
|
593
|
+
end
|
|
594
|
+
|
|
595
|
+
it "broadcasts an array as the value (array is not a proc)" do
|
|
596
|
+
namo = Namo.new([{a: 1}, {a: 2}])
|
|
597
|
+
namo[:weights] = [1, 2, 3]
|
|
598
|
+
_(namo.values(:weights)).must_equal [[1, 2, 3], [1, 2, 3]]
|
|
599
|
+
end
|
|
600
|
+
|
|
601
|
+
it "is last-write-wins: scalar then proc leaves a formula only" do
|
|
602
|
+
namo = Namo.new([{y: 7}])
|
|
603
|
+
namo[:x] = 5
|
|
604
|
+
namo[:x] = proc{|r| r[:y]}
|
|
605
|
+
_(namo.derived_dimensions).must_include :x
|
|
606
|
+
_(namo.data_dimensions).wont_include :x
|
|
607
|
+
_(namo.values(:x)).must_equal [7]
|
|
608
|
+
end
|
|
609
|
+
|
|
610
|
+
it "is last-write-wins: proc then scalar leaves a broadcast value only" do
|
|
611
|
+
namo = Namo.new([{y: 7}])
|
|
612
|
+
namo[:x] = proc{|r| r[:y]}
|
|
613
|
+
namo[:x] = 5
|
|
614
|
+
_(namo.data_dimensions).must_include :x
|
|
615
|
+
_(namo.derived_dimensions).wont_include :x
|
|
616
|
+
_(namo.values(:x)).must_equal [5]
|
|
617
|
+
end
|
|
618
|
+
|
|
619
|
+
it "surfaces a proc-assigned name as derived, not data, exactly once in dimensions" do
|
|
620
|
+
namo = Namo.new([{x: 1}, {x: 2}])
|
|
621
|
+
namo[:x] = proc{|r| 99}
|
|
622
|
+
_(namo.data_dimensions).wont_include :x
|
|
623
|
+
_(namo.derived_dimensions).must_include :x
|
|
624
|
+
_(namo.dimensions.count(:x)).must_equal 1
|
|
625
|
+
end
|
|
626
|
+
|
|
627
|
+
it "surfaces a scalar-assigned name as data, not derived, exactly once in dimensions" do
|
|
628
|
+
namo = Namo.new([{x: 1}])
|
|
629
|
+
namo[:rev] = proc{|r| r[:x]}
|
|
630
|
+
namo[:rev] = 5
|
|
631
|
+
_(namo.data_dimensions).must_include :rev
|
|
632
|
+
_(namo.derived_dimensions).wont_include :rev
|
|
633
|
+
_(namo.dimensions.count(:rev)).must_equal 1
|
|
634
|
+
end
|
|
635
|
+
|
|
636
|
+
it "does not walk rows to clear when no data column of that name exists" do
|
|
637
|
+
namo = Namo.new([{a: 1}])
|
|
638
|
+
namo[:b] = proc{|r| r[:a]}
|
|
639
|
+
_(namo.derived_dimensions).must_include :b
|
|
640
|
+
_(namo.values(:b)).must_equal [1]
|
|
641
|
+
end
|
|
642
|
+
|
|
643
|
+
it "handles an empty Namo without error" do
|
|
644
|
+
namo = Namo.new([])
|
|
645
|
+
namo[:x] = proc{|r| 1}
|
|
646
|
+
_(namo.derived_dimensions).must_include :x
|
|
647
|
+
end
|
|
648
|
+
end
|
|
649
|
+
|
|
563
650
|
describe "#each" do
|
|
564
651
|
it "yields Row objects" do
|
|
565
652
|
rows = []
|