namo 0.31.3 → 0.31.5
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 +23 -0
- data/README.md +25 -3
- data/lib/Namo/Collection.rb +55 -7
- data/lib/Namo/VERSION.rb +1 -1
- data/lib/namo.rb +26 -9
- data/test/Namo/Collection_test.rb +135 -3
- data/test/sizing_test.rb +42 -0
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 77e56d154536ec5ebde8dbb2d660277007a76d4ec108c6b669ff96399f9991d7
|
|
4
|
+
data.tar.gz: 79baaf53cfd3417b5db049b7c2ea6701c60580e97f41235bcafb91538c49d427
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ffa93bb4ad5f35eaffcefa76778dbffc843bc24a95130327d4da88e8ae8e3103fdc6a28a2eabaf0f38b996b768e6125f7e1538612ac8c922e0bb7c495870f128
|
|
7
|
+
data.tar.gz: 24e8d924c61152922e757ab30b87662cede03e6dcb85dbb2cd964de4b9230ee4d65617e74760b7e37a3d8831dd332c3f46e829adfbaa82cf275f2a9108528558
|
data/CHANGELOG
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
+
## 20260826
|
|
4
|
+
|
|
5
|
+
0.31.5: ~ Namo::Collection#detail, #as_detail: the detail view carries the members' formulae.
|
|
6
|
+
|
|
7
|
+
1. ~ Namo::Collection#detail: the returned Namo carries the members' formulae, folded in member order so that a later member wins a name collision, as a later member wins a collision of names in <<. It had been built with none, so the view documented at 0.18.0 as the lossless union of the members' rows dropped every derived dimension: group_by hands each member the source's formulae, and detail handed them back stripped.
|
|
8
|
+
2. ~ Namo::Collection#as_detail: merges the detail view's formulae into the Collection's own, the Collection having become that view. Without it the round trip still lost them, as_detail having taken only the data.
|
|
9
|
+
3. ~ script/demo: the collections section checks the round trip with === rather than ==, == comparing row multisets alone and so unable to see the formulae go missing, which is why this stood.
|
|
10
|
+
4. ~ README.md: the four views, on which of them carries formulae and why.
|
|
11
|
+
5. ~ ROADMAP.md: + 0.31.5.
|
|
12
|
+
6. ~ Namo::VERSION: /0.31.4/0.31.5/
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
## 20260826
|
|
16
|
+
|
|
17
|
+
0.31.4: ~ Namo::Collection#inspect: the members are rendered, nested under their names.
|
|
18
|
+
|
|
19
|
+
1. ~ Namo::Collection#inspect: the members are rendered nested under their names rather than listed as bare labels beside a row count, the members being the substance and the data view derived from them. The Namo::INSPECTED_ROWS budget is spread across the members shown — one member renders ten of its rows, five render two apiece, ten render one each and are put on one line — and members beyond it are elided with a count, as rows are. The member list had no cap at all: 2,641 members emitted 22,704 characters on a single line, which is the failure INSPECTED_ROWS was introduced at 0.31.0 to end. A member renders nested as it renders alone, its derived values among its stored ones and its own derived: suffix naming whatever none of its rendered rows could materialise. The detail view's row count closes the block, and a Collection's own formulae are named after it, being a namespace its members cannot resolve.
|
|
20
|
+
2. + Namo#inspected_data: the rendered rows, the count of those not shown, and the derived dimensions none of them carries — protected, so Namo::Collection can render its members' rows without going through Namo#inspect and repeating each member's name, and still report what a member reports of itself.
|
|
21
|
+
3. ~ README.md: the Inspection output section, and the two group_by examples whose rendering changes.
|
|
22
|
+
4. ~ ROADMAP.md: + 0.31.4.
|
|
23
|
+
5. ~ Namo::VERSION: /0.31.3/0.31.4/
|
|
24
|
+
|
|
25
|
+
|
|
3
26
|
## 20260824
|
|
4
27
|
|
|
5
28
|
0.31.3: ~ Namo#to_a: derived dimensions are carried alongside the stored ones.
|
data/README.md
CHANGED
|
@@ -996,14 +996,27 @@ Evaluation is bounded by the row cap rather than by the data — at most ten row
|
|
|
996
996
|
|
|
997
997
|
Rows beyond `Namo::INSPECTED_ROWS` — ten — are elided with a count of the remainder, so the output stays bounded however large the data: a thousand-row Namo renders its first ten rows and then `... 990 more rows`.
|
|
998
998
|
|
|
999
|
-
A `Row` renders itself, its derived values and its formula names in every case, having no `derived_dimensions` of its own to be asked, and never the Namo it came from.
|
|
999
|
+
A `Row` renders itself, its derived values and its formula names in every case, having no `derived_dimensions` of its own to be asked, and never the Namo it came from.
|
|
1000
|
+
|
|
1001
|
+
A `Collection` renders its members, nested under their names, because the members are its substance and `@data` is a view derived from them. The `INSPECTED_ROWS` budget is spread across the members shown rather than spent on one: a single member renders ten of its rows, five members render two apiece, ten render one each and are put on one line. Members beyond that are elided with a count, as rows are, so a Collection of thousands renders in a dozen lines. The total row count of the detail view closes the block.
|
|
1002
|
+
|
|
1003
|
+
A member renders nested as it would render alone: its derived values among its stored ones, and its own `derived:` suffix naming whatever it cannot materialise. A Collection's own formulae are a separate namespace — it cannot resolve its members' and they cannot resolve its — so they are named in a suffix of the Collection's own, after the closing bracket.
|
|
1000
1004
|
|
|
1001
1005
|
```ruby
|
|
1002
1006
|
sales.first
|
|
1003
1007
|
# => #<Namo::Row {product: "Widget", quarter: "Q1", price: 10.0, quantity: 100, revenue: 1000.0} derived: [:revenue]>
|
|
1004
1008
|
|
|
1005
1009
|
sales.group_by(:quarter)
|
|
1006
|
-
# => #<Namo::Collection
|
|
1010
|
+
# => #<Namo::Collection [
|
|
1011
|
+
# "Q1" => [
|
|
1012
|
+
# {product: "Widget", quarter: "Q1", price: 10.0, quantity: 100, revenue: 1000.0},
|
|
1013
|
+
# {product: "Gadget", quarter: "Q1", price: 25.0, quantity: 40, revenue: 1000.0}
|
|
1014
|
+
# ],
|
|
1015
|
+
# "Q2" => [
|
|
1016
|
+
# {product: "Widget", quarter: "Q2", price: 10.0, quantity: 150, revenue: 1500.0},
|
|
1017
|
+
# {product: "Gadget", quarter: "Q2", price: 25.0, quantity: 60, revenue: 1500.0}
|
|
1018
|
+
# ]
|
|
1019
|
+
# ] 4 rows>
|
|
1007
1020
|
```
|
|
1008
1021
|
|
|
1009
1022
|
### Named Namos
|
|
@@ -1108,6 +1121,8 @@ Detail is the lazy view because a Collection's rows simply *are* its members' ro
|
|
|
1108
1121
|
|
|
1109
1122
|
#### Four view methods
|
|
1110
1123
|
|
|
1124
|
+
`detail` carries the members' formulae, its rows being the members' rows — folded in member order, so a later member wins a name collision as it wins a collision of names in `<<`. A `summary`'s rows are reductions, holding neither the dimensions those formulae read nor the rows they read them from, so it carries none.
|
|
1125
|
+
|
|
1111
1126
|
The views come in a non-mutating pair and a mutating pair:
|
|
1112
1127
|
|
|
1113
1128
|
- `summary(dimension = nil, by:, reducer:, &block)` and `detail(by:)` are **non-mutating** — each returns a fresh `Namo` derived from the members, leaving the Collection untouched. Use these when you want a view to keep: assign the result to a variable and operate on it independently.
|
|
@@ -1204,7 +1219,14 @@ Freeze-gated memoisation is a 2.x optimisation — opt-in via `freeze`, transpar
|
|
|
1204
1219
|
|
|
1205
1220
|
```ruby
|
|
1206
1221
|
prices.group_by(:symbol)
|
|
1207
|
-
# => #<Namo::Collection
|
|
1222
|
+
# => #<Namo::Collection [
|
|
1223
|
+
# :BHP => [
|
|
1224
|
+
# {symbol: :BHP, date: "2025-01-02", close: 42.5},
|
|
1225
|
+
# {symbol: :BHP, date: "2025-01-03", close: 43.0}
|
|
1226
|
+
# ],
|
|
1227
|
+
# :RIO => [{symbol: :RIO, date: "2025-01-02", close: 118.0}],
|
|
1228
|
+
# :CBA => [{symbol: :CBA, date: "2025-01-02", close: 96.4}]
|
|
1229
|
+
# ] 4 rows>
|
|
1208
1230
|
|
|
1209
1231
|
prices.group_by(:symbol).summary(:close, reducer: :mean)
|
|
1210
1232
|
# => Namo with {member:, close:} rows — mean close per symbol
|
data/lib/Namo/Collection.rb
CHANGED
|
@@ -25,21 +25,24 @@ class Namo
|
|
|
25
25
|
|
|
26
26
|
def summary(dimension = nil, by: :member, reducer: :sum, &block)
|
|
27
27
|
raise ArgumentError, "summary needs a dimension or a block" unless dimension || block
|
|
28
|
-
rows =
|
|
28
|
+
rows = (
|
|
29
29
|
if block
|
|
30
30
|
@members.map{|member| block.call(member).merge(by => member.name)}
|
|
31
31
|
else
|
|
32
32
|
@members.map{|member| {by => member.name, dimension => member.values(dimension).send(reducer)}}
|
|
33
33
|
end
|
|
34
|
+
)
|
|
34
35
|
Namo.new(rows)
|
|
35
36
|
end
|
|
36
37
|
|
|
37
38
|
def detail(positional_by = nil, by: :member)
|
|
38
39
|
by = positional_by || by
|
|
39
|
-
rows =
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
rows = (
|
|
41
|
+
@members.flat_map do |member|
|
|
42
|
+
member.data.map{|row| row.key?(by) ? row : row.merge(by => member.name)}
|
|
43
|
+
end
|
|
44
|
+
)
|
|
45
|
+
Namo.new(rows, formulae: member_formulae)
|
|
43
46
|
end
|
|
44
47
|
|
|
45
48
|
def as_summary(dimension = nil, by: :member, reducer: :sum, &block)
|
|
@@ -49,16 +52,61 @@ class Namo
|
|
|
49
52
|
|
|
50
53
|
def as_detail(positional_by = nil, by: :member)
|
|
51
54
|
by = positional_by || by
|
|
52
|
-
|
|
55
|
+
view = detail(by)
|
|
56
|
+
@data = view.data
|
|
57
|
+
@formulae = @formulae.merge(view.formulae)
|
|
53
58
|
self
|
|
54
59
|
end
|
|
55
60
|
|
|
61
|
+
# The members are the substance, so they are what is rendered. A list of names
|
|
62
|
+
# and a row count described the derived view and called it the object, and the
|
|
63
|
+
# list was unbounded besides: 2,641 members emitted 22,704 characters on one
|
|
64
|
+
# line, which is the failure INSPECTED_ROWS was introduced to end.
|
|
65
|
+
#
|
|
66
|
+
# That budget is now spread across the members shown rather than spent on one
|
|
67
|
+
# member's rows: one member renders ten of its rows, ten members render one
|
|
68
|
+
# apiece, five render two.
|
|
56
69
|
def inspect
|
|
57
|
-
"#<#{self.class}#{inspected_name}
|
|
70
|
+
"#<#{self.class}#{inspected_name} #{inspected_members} #{@data.length} rows#{inspected_derived}>"
|
|
58
71
|
end
|
|
59
72
|
|
|
60
73
|
private
|
|
61
74
|
|
|
75
|
+
# The members' formulae, folded in member order so that a later member wins a
|
|
76
|
+
# name collision, as a later member wins a collision of names in <<. The
|
|
77
|
+
# detail view is the members' rows, so it must be able to answer what the
|
|
78
|
+
# members can answer of them; group_by hands every member the same formulae,
|
|
79
|
+
# so only an assembled Collection can have members which disagree.
|
|
80
|
+
def member_formulae
|
|
81
|
+
@members.map(&:formulae).reduce(Formulae.new){|merged, formulae| merged.merge(formulae)}
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def inspected_members
|
|
85
|
+
return '[]' if @members.empty?
|
|
86
|
+
shown = [@members.length, INSPECTED_ROWS].min
|
|
87
|
+
each = [INSPECTED_ROWS / shown, 1].max
|
|
88
|
+
rendered = @members.first(shown).map{|member| " #{member.name.inspect} => #{inspected_member(member, each)}"}.join(",\n")
|
|
89
|
+
rendered += "\n ... #{@members.length - shown} more members" if @members.length > shown
|
|
90
|
+
"[\n#{rendered}\n]"
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# A member showing a single row is rendered on one line: three lines of brackets
|
|
94
|
+
# to carry one row is the shape a Collection of many members lands in, and it
|
|
95
|
+
# cost more than the rows did. On what is rendered rather than on the limit,
|
|
96
|
+
# so a member holding one row of a larger allowance reads the same way.
|
|
97
|
+
def inspected_member(member, limit)
|
|
98
|
+
rows, remainder, unshown = member.inspected_data(limit)
|
|
99
|
+
elided = remainder > 0
|
|
100
|
+
suffix = inspected_derived_names(unshown)
|
|
101
|
+
if rows.length == 1
|
|
102
|
+
"[#{rows.join}#{elided ? ", ... #{remainder} more rows" : ''}]#{suffix}"
|
|
103
|
+
else
|
|
104
|
+
body = rows.map{|row| " #{row}"}.join(",\n")
|
|
105
|
+
body += "\n ... #{remainder} more rows" if elided
|
|
106
|
+
"[\n#{body}\n ]#{suffix}"
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
62
110
|
def initialize(positional_data = nil, data: [], formulae: {}, name: nil)
|
|
63
111
|
@members = []
|
|
64
112
|
super
|
data/lib/Namo/VERSION.rb
CHANGED
data/lib/namo.rb
CHANGED
|
@@ -262,6 +262,16 @@ class Namo
|
|
|
262
262
|
|
|
263
263
|
protected
|
|
264
264
|
|
|
265
|
+
# The rendered rows, the count of those not shown, and the derived dimensions
|
|
266
|
+
# none of them carries. Shared with Collection, which renders its members' rows
|
|
267
|
+
# rather than its own and must report what a member would report of itself.
|
|
268
|
+
def inspected_data(limit)
|
|
269
|
+
rendered = @data.first(limit).map{|row| [row, inspected_derivations(row)]}
|
|
270
|
+
[rendered.map{|row, derived| row.merge(derived).inspect},
|
|
271
|
+
@data.length - limit,
|
|
272
|
+
unshown_derived(rendered)]
|
|
273
|
+
end
|
|
274
|
+
|
|
265
275
|
def row_multiset
|
|
266
276
|
@data.tally
|
|
267
277
|
end
|
|
@@ -278,6 +288,13 @@ class Namo
|
|
|
278
288
|
|
|
279
289
|
private
|
|
280
290
|
|
|
291
|
+
def initialize(positional_data = nil, data: [], formulae: {}, name: nil)
|
|
292
|
+
@data = positional_data || data
|
|
293
|
+
@formulae = formulae.is_a?(Formulae) ? formulae : Formulae.new(formulae)
|
|
294
|
+
@name = name
|
|
295
|
+
attach_included_formularies
|
|
296
|
+
end
|
|
297
|
+
|
|
281
298
|
def inspected_name
|
|
282
299
|
@name.nil? ? '' : " #{@name.inspect}"
|
|
283
300
|
end
|
|
@@ -308,19 +325,19 @@ class Namo
|
|
|
308
325
|
# Only those the rows above do not already carry. A formula which raises, and a
|
|
309
326
|
# parameterised one which cannot be materialised without its arguments, render
|
|
310
327
|
# no value and would otherwise be nowhere in the output at all. A Collection
|
|
311
|
-
# renders no rows and so passes none, naming every one of them.
|
|
328
|
+
# renders no rows of its own and so passes none, naming every one of them.
|
|
312
329
|
def inspected_derived(rendered = [])
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
330
|
+
inspected_derived_names(unshown_derived(rendered))
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
def inspected_derived_names(unshown)
|
|
316
334
|
unshown.empty? ? '' : " derived: #{unshown.inspect}"
|
|
317
335
|
end
|
|
318
336
|
|
|
319
|
-
def
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
attach_included_formularies
|
|
337
|
+
def unshown_derived(rendered)
|
|
338
|
+
derived_dimensions.reject do |dimension|
|
|
339
|
+
rendered.any? && rendered.all?{|_, derived| derived.key?(dimension)}
|
|
340
|
+
end
|
|
324
341
|
end
|
|
325
342
|
|
|
326
343
|
def add_row(row)
|
|
@@ -347,15 +347,147 @@ describe Namo::Collection do
|
|
|
347
347
|
end
|
|
348
348
|
end
|
|
349
349
|
|
|
350
|
+
describe "the detail view and its formulae" do
|
|
351
|
+
def readings
|
|
352
|
+
namo = Namo.new([
|
|
353
|
+
{station: "Melbourne", month: "2025-01", high_temp: 26.4, mean_temp: 25.9},
|
|
354
|
+
{station: "Perth", month: "2025-01", high_temp: 32.0, mean_temp: 30.0}])
|
|
355
|
+
namo[:anomaly] = proc{|row| (row[:high_temp] - row[:mean_temp]).round(1)}
|
|
356
|
+
namo
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
it "carries the members' formulae, the view being the members' rows" do
|
|
360
|
+
_(readings.group_by(:station).detail(:station).derived_dimensions).must_equal [:anomaly]
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
it "answers a derived dimension through the detail view" do
|
|
364
|
+
_(readings.group_by(:station).detail(:station).values(:anomaly)).must_equal [0.5, 2.0]
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
# == compares row multisets and cannot see a formula go missing, so it called
|
|
368
|
+
# the partition exact while the derived dimensions were being dropped. === is
|
|
369
|
+
# the operator which compares the formula names too.
|
|
370
|
+
it "inverts the partition exactly, formulae and all" do
|
|
371
|
+
source = readings
|
|
372
|
+
_(source.group_by(:station).as_detail(:station)).must_be :===, source
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
it "folds the members' formulae, a later member winning the name" do
|
|
376
|
+
first = Namo.new([{a: 1}], name: :first)
|
|
377
|
+
first[:b] = proc{|row| :from_first}
|
|
378
|
+
second = Namo.new([{a: 2}], name: :second)
|
|
379
|
+
second[:b] = proc{|row| :from_second}
|
|
380
|
+
collection = Namo::Collection.new
|
|
381
|
+
collection << first << second
|
|
382
|
+
_(collection.detail.values(:b)).must_equal [:from_second, :from_second]
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
# A summary's rows are reductions, holding neither the dimensions the members'
|
|
386
|
+
# formulae read nor the rows they read them from, so it carries none.
|
|
387
|
+
it "leaves the summary without them" do
|
|
388
|
+
_(readings.group_by(:station).summary(:high_temp).derived_dimensions).must_equal []
|
|
389
|
+
end
|
|
390
|
+
end
|
|
391
|
+
|
|
350
392
|
describe "#inspect" do
|
|
351
|
-
|
|
393
|
+
def collection_of(members, rows_each)
|
|
394
|
+
rows = (1..members).flat_map{|member| (1..rows_each).map{|row| {group: "g#{member}", n: row}}}
|
|
395
|
+
Namo.new(rows).group_by(:group)
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
it "renders each member nested, under its name" do
|
|
352
399
|
collection = Namo::Collection.new
|
|
353
400
|
collection << Namo.new([{a: 1}], name: :first) << Namo.new([{a: 2}], name: :second)
|
|
354
|
-
_(collection.inspect).must_equal
|
|
401
|
+
_(collection.inspect).must_equal <<~INSPECT.chomp
|
|
402
|
+
#<Namo::Collection [
|
|
403
|
+
:first => [{a: 1}],
|
|
404
|
+
:second => [{a: 2}]
|
|
405
|
+
] 2 rows>
|
|
406
|
+
INSPECT
|
|
355
407
|
end
|
|
356
408
|
|
|
357
409
|
it "renders an empty Collection" do
|
|
358
|
-
_(Namo::Collection.new.inspect).must_equal "#<Namo::Collection
|
|
410
|
+
_(Namo::Collection.new.inspect).must_equal "#<Namo::Collection [] 0 rows>"
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
# The budget is INSPECTED_ROWS spread across the members shown, so the output
|
|
414
|
+
# is bounded by what was asked for rather than by how the data is shaped.
|
|
415
|
+
it "gives a lone member the whole row budget" do
|
|
416
|
+
rendered = collection_of(1, 50).inspect
|
|
417
|
+
_(rendered.scan(/\{group:/).length).must_equal Namo::INSPECTED_ROWS
|
|
418
|
+
_(rendered).must_include "... 40 more rows"
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
it "gives five members two rows apiece" do
|
|
422
|
+
rendered = collection_of(5, 9).inspect
|
|
423
|
+
_(rendered.scan(/\{group:/).length).must_equal Namo::INSPECTED_ROWS
|
|
424
|
+
_(rendered).must_include "... 7 more rows"
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
it "gives ten members one row apiece, on one line each" do
|
|
428
|
+
rendered = collection_of(10, 4).inspect
|
|
429
|
+
_(rendered.scan(/\{group:/).length).must_equal Namo::INSPECTED_ROWS
|
|
430
|
+
_(rendered).must_include %q{"g1" => [{group: "g1", n: 1}, ... 3 more rows]}
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
it "elides the members beyond the budget, with a count" do
|
|
434
|
+
rendered = collection_of(2641, 130).inspect
|
|
435
|
+
_(rendered).must_include "... 2631 more members"
|
|
436
|
+
_(rendered.lines.length).must_equal 13
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
# 2,641 members emitted 22,704 characters on a single line before the budget.
|
|
440
|
+
it "stays bounded as the members multiply" do
|
|
441
|
+
_(collection_of(2641, 130).inspect.length).must_be :<, collection_of(10, 130).inspect.length * 2
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
it "reports the total row count of the detail view" do
|
|
445
|
+
_(collection_of(3, 7).inspect).must_include "] 21 rows>"
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
# Where the two rules meet: 0.31.2 names only what the rendered rows do not
|
|
449
|
+
# carry. A member's formula is carried, since its rows are what is rendered; a
|
|
450
|
+
# Collection's own applies to the data view, which the nested form never shows,
|
|
451
|
+
# so naming it is the only way to learn it is there.
|
|
452
|
+
it "names its own derived dimensions while showing its members' inline" do
|
|
453
|
+
member = Namo.new([{part: "block", weight: 90}], name: :powertrain)
|
|
454
|
+
member[:light] = proc{|row| row[:weight] < 50}
|
|
455
|
+
collection = Namo::Collection.new
|
|
456
|
+
collection << member
|
|
457
|
+
collection[:heavy] = proc{|row| row[:weight] > 50}
|
|
458
|
+
_(collection.derived_dimensions).must_equal [:heavy]
|
|
459
|
+
_(member.derived_dimensions).must_equal [:light]
|
|
460
|
+
_(collection.inspect).must_include "{part: \"block\", weight: 90, light: false}"
|
|
461
|
+
_(collection.inspect).must_include "] 1 rows derived: [:heavy]>"
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
# Nested, a member must say what it says of itself: a formula it cannot
|
|
465
|
+
# materialise is named in its own inspect, and vanishing here would make the
|
|
466
|
+
# nested rendering quieter than the parts it is made of.
|
|
467
|
+
it "carries a member's own suffix for what that member cannot materialise" do
|
|
468
|
+
member = Namo.new([{part: "block", weight: 90}], name: :powertrain)
|
|
469
|
+
member[:light] = proc{|row| row[:weight] < 50}
|
|
470
|
+
member[:scaled] = proc{|row, namo, factor| row[:weight] * factor}
|
|
471
|
+
collection = Namo::Collection.new
|
|
472
|
+
collection << member
|
|
473
|
+
_(member.inspect).must_include "derived: [:scaled]"
|
|
474
|
+
_(collection.inspect).must_include ":powertrain => [{part: \"block\", weight: 90, light: false}] derived: [:scaled]"
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
it "carries no suffix when only its members hold formulae" do
|
|
478
|
+
member = Namo.new([{part: "block", weight: 90}], name: :powertrain)
|
|
479
|
+
member[:light] = proc{|row| row[:weight] < 50}
|
|
480
|
+
collection = Namo::Collection.new
|
|
481
|
+
collection << member
|
|
482
|
+
_(collection.inspect).wont_include "derived:"
|
|
483
|
+
end
|
|
484
|
+
|
|
485
|
+
it "renders a member's derived dimensions among its stored ones" do
|
|
486
|
+
member = Namo.new([{a: 1}], name: :first)
|
|
487
|
+
member[:b] = proc{|row| row[:a] + 1}
|
|
488
|
+
collection = Namo::Collection.new
|
|
489
|
+
collection << member
|
|
490
|
+
_(collection.inspect).must_include "{a: 1, b: 2}"
|
|
359
491
|
end
|
|
360
492
|
end
|
|
361
493
|
|
data/test/sizing_test.rb
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# test/sizing_test.rb
|
|
2
|
+
|
|
3
|
+
require 'minitest/autorun'
|
|
4
|
+
require 'minitest-spec-context'
|
|
5
|
+
|
|
6
|
+
require_relative '../lib/namo'
|
|
7
|
+
|
|
8
|
+
# script/sizing produces the figures in ROADMAP.md's "What size it is for". A
|
|
9
|
+
# measurement whose script has rotted is a measurement nobody can repeat, which is
|
|
10
|
+
# the condition the script exists to end — so assert it still runs, at sizes small
|
|
11
|
+
# enough not to make the suite a benchmark.
|
|
12
|
+
|
|
13
|
+
describe 'script/sizing' do
|
|
14
|
+
def sizing(*arguments)
|
|
15
|
+
root = File.expand_path('..', __dir__)
|
|
16
|
+
IO.popen([File.join(root, 'script', 'sizing'), *arguments],
|
|
17
|
+
chdir: root, err: [:child, :out]){|io| io.read}
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it "reports a row per size asked for" do
|
|
21
|
+
output = sizing('2000', '4000')
|
|
22
|
+
_($?.success?).must_equal true
|
|
23
|
+
_(output).must_match(/^\| 2,000 \| \d+ ms \|/)
|
|
24
|
+
_(output).must_match(/^\| 4,000 \| \d+ ms \|/)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it "renders as the table the ROADMAP carries" do
|
|
28
|
+
_(sizing('2000').lines.first).must_equal "| rows | group_by | per row | bytes/row |\n"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it "refuses a size it cannot measure" do
|
|
32
|
+
sizing('0')
|
|
33
|
+
_($?.success?).must_equal false
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it "needs nothing but the gem's own dependencies" do
|
|
37
|
+
root = File.expand_path('..', __dir__)
|
|
38
|
+
output = IO.popen({'RUBYLIB' => nil}, [File.join(root, 'script', 'sizing'), '2000'],
|
|
39
|
+
chdir: root, err: [:child, :out]){|io| io.read}
|
|
40
|
+
_(output).wont_match(/LoadError/)
|
|
41
|
+
end
|
|
42
|
+
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.31.
|
|
4
|
+
version: 0.31.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- thoran
|
|
@@ -82,6 +82,7 @@ files:
|
|
|
82
82
|
- test/console_test.rb
|
|
83
83
|
- test/demo_test.rb
|
|
84
84
|
- test/namo_test.rb
|
|
85
|
+
- test/sizing_test.rb
|
|
85
86
|
homepage: https://github.com/thoran/namo
|
|
86
87
|
licenses:
|
|
87
88
|
- MIT
|
|
@@ -100,7 +101,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
100
101
|
- !ruby/object:Gem::Version
|
|
101
102
|
version: '0'
|
|
102
103
|
requirements: []
|
|
103
|
-
rubygems_version: 4.0.
|
|
104
|
+
rubygems_version: 4.0.19
|
|
104
105
|
specification_version: 4
|
|
105
106
|
summary: Named dimensional data for Ruby.
|
|
106
107
|
test_files: []
|