namo 0.1.0 → 0.3.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 +24 -0
- data/README.md +70 -1
- data/lib/Namo/NegatedDimension.rb +14 -0
- data/lib/Namo/Row.rb +36 -0
- data/lib/Namo/VERSION.rb +2 -2
- data/lib/Symbol.rb +8 -0
- data/lib/namo.rb +30 -36
- data/namo.gemspec +14 -5
- data/test/Namo/NegatedDimension_test.rb +13 -0
- data/test/Namo/Row_test.rb +69 -0
- data/test/{namo_test.rb → Namo_test.rb} +105 -0
- data/test/Symbol_test.rb +16 -0
- metadata +9 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a0cb29939a05de5108211f0d9f89b8c6b3cd0826433199f3f4014f1201a28fee
|
|
4
|
+
data.tar.gz: d628a872b860561da5c9f02867123864508c34c3311e7d9962457146226d1071
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 72b7db10583b8316b0e727b4af8404c2069533049c40e83a3974841b8a1822139898922492f721e31328632b32582c8795a50fb9a53262bee04fa5bc75672713
|
|
7
|
+
data.tar.gz: b269559c27dd1999ba2c3fb638c85abc0ee22530d984ffe620b2ac9e3fa1726655ac7c7d26c96273baf1bd1396df6ff0eeaeaf7c4e71753aee588eabdb09417f
|
data/CHANGELOG
CHANGED
|
@@ -1,6 +1,30 @@
|
|
|
1
1
|
CHANGELOG
|
|
2
2
|
_________
|
|
3
3
|
|
|
4
|
+
20260415
|
|
5
|
+
0.3.0: + contraction
|
|
6
|
+
|
|
7
|
+
1. + Namo::NegatedDimension: Wrapper for negated dimension names used in contraction.
|
|
8
|
+
2. + Symbol#-@: Produce a NegatedDimension, enabling -:date syntax.
|
|
9
|
+
3. ~ Namo#[]: Extend to recognise NegatedDimension for contraction (remove named dimensions, keep everything else). Raise ArgumentError when mixing projection and contraction.
|
|
10
|
+
4. > Namo::Row: Extract to Namo/Row.rb.
|
|
11
|
+
5. ~ namo_test.rb: Add tests for contraction, mixed-mode error, and contraction with selection and formulae. Split tests into one file per class: Row_test.rb, NegatedDimension_test.rb, Symbol_test.rb.
|
|
12
|
+
6. ~ README.md: + Contraction section
|
|
13
|
+
7. ~ Namo::VERSION: /0.2.0/0.3.0/
|
|
14
|
+
|
|
15
|
+
20260414
|
|
16
|
+
0.2.0: Include Enumerable.
|
|
17
|
+
|
|
18
|
+
1. ~ Namo: include Enumerable
|
|
19
|
+
2. + Namo#each: Yield Row objects wrapping each data row with formulae. Returns an Enumerator when no block is given.
|
|
20
|
+
3. ~ Namo#[]: Refactor to use Enumerable#select and Row#match? instead of select_rows.
|
|
21
|
+
4. - Namo#select_rows: Replaced by Row#match?.
|
|
22
|
+
5. + Namo::Row#match?: Selection logic moved from Namo to Row.
|
|
23
|
+
6. + Namo::Row#to_h: Return the underlying row hash.
|
|
24
|
+
7. ~ namo_test.rb: Add tests for #each and Enumerable methods (map, reduce, min_by, flat_map).
|
|
25
|
+
8. ~ README.md: + Enumerable section
|
|
26
|
+
9. ~ Namo::VERSION: /0.1.0/0.2.0/; /module/class/ to match Namo's definition.
|
|
27
|
+
|
|
4
28
|
20260328
|
|
5
29
|
0.1.0: Add formulae via []=. Formulae are procs which receive a Row object, compose via named references, and carry through selection.
|
|
6
30
|
|
data/README.md
CHANGED
|
@@ -111,7 +111,47 @@ sales[:quarter, :price, product: 'Widget']
|
|
|
111
111
|
# ]>
|
|
112
112
|
```
|
|
113
113
|
|
|
114
|
-
|
|
114
|
+
### Contraction
|
|
115
|
+
|
|
116
|
+
Contraction is the complement of projection. Projection says "keep these dimensions"; contraction says "remove these dimensions, keep everything else":
|
|
117
|
+
|
|
118
|
+
```ruby
|
|
119
|
+
sales[-:price, -:quantity]
|
|
120
|
+
# => #<Namo [
|
|
121
|
+
# {product: 'Widget', quarter: 'Q1'},
|
|
122
|
+
# {product: 'Widget', quarter: 'Q2'},
|
|
123
|
+
# {product: 'Gadget', quarter: 'Q1'},
|
|
124
|
+
# {product: 'Gadget', quarter: 'Q2'}
|
|
125
|
+
# ]>
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
The `-:price` syntax uses unary minus on Symbol to produce a negated dimension. Mixing projection and contraction in the same call is an error — the two modes are mutually exclusive:
|
|
129
|
+
|
|
130
|
+
```ruby
|
|
131
|
+
sales[:product, -:price] # => ArgumentError
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Selection and contraction can be chained:
|
|
135
|
+
|
|
136
|
+
```ruby
|
|
137
|
+
sales[product: 'Widget'][-:price, -:quantity]
|
|
138
|
+
# => #<Namo [
|
|
139
|
+
# {product: 'Widget', quarter: 'Q1'},
|
|
140
|
+
# {product: 'Widget', quarter: 'Q2'}
|
|
141
|
+
# ]>
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Or combined in a single call (names before selectors):
|
|
145
|
+
|
|
146
|
+
```ruby
|
|
147
|
+
sales[-:price, -:quantity, product: 'Widget']
|
|
148
|
+
# => #<Namo [
|
|
149
|
+
# {product: 'Widget', quarter: 'Q1'},
|
|
150
|
+
# {product: 'Widget', quarter: 'Q2'}
|
|
151
|
+
# ]>
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Selection, projection, and contraction always return a new Namo instance, so everything chains.
|
|
115
155
|
|
|
116
156
|
### Formulae
|
|
117
157
|
|
|
@@ -156,6 +196,35 @@ sales[product: 'Widget'][:revenue, :quarter]
|
|
|
156
196
|
|
|
157
197
|
Formulae carry through selection — a filtered Namo instance remembers its formulae.
|
|
158
198
|
|
|
199
|
+
### Enumerable
|
|
200
|
+
|
|
201
|
+
Namo includes `Enumerable`, so `each`, `reduce`, `map`, `select`, `min_by`, and all the rest work out of the box. Rows are yielded as `Row` objects, so formulae are accessible during enumeration:
|
|
202
|
+
|
|
203
|
+
```ruby
|
|
204
|
+
sales.reduce(0){|sum, row| sum + row[:quantity]}
|
|
205
|
+
# => 350
|
|
206
|
+
|
|
207
|
+
sales[product: 'Widget'].reduce(0){|sum, row| sum + row[:quantity]}
|
|
208
|
+
# => 250
|
|
209
|
+
|
|
210
|
+
sales[:revenue] = proc{|row| row[:price] * row[:quantity]}
|
|
211
|
+
|
|
212
|
+
sales.reduce(0){|sum, row| sum + row[:revenue]}
|
|
213
|
+
# => 5000.0
|
|
214
|
+
|
|
215
|
+
sales[product: 'Widget'].reduce(0){|sum, row| sum + row[:revenue]}
|
|
216
|
+
# => 2500.0
|
|
217
|
+
|
|
218
|
+
sales.map{|row| row[:product]}
|
|
219
|
+
# => ['Widget', 'Widget', 'Gadget', 'Gadget']
|
|
220
|
+
|
|
221
|
+
sales.min_by{|row| row[:price]}[:product]
|
|
222
|
+
# => 'Widget'
|
|
223
|
+
|
|
224
|
+
sales.flat_map{|row| [row[:price]]}
|
|
225
|
+
# => [10.0, 10.0, 25.0, 25.0]
|
|
226
|
+
```
|
|
227
|
+
|
|
159
228
|
### Extracting data
|
|
160
229
|
|
|
161
230
|
`to_a` returns an array of hashes:
|
data/lib/Namo/Row.rb
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Namo/Row.rb
|
|
2
|
+
# Namo::Row
|
|
3
|
+
|
|
4
|
+
class Namo
|
|
5
|
+
class Row
|
|
6
|
+
def [](name)
|
|
7
|
+
if @formulae.key?(name)
|
|
8
|
+
@formulae[name].call(self)
|
|
9
|
+
else
|
|
10
|
+
@row[name]
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def match?(selections)
|
|
15
|
+
selections.all? do |dimension, coordinate|
|
|
16
|
+
case coordinate
|
|
17
|
+
when Array, Range
|
|
18
|
+
coordinate.include?(self[dimension])
|
|
19
|
+
else
|
|
20
|
+
self[dimension] == coordinate
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def to_h
|
|
26
|
+
@row
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def initialize(row, formulae)
|
|
32
|
+
@row = row
|
|
33
|
+
@formulae = formulae
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
data/lib/Namo/VERSION.rb
CHANGED
data/lib/Symbol.rb
ADDED
data/lib/namo.rb
CHANGED
|
@@ -1,23 +1,12 @@
|
|
|
1
1
|
# namo.rb
|
|
2
2
|
# Namo
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
if @formulae.key?(name)
|
|
8
|
-
@formulae[name].call(self)
|
|
9
|
-
else
|
|
10
|
-
@row[name]
|
|
11
|
-
end
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
private
|
|
4
|
+
require_relative 'Namo/NegatedDimension'
|
|
5
|
+
require_relative 'Namo/Row'
|
|
6
|
+
require_relative 'Symbol'
|
|
15
7
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
@formulae = formulae
|
|
19
|
-
end
|
|
20
|
-
end
|
|
8
|
+
class Namo
|
|
9
|
+
include Enumerable
|
|
21
10
|
|
|
22
11
|
attr_accessor :data
|
|
23
12
|
attr_accessor :formulae
|
|
@@ -35,20 +24,38 @@ class Namo
|
|
|
35
24
|
end
|
|
36
25
|
|
|
37
26
|
def [](*names, **selections)
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
names.each_with_object({}){|name, hash| hash[name] = row[name]}
|
|
43
|
-
end
|
|
27
|
+
rows = selections.any? ? select{|row| row.match?(selections)} : entries
|
|
28
|
+
negated, positive = names.partition{|n| n.is_a?(NegatedDimension)}
|
|
29
|
+
if negated.any? && positive.any?
|
|
30
|
+
raise ArgumentError, "cannot mix projection and contraction in a single call"
|
|
44
31
|
end
|
|
45
|
-
|
|
32
|
+
projected = (
|
|
33
|
+
if negated.any?
|
|
34
|
+
excluded = negated.map(&:name)
|
|
35
|
+
kept = dimensions - excluded
|
|
36
|
+
rows.map do |row|
|
|
37
|
+
kept.each_with_object({}){|name, hash| hash[name] = row[name]}
|
|
38
|
+
end
|
|
39
|
+
elsif positive.any?
|
|
40
|
+
rows.map do |row|
|
|
41
|
+
positive.each_with_object({}){|name, hash| hash[name] = row[name]}
|
|
42
|
+
end
|
|
43
|
+
else
|
|
44
|
+
rows.map(&:to_h)
|
|
45
|
+
end
|
|
46
|
+
)
|
|
47
|
+
self.class.new(projected, formulae: @formulae.dup)
|
|
46
48
|
end
|
|
47
49
|
|
|
48
50
|
def []=(name, proc)
|
|
49
51
|
@formulae[name] = proc
|
|
50
52
|
end
|
|
51
53
|
|
|
54
|
+
def each(&block)
|
|
55
|
+
return enum_for(:each) unless block_given?
|
|
56
|
+
@data.each{|row_data| block.call(Row.new(row_data, @formulae))}
|
|
57
|
+
end
|
|
58
|
+
|
|
52
59
|
def to_a
|
|
53
60
|
@data.map do |row|
|
|
54
61
|
row.keys.each_with_object({}) do |key, hash|
|
|
@@ -63,17 +70,4 @@ class Namo
|
|
|
63
70
|
@data = data
|
|
64
71
|
@formulae = formulae
|
|
65
72
|
end
|
|
66
|
-
|
|
67
|
-
def select_rows(selections)
|
|
68
|
-
@data.select do |row|
|
|
69
|
-
selections.all? do |dimension, coordinate|
|
|
70
|
-
case coordinate
|
|
71
|
-
when Array, Range
|
|
72
|
-
coordinate.include?(row[dimension])
|
|
73
|
-
else
|
|
74
|
-
row[dimension] == coordinate
|
|
75
|
-
end
|
|
76
|
-
end
|
|
77
|
-
end
|
|
78
|
-
end
|
|
79
73
|
end
|
data/namo.gemspec
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
require_relative './lib/Namo/VERSION'
|
|
2
2
|
|
|
3
|
+
class Gem::Specification
|
|
4
|
+
def development_dependencies=(gems)
|
|
5
|
+
gems.each{|gem| add_development_dependency(*gem)}
|
|
6
|
+
end
|
|
7
|
+
end
|
|
8
|
+
|
|
3
9
|
Gem::Specification.new do |spec|
|
|
4
10
|
spec.name = 'namo'
|
|
5
11
|
spec.version = Namo::VERSION
|
|
@@ -13,20 +19,23 @@ Gem::Specification.new do |spec|
|
|
|
13
19
|
spec.license = 'MIT'
|
|
14
20
|
|
|
15
21
|
spec.required_ruby_version = '>= 2.7'
|
|
22
|
+
|
|
16
23
|
spec.require_paths = ['lib']
|
|
17
24
|
|
|
18
25
|
spec.files = [
|
|
26
|
+
'namo.gemspec',
|
|
19
27
|
'CHANGELOG',
|
|
20
28
|
'Gemfile',
|
|
21
|
-
Dir['lib/**/*.rb'],
|
|
22
29
|
'LICENSE',
|
|
23
|
-
'namo.gemspec',
|
|
24
30
|
'Rakefile',
|
|
25
31
|
'README.md',
|
|
32
|
+
Dir['lib/**/*.rb'],
|
|
26
33
|
Dir['test/**/*.rb'],
|
|
27
34
|
].flatten
|
|
28
35
|
|
|
29
|
-
spec.
|
|
30
|
-
|
|
31
|
-
|
|
36
|
+
spec.development_dependencies = %w{
|
|
37
|
+
minitest
|
|
38
|
+
minitest-spec-context
|
|
39
|
+
rake
|
|
40
|
+
}
|
|
32
41
|
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
require 'minitest/autorun'
|
|
2
|
+
require 'minitest-spec-context'
|
|
3
|
+
|
|
4
|
+
require_relative '../../lib/namo'
|
|
5
|
+
|
|
6
|
+
describe Namo::NegatedDimension do
|
|
7
|
+
describe "#name" do
|
|
8
|
+
it "returns the original symbol" do
|
|
9
|
+
nd = Namo::NegatedDimension.new(:price)
|
|
10
|
+
_(nd.name).must_equal :price
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
require 'minitest/autorun'
|
|
2
|
+
require 'minitest-spec-context'
|
|
3
|
+
|
|
4
|
+
require_relative '../../lib/namo'
|
|
5
|
+
|
|
6
|
+
describe Namo::Row do
|
|
7
|
+
let(:row_data) do
|
|
8
|
+
{product: 'Widget', quarter: 'Q1', price: 10.0, quantity: 100}
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
let(:formulae) do
|
|
12
|
+
{}
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
let(:row) do
|
|
16
|
+
Namo::Row.new(row_data, formulae)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
describe "#[]" do
|
|
20
|
+
it "returns raw data by dimension name" do
|
|
21
|
+
_(row[:product]).must_equal 'Widget'
|
|
22
|
+
_(row[:price]).must_equal 10.0
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it "returns nil for missing dimensions" do
|
|
26
|
+
_(row[:missing]).must_be_nil
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it "resolves formulae over raw data" do
|
|
30
|
+
formulae[:revenue] = proc{|r| r[:price] * r[:quantity]}
|
|
31
|
+
_(row[:revenue]).must_equal 1000.0
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it "composes formulae" do
|
|
35
|
+
formulae[:revenue] = proc{|r| r[:price] * r[:quantity]}
|
|
36
|
+
formulae[:cost] = proc{|r| r[:quantity] * 4.0}
|
|
37
|
+
formulae[:profit] = proc{|r| r[:revenue] - r[:cost]}
|
|
38
|
+
_(row[:profit]).must_equal 600.0
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
describe "#match?" do
|
|
43
|
+
it "matches a single value" do
|
|
44
|
+
_(row.match?(product: 'Widget')).must_equal true
|
|
45
|
+
_(row.match?(product: 'Gadget')).must_equal false
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it "matches an array of values" do
|
|
49
|
+
_(row.match?(product: ['Widget', 'Gadget'])).must_equal true
|
|
50
|
+
_(row.match?(product: ['Gadget'])).must_equal false
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it "matches a range" do
|
|
54
|
+
_(row.match?(price: 5.0..15.0)).must_equal true
|
|
55
|
+
_(row.match?(price: 20.0..30.0)).must_equal false
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it "matches multiple dimensions" do
|
|
59
|
+
_(row.match?(product: 'Widget', quarter: 'Q1')).must_equal true
|
|
60
|
+
_(row.match?(product: 'Widget', quarter: 'Q2')).must_equal false
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
describe "#to_h" do
|
|
65
|
+
it "returns the underlying row hash" do
|
|
66
|
+
_(row.to_h).must_equal row_data
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -85,6 +85,53 @@ describe Namo do
|
|
|
85
85
|
_(result.to_a).must_equal [{price: 10.0}, {price: 10.0}]
|
|
86
86
|
end
|
|
87
87
|
end
|
|
88
|
+
|
|
89
|
+
context "contraction" do
|
|
90
|
+
it "removes named dimensions" do
|
|
91
|
+
result = sales[-:price, -:quantity]
|
|
92
|
+
_(result.dimensions).must_equal [:product, :quarter]
|
|
93
|
+
_(result.to_a.count).must_equal 4
|
|
94
|
+
_(result.to_a.first).must_equal({product: 'Widget', quarter: 'Q1'})
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
it "removes a single dimension" do
|
|
98
|
+
result = sales[-:price]
|
|
99
|
+
_(result.dimensions).must_equal [:product, :quarter, :quantity]
|
|
100
|
+
_(result.to_a.count).must_equal 4
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
it "raises when mixing projection and contraction" do
|
|
104
|
+
_ { sales[:product, -:price] }.must_raise ArgumentError
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
it "carries formulae through contraction" do
|
|
108
|
+
sales[:label] = proc{|r| "#{r[:product]}-#{r[:quarter]}"}
|
|
109
|
+
result = sales[-:price, -:quantity]
|
|
110
|
+
_(result.map{|row| row[:label]}).must_equal [
|
|
111
|
+
'Widget-Q1', 'Widget-Q2', 'Gadget-Q1', 'Gadget-Q2'
|
|
112
|
+
]
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
context "selection and contraction" do
|
|
117
|
+
it "can use them together" do
|
|
118
|
+
result = sales[-:price, -:quantity, product: 'Widget']
|
|
119
|
+
_(result.to_a.count).must_equal 2
|
|
120
|
+
_(result.to_a).must_equal [
|
|
121
|
+
{product: 'Widget', quarter: 'Q1'},
|
|
122
|
+
{product: 'Widget', quarter: 'Q2'}
|
|
123
|
+
]
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
it "can chain them" do
|
|
127
|
+
result = sales[product: 'Widget'][-:price, -:quantity]
|
|
128
|
+
_(result.to_a.count).must_equal 2
|
|
129
|
+
_(result.to_a).must_equal [
|
|
130
|
+
{product: 'Widget', quarter: 'Q1'},
|
|
131
|
+
{product: 'Widget', quarter: 'Q2'}
|
|
132
|
+
]
|
|
133
|
+
end
|
|
134
|
+
end
|
|
88
135
|
end
|
|
89
136
|
|
|
90
137
|
describe "#[]= formulae" do
|
|
@@ -146,6 +193,64 @@ describe Namo do
|
|
|
146
193
|
end
|
|
147
194
|
end
|
|
148
195
|
|
|
196
|
+
describe "#each" do
|
|
197
|
+
it "yields Row objects" do
|
|
198
|
+
rows = []
|
|
199
|
+
sales.each{|row| rows << row}
|
|
200
|
+
_(rows.first).must_be_kind_of Namo::Row
|
|
201
|
+
_(rows.length).must_equal 4
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
it "yields rows with access to data" do
|
|
205
|
+
products = sales.map{|row| row[:product]}
|
|
206
|
+
_(products).must_equal ['Widget', 'Widget', 'Gadget', 'Gadget']
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
it "yields rows with access to formulae" do
|
|
210
|
+
sales[:revenue] = proc{|r| r[:price] * r[:quantity]}
|
|
211
|
+
revenues = sales.map{|row| row[:revenue]}
|
|
212
|
+
_(revenues).must_equal [1000.0, 1500.0, 1000.0, 1500.0]
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
it "returns an enumerator without a block" do
|
|
216
|
+
_(sales.each).must_be_kind_of Enumerator
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
describe "Enumerable" do
|
|
221
|
+
it "supports reduce" do
|
|
222
|
+
total_quantity = sales.reduce(0){|sum, row| sum + row[:quantity]}
|
|
223
|
+
_(total_quantity).must_equal 350
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
it "supports reduce with selection" do
|
|
227
|
+
widget_quantity = sales[product: 'Widget'].reduce(0){|sum, row| sum + row[:quantity]}
|
|
228
|
+
_(widget_quantity).must_equal 250
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
it "supports reduce with formulae" do
|
|
232
|
+
sales[:revenue] = proc{|r| r[:price] * r[:quantity]}
|
|
233
|
+
total_revenue = sales.reduce(0){|sum, row| sum + row[:revenue]}
|
|
234
|
+
_(total_revenue).must_equal 5000.0
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
it "supports reduce with selection and formulae" do
|
|
238
|
+
sales[:revenue] = proc{|r| r[:price] * r[:quantity]}
|
|
239
|
+
widget_revenue = sales[product: 'Widget'].reduce(0){|sum, row| sum + row[:revenue]}
|
|
240
|
+
_(widget_revenue).must_equal 2500.0
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
it "supports min_by" do
|
|
244
|
+
cheapest = sales.min_by{|row| row[:price]}
|
|
245
|
+
_(cheapest[:product]).must_equal 'Widget'
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
it "supports flat_map" do
|
|
249
|
+
prices = sales.flat_map{|row| [row[:price]]}
|
|
250
|
+
_(prices).must_equal [10.0, 10.0, 25.0, 25.0]
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
|
|
149
254
|
describe "#to_a" do
|
|
150
255
|
it "returns the data as an array of hashes" do
|
|
151
256
|
_(sales.to_a).must_equal sample_data
|
data/test/Symbol_test.rb
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
require 'minitest/autorun'
|
|
2
|
+
require 'minitest-spec-context'
|
|
3
|
+
|
|
4
|
+
require_relative '../lib/namo'
|
|
5
|
+
|
|
6
|
+
describe Symbol do
|
|
7
|
+
describe "#-@" do
|
|
8
|
+
it "returns a NegatedDimension" do
|
|
9
|
+
_(-:price).must_be_kind_of Namo::NegatedDimension
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
it "preserves the symbol name" do
|
|
13
|
+
_((-:price).name).must_equal :price
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
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.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- thoran
|
|
@@ -64,10 +64,16 @@ files:
|
|
|
64
64
|
- LICENSE
|
|
65
65
|
- README.md
|
|
66
66
|
- Rakefile
|
|
67
|
+
- lib/Namo/NegatedDimension.rb
|
|
68
|
+
- lib/Namo/Row.rb
|
|
67
69
|
- lib/Namo/VERSION.rb
|
|
70
|
+
- lib/Symbol.rb
|
|
68
71
|
- lib/namo.rb
|
|
69
72
|
- namo.gemspec
|
|
70
|
-
- test/
|
|
73
|
+
- test/Namo/NegatedDimension_test.rb
|
|
74
|
+
- test/Namo/Row_test.rb
|
|
75
|
+
- test/Namo_test.rb
|
|
76
|
+
- test/Symbol_test.rb
|
|
71
77
|
homepage: https://github.com/thoran/namo
|
|
72
78
|
licenses:
|
|
73
79
|
- MIT
|
|
@@ -86,7 +92,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
86
92
|
- !ruby/object:Gem::Version
|
|
87
93
|
version: '0'
|
|
88
94
|
requirements: []
|
|
89
|
-
rubygems_version: 4.0.
|
|
95
|
+
rubygems_version: 4.0.10
|
|
90
96
|
specification_version: 4
|
|
91
97
|
summary: Named dimensional data for Ruby.
|
|
92
98
|
test_files: []
|