namo 0.11.1 → 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 +4 -4
- data/CHANGELOG +19 -0
- data/README.md +79 -0
- data/lib/Namo/VERSION.rb +1 -1
- data/lib/namo.rb +13 -4
- data/test/namo_test.rb +194 -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: ed83092f20799b8589b970ffa06ba8d9c976dc25e4545670e72e600895273548
|
|
4
|
+
data.tar.gz: ad2b6d939418fe8c0197fdc415a0f884179b374009a3397480b9e25e1814b993
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 791f6bd3afa3589546df37e9c6fb7b016de75a6c368202554b53cbc55b92e23c7e9821837ce2057ac8ea40b7f13ef42ec493bfcd061667fa3d293a217608996b
|
|
7
|
+
data.tar.gz: ec205a0df5900931bdf2df644522cffdbd7ef53d6d665fc8fa0ca3b18e79fc5e97971e0c089ea7254e446124866db91d43eed91dda9b0230b5a6f8d6abb9ada3
|
data/CHANGELOG
CHANGED
|
@@ -1,6 +1,25 @@
|
|
|
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
|
+
|
|
13
|
+
20260601
|
|
14
|
+
0.12.0: Constructor widening — keyword data: and name:.
|
|
15
|
+
|
|
16
|
+
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.
|
|
17
|
+
2. + lib/namo.rb: `attr_accessor :name`, alongside the existing `:data` and `:formulae` accessors. Gives `name` reading and `name=` post-construction mutation. Operator results carry `name == nil` (the operators construct without `name:`), establishing the subclass guard convention: subclasses guard `initialize` side effects with `return unless name`, so operator-derived instances skip them and only explicitly-named constructions fire them.
|
|
18
|
+
3. ~ test/namo_test.rb: + "construction" describe (positional data, positional + keyword formulae, no-arg empty, formulae-only, explicit `[]` honoured over the nil sentinel, keyword `data:`, positional-wins-over-keyword, and round-trips through a set operator and an Enumerable method). + "#name" describe (stored from keyword, defaults to nil, settable post-construction, nil on set-operator and Enumerable-derived results). + "subclass side-effect guard" describe — an anonymous Class.new(Namo) with a `return unless name`-guarded counter, asserting it fires for a named construction and stays untouched for an unnamed construction and an operator result.
|
|
19
|
+
4. ~ README.md: + note in the Usage section that data may be passed positionally or by `data:`, with positional winning when both are given. + "Named Namos" section: the `name:` keyword, the `name`/`name=` accessor, nil-on-derivation for operator results, and the subclass guard convention with the TradingAnalysis / argument-less `super` example.
|
|
20
|
+
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+.
|
|
21
|
+
6. ~ Namo::VERSION: /0.11.1/0.12.0/
|
|
22
|
+
|
|
4
23
|
20260601
|
|
5
24
|
0.11.1: Extract the subset Enumerable methods into a Namo::Enumerable module.
|
|
6
25
|
|
data/README.md
CHANGED
|
@@ -33,6 +33,16 @@ sales = Namo.new([
|
|
|
33
33
|
])
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
+
Data may be passed positionally, as above, or by the `data:` keyword where that reads more explicitly:
|
|
37
|
+
|
|
38
|
+
```ruby
|
|
39
|
+
sales = Namo.new(data: [
|
|
40
|
+
{product: 'Widget', quarter: 'Q1', price: 10.0, quantity: 100}
|
|
41
|
+
])
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
When both are given, the positional argument wins and the keyword `data:` is ignored.
|
|
45
|
+
|
|
36
46
|
Dimensions and coordinates are inferred:
|
|
37
47
|
|
|
38
48
|
```ruby
|
|
@@ -575,6 +585,42 @@ sales[product: 'Widget'][:revenue, :quarter]
|
|
|
575
585
|
|
|
576
586
|
Formulae carry through selection — a filtered Namo instance remembers its formulae.
|
|
577
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
|
+
|
|
578
624
|
### Coordinates and values
|
|
579
625
|
|
|
580
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:
|
|
@@ -715,6 +761,39 @@ sales[:product, :quarter, :revenue].to_h
|
|
|
715
761
|
# }
|
|
716
762
|
```
|
|
717
763
|
|
|
764
|
+
### Named Namos
|
|
765
|
+
|
|
766
|
+
A Namo can carry a name, passed by the `name:` keyword and read or set through the `name` accessor:
|
|
767
|
+
|
|
768
|
+
```ruby
|
|
769
|
+
sales = Namo.new(data: rows, name: :sales)
|
|
770
|
+
sales.name
|
|
771
|
+
# => :sales
|
|
772
|
+
|
|
773
|
+
sales.name = :renamed
|
|
774
|
+
```
|
|
775
|
+
|
|
776
|
+
A name defaults to `nil`, and operator results are name-less by design — the result of `+`, `*`, `select`, and the rest is a derived object, not the original, so giving it the parent's name would mislead:
|
|
777
|
+
|
|
778
|
+
```ruby
|
|
779
|
+
(sales + more).name
|
|
780
|
+
# => nil
|
|
781
|
+
```
|
|
782
|
+
|
|
783
|
+
This `nil`-on-derivation behaviour is what lets subclasses with side effects in `initialize` guard those effects on the name. Operator-derived instances are name-less and skip the side effects; explicitly constructed instances pass `name:` and the side effects fire:
|
|
784
|
+
|
|
785
|
+
```ruby
|
|
786
|
+
class TradingAnalysis < Namo
|
|
787
|
+
def initialize(positional_data = nil, data: [], formulae: {}, name: nil)
|
|
788
|
+
super
|
|
789
|
+
return unless name
|
|
790
|
+
register_indicators
|
|
791
|
+
end
|
|
792
|
+
end
|
|
793
|
+
```
|
|
794
|
+
|
|
795
|
+
`super` with no parentheses forwards every argument — positional and keyword — to `Namo#initialize` unchanged. The `return unless name` guard means a subclass need not override every operator to stop the result of `*` or `select` from re-running its construction side effects: it guards on `name` instead.
|
|
796
|
+
|
|
718
797
|
## Why?
|
|
719
798
|
|
|
720
799
|
Every other multi-dimensional array library requires you to pre-shape your data before you can work with it. Namo takes it in the form it likely already comes in.
|
data/lib/Namo/VERSION.rb
CHANGED
data/lib/namo.rb
CHANGED
|
@@ -12,6 +12,7 @@ class Namo
|
|
|
12
12
|
|
|
13
13
|
attr_accessor :data
|
|
14
14
|
attr_accessor :formulae
|
|
15
|
+
attr_accessor :name
|
|
15
16
|
|
|
16
17
|
def dimensions
|
|
17
18
|
data_dimensions + derived_dimensions
|
|
@@ -73,8 +74,15 @@ class Namo
|
|
|
73
74
|
self.class.new(projected, formulae: @formulae.dup)
|
|
74
75
|
end
|
|
75
76
|
|
|
76
|
-
def []=(name,
|
|
77
|
-
|
|
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
|
|
78
86
|
end
|
|
79
87
|
|
|
80
88
|
def +(other)
|
|
@@ -246,8 +254,9 @@ class Namo
|
|
|
246
254
|
end
|
|
247
255
|
end
|
|
248
256
|
|
|
249
|
-
def initialize(
|
|
250
|
-
@data = data
|
|
257
|
+
def initialize(positional_data = nil, data: [], formulae: {}, name: nil)
|
|
258
|
+
@data = positional_data || data
|
|
251
259
|
@formulae = formulae
|
|
260
|
+
@name = name
|
|
252
261
|
end
|
|
253
262
|
end
|
data/test/namo_test.rb
CHANGED
|
@@ -33,6 +33,113 @@ describe Namo do
|
|
|
33
33
|
end
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
+
describe "construction" do
|
|
37
|
+
it "accepts positional data" do
|
|
38
|
+
_(Namo.new([{x: 1}]).data).must_equal [{x: 1}]
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
it "accepts positional data with keyword formulae" do
|
|
42
|
+
namo = Namo.new([{x: 1}], formulae: {y: proc{|r| r[:x] * 2}})
|
|
43
|
+
_(namo.data).must_equal [{x: 1}]
|
|
44
|
+
_(namo.values(:y)).must_equal [2]
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
it "produces an empty Namo with no arguments" do
|
|
48
|
+
namo = Namo.new
|
|
49
|
+
_(namo.data).must_equal []
|
|
50
|
+
_(namo.formulae).must_equal({})
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it "accepts keyword formulae with no data" do
|
|
54
|
+
namo = Namo.new(formulae: {y: proc{|r| r[:x] * 2}})
|
|
55
|
+
_(namo.data).must_equal []
|
|
56
|
+
_(namo.derived_dimensions).must_equal [:y]
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
it "honours an explicit empty positional array over the nil sentinel" do
|
|
60
|
+
_(Namo.new([]).data).must_equal []
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
it "accepts data by keyword" do
|
|
64
|
+
_(Namo.new(data: [{x: 1}]).data).must_equal [{x: 1}]
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it "lets positional data win when both positional and keyword data are given" do
|
|
68
|
+
_(Namo.new([{x: 1}], data: [{x: 2}]).data).must_equal [{x: 1}]
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
it "survives a round-trip through a set operator" do
|
|
72
|
+
a = Namo.new([{x: 1}])
|
|
73
|
+
b = Namo.new([{x: 2}])
|
|
74
|
+
_((a + b).data).must_equal [{x: 1}, {x: 2}]
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
it "survives a round-trip through an Enumerable method" do
|
|
78
|
+
namo = Namo.new([{x: 1}, {x: 2}])
|
|
79
|
+
_(namo.select{|row| row[:x] > 1}.data).must_equal [{x: 2}]
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
describe "#name" do
|
|
84
|
+
it "stores a name passed by keyword" do
|
|
85
|
+
_(Namo.new([{x: 1}], name: :foo).name).must_equal :foo
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
it "defaults to nil when no name is passed" do
|
|
89
|
+
_(Namo.new([{x: 1}]).name).must_be_nil
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
it "is settable post-construction" do
|
|
93
|
+
namo = Namo.new([{x: 1}])
|
|
94
|
+
namo.name = :bar
|
|
95
|
+
_(namo.name).must_equal :bar
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
it "is nil on a Namo derived from a set operator" do
|
|
99
|
+
a = Namo.new([{x: 1}], name: :a)
|
|
100
|
+
b = Namo.new([{x: 2}], name: :b)
|
|
101
|
+
_((a + b).name).must_be_nil
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
it "is nil on a Namo derived from an Enumerable method" do
|
|
105
|
+
namo = Namo.new([{x: 1}, {x: 2}], name: :original)
|
|
106
|
+
_(namo.select{|row| row[:x] > 1}.name).must_be_nil
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
describe "subclass side-effect guard" do
|
|
111
|
+
before do
|
|
112
|
+
@guard_class = Class.new(Namo) do
|
|
113
|
+
def self.fired
|
|
114
|
+
@fired ||= []
|
|
115
|
+
end
|
|
116
|
+
def initialize(positional_data = nil, data: [], formulae: {}, name: nil)
|
|
117
|
+
super
|
|
118
|
+
return unless name
|
|
119
|
+
self.class.fired << name
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
it "fires guarded side effects for an explicitly named construction" do
|
|
125
|
+
@guard_class.new(data: [{x: 1}], name: :foo)
|
|
126
|
+
_(@guard_class.fired).must_equal [:foo]
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
it "skips guarded side effects for an unnamed construction" do
|
|
130
|
+
@guard_class.new(data: [{x: 1}])
|
|
131
|
+
_(@guard_class.fired).must_equal []
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
it "skips guarded side effects for an operator result" do
|
|
135
|
+
a = @guard_class.new(data: [{x: 1}], name: :a)
|
|
136
|
+
b = @guard_class.new(data: [{x: 2}], name: :b)
|
|
137
|
+
@guard_class.fired.clear
|
|
138
|
+
_((a + b).name).must_be_nil
|
|
139
|
+
_(@guard_class.fired).must_equal []
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
36
143
|
describe "#dimensions" do
|
|
37
144
|
it "infers dimensions from hash keys" do
|
|
38
145
|
_(sales.dimensions).must_equal [:product, :quarter, :price, :quantity]
|
|
@@ -453,6 +560,93 @@ describe Namo do
|
|
|
453
560
|
end
|
|
454
561
|
end
|
|
455
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
|
+
|
|
456
650
|
describe "#each" do
|
|
457
651
|
it "yields Row objects" do
|
|
458
652
|
rows = []
|