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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4a7dd953b56fb4f692b0e92822cb6d5dcf9a38f8645a913aedc06333441ad305
4
- data.tar.gz: b492295ead6b4a9ea5647290eef7d008e86e6cbc0275176f45f2ea76b1692854
3
+ metadata.gz: 9dfca5e33dc24464a1e109e6b2a32ff5d85fd171c24497832ae5bda3f91e07dc
4
+ data.tar.gz: d6f4fc0cf6db0dfd349e77588d186c42ebeb0fffea32e55771549e4114f224c2
5
5
  SHA512:
6
- metadata.gz: 35a4a0acebfa4f01b83948c7a1639606cb58f9de7c0e75c8cf5886654128526cbe5554e4670503b74b5687674da00929a1b3252ee7bf30d2ecb7db976b580de5
7
- data.tar.gz: ce62a04e0e1a7db2e1ec1a5f20674e000baadf3de64186ea1afe2718d8acdd36189e612aa14c6ca71a8e6330009aa0bfa380b797020ee9fbb69a6a49e9390e90
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 — concatenating Namo objects with different dimensions raises an `ArgumentError`. Formulae carry through from the left-hand side.
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 is ignored (it's an accident of ingestion, not data), but row multiplicities count (they *are* data). The same stance carries across the equality, pattern-match, and subset/superset operators below.
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
@@ -2,5 +2,5 @@
2
2
  # Namo::VERSION
3
3
 
4
4
  class Namo
5
- VERSION = '0.12.0'
5
+ VERSION = '0.13.1'
6
6
  end
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, proc)
78
- @formulae[name] = proc
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 = []
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.12.0
4
+ version: 0.13.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - thoran