namo 0.12.0 → 0.13.0

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: ed83092f20799b8589b970ffa06ba8d9c976dc25e4545670e72e600895273548
4
+ data.tar.gz: ad2b6d939418fe8c0197fdc415a0f884179b374009a3397480b9e25e1814b993
5
5
  SHA512:
6
- metadata.gz: 35a4a0acebfa4f01b83948c7a1639606cb58f9de7c0e75c8cf5886654128526cbe5554e4670503b74b5687674da00929a1b3252ee7bf30d2ecb7db976b580de5
7
- data.tar.gz: ce62a04e0e1a7db2e1ec1a5f20674e000baadf3de64186ea1afe2718d8acdd36189e612aa14c6ca71a8e6330009aa0bfa380b797020ee9fbb69a6a49e9390e90
6
+ metadata.gz: 791f6bd3afa3589546df37e9c6fb7b016de75a6c368202554b53cbc55b92e23c7e9821837ce2057ac8ea40b7f13ef42ec493bfcd061667fa3d293a217608996b
7
+ data.tar.gz: ec205a0df5900931bdf2df644522cffdbd7ef53d6d665fc8fa0ca3b18e79fc5e97971e0c089ea7254e446124866db91d43eed91dda9b0230b5a6f8d6abb9ada3
data/CHANGELOG CHANGED
@@ -1,6 +1,15 @@
1
1
  CHANGELOG
2
2
  _________
3
3
 
4
+ 20260601
5
+ 0.13.0: Polymorphic []= — proc registers a formula, scalar broadcasts to every row.
6
+
7
+ 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.
8
+ 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.
9
+ 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.
10
+ 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+.
11
+ 5. ~ Namo::VERSION: /0.12.0/0.13.0/
12
+
4
13
  20260601
5
14
  0.12.0: Constructor widening — keyword data: and name:.
6
15
 
data/README.md CHANGED
@@ -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.0'
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.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - thoran