namo 0.4.0 → 0.6.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.
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.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - thoran
@@ -72,8 +72,8 @@ files:
72
72
  - namo.gemspec
73
73
  - test/Namo/NegatedDimension_test.rb
74
74
  - test/Namo/Row_test.rb
75
- - test/Namo_test.rb
76
75
  - test/Symbol_test.rb
76
+ - test/namo_test.rb
77
77
  homepage: https://github.com/thoran/namo
78
78
  licenses:
79
79
  - MIT
@@ -92,7 +92,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
92
92
  - !ruby/object:Gem::Version
93
93
  version: '0'
94
94
  requirements: []
95
- rubygems_version: 4.0.10
95
+ rubygems_version: 4.0.11
96
96
  specification_version: 4
97
97
  summary: Named dimensional data for Ruby.
98
98
  test_files: []
data/test/Namo_test.rb DELETED
@@ -1,345 +0,0 @@
1
- require 'minitest/autorun'
2
- require 'minitest-spec-context'
3
-
4
- require_relative '../lib/namo'
5
-
6
- describe Namo do
7
- let(:sample_data) do
8
- [
9
- {product: 'Widget', quarter: 'Q1', price: 10.0, quantity: 100},
10
- {product: 'Widget', quarter: 'Q2', price: 10.0, quantity: 150},
11
- {product: 'Gadget', quarter: 'Q1', price: 25.0, quantity: 40},
12
- {product: 'Gadget', quarter: 'Q2', price: 25.0, quantity: 60}
13
- ]
14
- end
15
-
16
- let(:sales) do
17
- Namo.new(sample_data)
18
- end
19
-
20
- describe "#dimensions" do
21
- it "infers dimensions from hash keys" do
22
- _(sales.dimensions).must_equal [:product, :quarter, :price, :quantity]
23
- end
24
- end
25
-
26
- describe "#coordinates" do
27
- it "extracts unique values for each dimension" do
28
- _(sales.coordinates).must_equal ({
29
- product: ['Widget', 'Gadget'],
30
- quarter: ['Q1', 'Q2'],
31
- price: [10.0, 25.0],
32
- quantity: [100, 150, 40, 60]
33
- })
34
- _(sales.coordinates[:product]).must_equal ['Widget', 'Gadget']
35
- _(sales.coordinates[:quarter]).must_equal ['Q1', 'Q2']
36
- end
37
- end
38
-
39
- describe "#[]" do
40
- context "selection" do
41
- it "selects by single coordinate" do
42
- result = sales[product: 'Widget']
43
- _(result.coordinates[:product]).must_equal ['Widget']
44
- _(result.to_a.count).must_equal 2
45
- _(result.to_a.map{|row| row[:product]}).must_equal ['Widget', 'Widget']
46
- end
47
-
48
- it "selects by array of coordinates" do
49
- result = sales[quarter: ['Q1']]
50
- _(result.coordinates[:quarter]).must_equal ['Q1']
51
- _(result.to_a.count).must_equal 2
52
- end
53
-
54
- it "selects by multiple dimensions" do
55
- result = sales[product: 'Widget', quarter: 'Q1']
56
- _(result.to_a.count).must_equal 1
57
- _(result.to_a).must_equal [{product: 'Widget', quarter: 'Q1', price: 10.0, quantity: 100}]
58
- end
59
-
60
- it "returns a Namo instance" do
61
- result = sales[product: 'Widget']
62
- _(result.coordinates[:product]).must_equal ['Widget']
63
- end
64
- end
65
-
66
- context "projection" do
67
- it "projects to named dimensions" do
68
- result = sales[:product, :price]
69
- _(result.dimensions).must_equal [:product, :price]
70
- _(result.coordinates[:product]).must_equal ['Widget', 'Gadget']
71
- _(result.to_a.count).must_equal 4
72
- end
73
- end
74
-
75
- context "selection and projection" do
76
- it "can use them together" do
77
- result = sales[:price, product: 'Widget']
78
- _(result.to_a.count).must_equal 2
79
- _(result.to_a).must_equal [{price: 10.0}, {price: 10.0}]
80
- end
81
-
82
- it "can chain them" do
83
- result = sales[product: 'Widget'][:price]
84
- _(result.to_a.count).must_equal 2
85
- _(result.to_a).must_equal [{price: 10.0}, {price: 10.0}]
86
- end
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
135
- end
136
-
137
- describe "#[]= formulae" do
138
- it "defines a formula" do
139
- sales[:revenue] = proc{|r| r[:price] * r[:quantity]}
140
- _(sales[:product, :quarter, :revenue].to_a).must_equal [
141
- {product: "Widget", quarter: "Q1", revenue: 1000.0},
142
- {product: "Widget", quarter: "Q2", revenue: 1500.0},
143
- {product: "Gadget", quarter: "Q1", revenue: 1000.0},
144
- {product: "Gadget", quarter: "Q2", revenue: 1500.0}
145
- ]
146
- end
147
-
148
- it "composes formulae" do
149
- sales[:revenue] = proc{|r| r[:price] * r[:quantity]}
150
- sales[:cost] = proc{|r| r[:quantity] * 4.0}
151
- sales[:profit] = proc{|r| r[:revenue] - r[:cost]}
152
- _(sales[:product, :quarter, :profit].to_a).must_equal [
153
- {product: "Widget", quarter: "Q1", profit: 600.0},
154
- {product: "Widget", quarter: "Q2", profit: 900.0},
155
- {product: "Gadget", quarter: "Q1", profit: 840.0},
156
- {product: "Gadget", quarter: "Q2", profit: 1260.0}
157
- ]
158
- end
159
-
160
- it "works with chained selection and projection" do
161
- sales[:revenue] = proc{|r| r[:price] * r[:quantity]}
162
- result = sales[product: 'Widget'][:product, :quarter, :revenue]
163
- _(result.to_a).must_equal [
164
- {product: "Widget", quarter: "Q1", revenue: 1000.0},
165
- {product: "Widget", quarter: "Q2", revenue: 1500.0}
166
- ]
167
- end
168
-
169
- it "works with single-call selection and projection" do
170
- sales[:revenue] = proc{|r| r[:price] * r[:quantity]}
171
- result = sales[:product, :revenue, product: 'Widget']
172
- _(result.to_a).must_equal [
173
- {product: "Widget", revenue: 1000.0},
174
- {product: "Widget", revenue: 1500.0}
175
- ]
176
- end
177
-
178
- it "carries formulae through selection" do
179
- sales[:revenue] = proc{|r| r[:price] * r[:quantity]}
180
- widgets = sales[product: 'Widget']
181
- _(widgets[:revenue].to_a).must_equal [{revenue: 1000.0}, {revenue: 1500.0}]
182
- end
183
-
184
- it "projects formula with context dimensions" do
185
- sales[:revenue] = proc{|r| r[:price] * r[:quantity]}
186
- result = sales[:product, :quarter, :revenue]
187
- _(result.to_a).must_equal [
188
- {product: 'Widget', quarter: 'Q1', revenue: 1000.0},
189
- {product: 'Widget', quarter: 'Q2', revenue: 1500.0},
190
- {product: 'Gadget', quarter: 'Q1', revenue: 1000.0},
191
- {product: 'Gadget', quarter: 'Q2', revenue: 1500.0}
192
- ]
193
- end
194
- end
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
-
254
- describe "#+" do
255
- let(:more_data) do
256
- [
257
- {product: 'Widget', quarter: 'Q3', price: 10.0, quantity: 200},
258
- {product: 'Gadget', quarter: 'Q3', price: 25.0, quantity: 80}
259
- ]
260
- end
261
-
262
- let(:more_sales) do
263
- Namo.new(more_data)
264
- end
265
-
266
- it "concatenates rows" do
267
- result = sales + more_sales
268
- _(result.to_a.count).must_equal 6
269
- _(result.to_a).must_equal(sample_data + more_data)
270
- end
271
-
272
- it "preserves dimensions" do
273
- result = sales + more_sales
274
- _(result.dimensions).must_equal [:product, :quarter, :price, :quantity]
275
- end
276
-
277
- it "carries formulae through from self" do
278
- sales[:revenue] = proc{|r| r[:price] * r[:quantity]}
279
- result = sales + more_sales
280
- _(result.map{|row| row[:revenue]}).must_equal [1000.0, 1500.0, 1000.0, 1500.0, 2000.0, 2000.0]
281
- end
282
-
283
- it "merges formulae from other" do
284
- more_sales[:revenue] = proc{|r| r[:price] * r[:quantity]}
285
- result = sales + more_sales
286
- _(result.map{|row| row[:revenue]}).must_equal [1000.0, 1500.0, 1000.0, 1500.0, 2000.0, 2000.0]
287
- end
288
-
289
- it "prefers self's formulae on conflict" do
290
- sales[:label] = proc{|r| "self: #{r[:product]}"}
291
- more_sales[:label] = proc{|r| "other: #{r[:product]}"}
292
- result = sales + more_sales
293
- _(result.map{|row| row[:label]}).must_equal [
294
- 'self: Widget', 'self: Widget', 'self: Gadget', 'self: Gadget',
295
- 'self: Widget', 'self: Gadget'
296
- ]
297
- end
298
-
299
- it "raises when dimensions differ" do
300
- other = Namo.new([{product: 'Widget', quarter: 'Q1'}])
301
- _ { sales + other }.must_raise ArgumentError
302
- end
303
- end
304
-
305
- describe "#-" do
306
- let(:to_remove) do
307
- Namo.new([
308
- {product: 'Widget', quarter: 'Q1', price: 10.0, quantity: 100},
309
- {product: 'Gadget', quarter: 'Q2', price: 25.0, quantity: 60}
310
- ])
311
- end
312
-
313
- it "removes matching rows" do
314
- result = sales - to_remove
315
- _(result.to_a.count).must_equal 2
316
- _(result.to_a).must_equal [
317
- {product: 'Widget', quarter: 'Q2', price: 10.0, quantity: 150},
318
- {product: 'Gadget', quarter: 'Q1', price: 25.0, quantity: 40}
319
- ]
320
- end
321
-
322
- it "preserves non-matching rows" do
323
- other = Namo.new([{product: 'Thingo', quarter: 'Q4', price: 99.0, quantity: 1}])
324
- result = sales - other
325
- _(result.to_a).must_equal sample_data
326
- end
327
-
328
- it "carries formulae through from self" do
329
- sales[:revenue] = proc{|r| r[:price] * r[:quantity]}
330
- result = sales - to_remove
331
- _(result.map{|row| row[:revenue]}).must_equal [1500.0, 1000.0]
332
- end
333
-
334
- it "raises when dimensions differ" do
335
- other = Namo.new([{product: 'Widget', quarter: 'Q1'}])
336
- _ { sales - other }.must_raise ArgumentError
337
- end
338
- end
339
-
340
- describe "#to_a" do
341
- it "returns the data as an array of hashes" do
342
- _(sales.to_a).must_equal sample_data
343
- end
344
- end
345
- end