namo 0.25.1 → 0.26.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 +13 -0
- data/README.md +8 -0
- data/lib/Namo/Formulae.rb +15 -0
- data/lib/Namo/VERSION.rb +1 -1
- data/lib/namo.rb +5 -0
- data/test/Namo/Collection_test.rb +9 -0
- data/test/Namo/Formulae_test.rb +58 -0
- data/test/namo_test.rb +51 -1
- 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: 326ba6ba610149b0998452136812f313617014d5e68fc066d5c81ace134ad8eb
|
|
4
|
+
data.tar.gz: ddd93f8e3866169c6bc6835c5720375fbfd53860b2e036f1540dcb567414b61c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9402c8b7193dd068d624b883f9e2b7492942cf67dcf0c3d697e85bb77c2615deb205538cf86c5b341e567e25990241235b88bd5bfa0582b0cace025c65a7fa10
|
|
7
|
+
data.tar.gz: 40af6d9dd32c57b4a60e038887fbc4c73c7a6412e25854688b9ff8ddc5ee35c6419d30fcb76ce6e0ee5cdcf491c757a3ebcd1f81ec584ad41243413300b8c40f
|
data/CHANGELOG
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
+
## 20260704
|
|
4
|
+
|
|
5
|
+
0.26.0: + Namo#detach, Namo::Formulae#detach — remove a formula by name or a formulary's methods by module, completing the attach/attach!/detach family.
|
|
6
|
+
|
|
7
|
+
detach becomes the sanctioned formula-removal idiom, displacing the namo.formulae.delete(:name) reach-through. It is by-name with no provenance (the store is a snapshot): the Module arm removes whatever currently holds each of the module's method names. There is no detach! (removing a formula can't collide with data, so no guard to force through) and no removal operator (<< appends; per the 0.14.0 principle, no >>).
|
|
8
|
+
|
|
9
|
+
1. + Namo::Formulae#detach(constituent) — dispatches a Symbol to @store.delete(constituent); a Module to deleting each of its public_instance_methods(false) names, after the same Namo::Formulary marker guard attach uses (an untagged module raises ArgumentError); anything else raises TypeError naming the class, in the << else-arm's style. Returns self. Placed directly after attach/<<. Formulae#delete is unchanged — it stays the store-level primitive detach sits on.
|
|
10
|
+
2. + Namo#detach(constituent) — forwards to @formulae.detach and returns self, placed with the attach family (after attach!, before <<). No guard at the Namo level: there is no data-side interaction, so the Formulae worker owns the dispatch and the raises. Collection inherits detach harmlessly.
|
|
11
|
+
3. ~ test/Namo/Formulae_test.rb: + a "#detach" describe in the formulary tier — both arms return self; the Symbol arm removes the named formula and no-ops on an absent name; the Module arm removes exactly the formulary's method names, leaves a []= name it doesn't contribute, and deletes a []= overwrite occupying a formulary-method name (the by-name/no-provenance pin); ArgumentError on an untagged module; TypeError on a String and a Hash naming the class; chaining removes both. ~ test/namo_test.rb: + a "#detach" describe under formularies — returns the Namo; removes a []= formula from the queryable namespace (derived_dimensions drops it, values returns one nil per row); leaves other formulae resolving; the Module arm empties derived_dimensions; a data dimension is a no-op (the column survives); an absent name no-ops returning the Namo; TypeError on a non-Symbol non-Module and ArgumentError on an untagged module; the #dimensions "reflects mutation" test migrates sales.formulae.delete(:revenue) to sales.detach(:revenue). ~ test/Namo/Collection_test.rb: + a "#detach" describe pinning that a Collection inherits detach — a formulary attached to the collection detaches, so the detail rows no longer resolve its name.
|
|
12
|
+
4. ~ README.md: + a "Removing a formula" note under Formulae (detach removes a formula, returns the Namo, chains, no-ops on an absent name; complements contraction across the data/derived and algebra/mutation splits; never touches @data); + a detach(Module) note in the Formularies section (the reverse of attach, by-name with no provenance, raises on an untagged module, the attach/attach!/detach family, no << removal counterpart, no detach!).
|
|
13
|
+
5. ~ ROADMAP.md: + the 0.26.0 release entry recording the settled decisions (by-name/no-provenance, marker required, loose-on-absent, derived-side-only, no-bang, no-operator, single-argument/chaining, untouched-host-ancestry); current-state bumped to 0.26.0; detach folded into the Summary's formulary vocabulary and the 1.0.0 feature list.
|
|
14
|
+
6. ~ Namo::VERSION: /0.25.1/0.26.0/
|
|
15
|
+
|
|
3
16
|
## 20260703
|
|
4
17
|
|
|
5
18
|
0.25.1: Fix documentation and reformat CHANGELOG to use Markdown headers.
|
data/README.md
CHANGED
|
@@ -716,6 +716,12 @@ prices[:sma_close_20] = proc{|row| row[:sma, :close, 20]}
|
|
|
716
716
|
prices[:date, :sma_close_20] # materialises per the usual projection rule
|
|
717
717
|
```
|
|
718
718
|
|
|
719
|
+
#### Removing a formula
|
|
720
|
+
|
|
721
|
+
`detach(:name)` removes a formula, returns the Namo, and chains — `sales.detach(:revenue).detach(:cost)`. Detaching a name that isn't there is a quiet no-op, so a cleanup step composes over any Namo without first checking whether the formula is present. It is the sanctioned way to drop a derived dimension, sitting over the store-level primitive `formulae.delete`.
|
|
722
|
+
|
|
723
|
+
`detach` and contraction are complements across two axes. Contraction (`sales[-:price]`) is non-mutating — it returns a *new* Namo with a *data* dimension removed and the formulae carried. `detach` mutates the receiver and removes a *derived* dimension. So the pair spans both the data/derived split and the algebra/mutation split: contraction is algebra over data, `detach` is mutation of the derived side. `detach` never touches `@data` — naming a data dimension is a no-op and the column survives; removing a data dimension is contraction's job.
|
|
724
|
+
|
|
719
725
|
#### Formularies
|
|
720
726
|
|
|
721
727
|
A *formulary* is a reusable body of derived dimensions — a module whose public instance methods are formulae. A Namo attaches a formulary, and from then on those methods resolve as derived dimensions through the same interface as `[]=` formulae. The methods take `row` (and `namo`, and any further parameters) exactly as a `[]=` formula does:
|
|
@@ -772,6 +778,8 @@ Because each method is copied into the store, the rules already governing `[]=`
|
|
|
772
778
|
|
|
773
779
|
Note that there is no counterpart in `[]=`'s scalar-over-formula eviction and is worth naming: a formulary method that reads a column its own name evicts will **recurse** on access. The eviction removes the datum, and exclusive storage leaves no shadowed value behind the formula, so `Row#[]` re-enters the formula rather than falling back to data — a `def close(row); row[:close] * ...; end` attached with `attach!` over a `:close` data column has no `:close` data left to read. `[]=` rarely bites this way because you write the proc at the call site, beside the name it replaces; a formulary is authored elsewhere, so a method reading its own evicted column is easy to attach without noticing. Self-reference is unguarded here as everywhere in the formula mechanism — keep a formulary's inputs and outputs under distinct names, or `attach` (not `attach!`) and contract deliberately.
|
|
774
780
|
|
|
781
|
+
`detach(OrderFlow)` is the reverse of `attach`: it removes the formulary's public method names from the store, so `flows.attach(OrderFlow)` then `flows.detach(OrderFlow)` leaves the formulae as they started. It works by name and keeps no provenance — the store is a snapshot, so `detach` removes *whatever currently holds each name*, including a `[]=` formula that later overwrote a formulary method of the same name. Like the `attach` side, the marker is required: `detach` raises `ArgumentError` for an untagged module, so you can only detach what could have been attached. The mutating family is `attach` / `attach!` / `detach`; `<<` has no removal counterpart (it *appends* constituents — a removal operator would be cuteness over clarity), and there is no `detach!` because removing a formula can't collide with data, so there is no guard for a bang to force through.
|
|
782
|
+
|
|
775
783
|
Two Namos that expose the same names — one via `[]=`, one via a formulary — are `===`; a Namo with a formulary attached is not `===` to a vanilla Namo of the same data dimensions.
|
|
776
784
|
|
|
777
785
|
Formulary methods take `row` explicitly and index it (`row[:buys]`), the same as `[]=` formulae. Resolving bare names inside a method body (`buys` for `row[:buys]`) and memoising derived values on a frozen Namo arrive in a later release; until then a formulary method is a plain function of its `row` (and `namo`).
|
data/lib/Namo/Formulae.rb
CHANGED
|
@@ -25,6 +25,21 @@ class Namo
|
|
|
25
25
|
end
|
|
26
26
|
alias_method :<<, :attach
|
|
27
27
|
|
|
28
|
+
def detach(constituent)
|
|
29
|
+
case constituent
|
|
30
|
+
when Symbol
|
|
31
|
+
@store.delete(constituent)
|
|
32
|
+
when Module
|
|
33
|
+
unless constituent.include?(Namo::Formulary)
|
|
34
|
+
raise ArgumentError, "not a Namo::Formulary: #{constituent}"
|
|
35
|
+
end
|
|
36
|
+
constituent.public_instance_methods(false).each{|name| @store.delete(name)}
|
|
37
|
+
else
|
|
38
|
+
raise TypeError, "can't detach #{constituent.class} from a Formulae; expected a Symbol or a Module (formulary)"
|
|
39
|
+
end
|
|
40
|
+
self
|
|
41
|
+
end
|
|
42
|
+
|
|
28
43
|
def derive(name, row, namo, *arguments)
|
|
29
44
|
formula = self[name]
|
|
30
45
|
if collection_scoped?(name)
|
data/lib/Namo/VERSION.rb
CHANGED
data/lib/namo.rb
CHANGED
|
@@ -145,6 +145,15 @@ describe Namo::Collection do
|
|
|
145
145
|
end
|
|
146
146
|
end
|
|
147
147
|
|
|
148
|
+
describe "#detach" do
|
|
149
|
+
it "detaches a formulary attached to the collection, so the detail rows no longer resolve its name" do
|
|
150
|
+
collection << component_pricing
|
|
151
|
+
_(collection.values(:cost_per_kg)).must_equal [250, 250, 200, 250]
|
|
152
|
+
collection.detach(component_pricing)
|
|
153
|
+
_(collection.derived_dimensions).wont_include :cost_per_kg
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
148
157
|
describe "#find" do
|
|
149
158
|
it "returns the member with the given name" do
|
|
150
159
|
_(collection.find(:chassis)).must_be_same_as chassis
|
data/test/Namo/Formulae_test.rb
CHANGED
|
@@ -330,6 +330,64 @@ describe Namo::Formulae do
|
|
|
330
330
|
end
|
|
331
331
|
end
|
|
332
332
|
|
|
333
|
+
describe "#detach" do
|
|
334
|
+
it "returns self for a Symbol" do
|
|
335
|
+
_(attached.detach(:signed_volume)).must_be_same_as attached
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
it "returns self for a Module" do
|
|
339
|
+
_(attached.detach(delta)).must_be_same_as attached
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
it "removes the named formula, leaving the others" do
|
|
343
|
+
attached.detach(:signed_volume)
|
|
344
|
+
_(attached.key?(:signed_volume)).must_equal false
|
|
345
|
+
_(attached.key?(:echo_namo)).must_equal true
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
it "is a no-op on an absent name, still returning self" do
|
|
349
|
+
_(attached.detach(:missing)).must_be_same_as attached
|
|
350
|
+
_(attached.key?(:signed_volume)).must_equal true
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
it "removes exactly the formulary's public method names via the Module arm" do
|
|
354
|
+
attached.detach(delta)
|
|
355
|
+
_(attached.keys).must_equal []
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
it "leaves a []= formula not among the formulary's methods when detaching the module" do
|
|
359
|
+
attached[:extra] = proc{|r| 0}
|
|
360
|
+
attached.detach(delta)
|
|
361
|
+
_(attached.key?(:extra)).must_equal true
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
it "deletes a []= overwrite occupying a formulary-method name" do
|
|
365
|
+
attached[:signed_volume] = proc{|r| 0}
|
|
366
|
+
attached.detach(delta)
|
|
367
|
+
_(attached.key?(:signed_volume)).must_equal false
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
it "raises ArgumentError for an untagged module" do
|
|
371
|
+
_(proc{attached.detach(untagged)}).must_raise ArgumentError
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
it "raises TypeError for a String, naming the class" do
|
|
375
|
+
error = _(proc{attached.detach("signed_volume")}).must_raise TypeError
|
|
376
|
+
_(error.message).must_match(/String/)
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
it "raises TypeError for a Hash, naming the class" do
|
|
380
|
+
error = _(proc{attached.detach({})}).must_raise TypeError
|
|
381
|
+
_(error.message).must_match(/Hash/)
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
it "chains, removing both named formulae" do
|
|
385
|
+
formulae[:cost] = cost
|
|
386
|
+
formulae.detach(:revenue).detach(:cost)
|
|
387
|
+
_(formulae.keys).must_equal []
|
|
388
|
+
end
|
|
389
|
+
end
|
|
390
|
+
|
|
333
391
|
describe "carry-through" do
|
|
334
392
|
it "preserves attached formularies through #dup" do
|
|
335
393
|
_(attached.dup.keys).must_include :signed_volume
|
data/test/namo_test.rb
CHANGED
|
@@ -165,7 +165,7 @@ describe Namo do
|
|
|
165
165
|
it "reflects mutation on the next call" do
|
|
166
166
|
sales[:revenue] = proc{|r| r[:price] * r[:quantity]}
|
|
167
167
|
_(sales.dimensions).must_include :revenue
|
|
168
|
-
sales.
|
|
168
|
+
sales.detach(:revenue)
|
|
169
169
|
_(sales.dimensions).wont_include :revenue
|
|
170
170
|
end
|
|
171
171
|
end
|
|
@@ -978,6 +978,56 @@ describe Namo do
|
|
|
978
978
|
_(flows === other).must_equal true
|
|
979
979
|
end
|
|
980
980
|
end
|
|
981
|
+
|
|
982
|
+
describe "#detach" do
|
|
983
|
+
it "returns the Namo" do
|
|
984
|
+
_(flows.detach(:signed_volume)).must_be_same_as flows
|
|
985
|
+
end
|
|
986
|
+
|
|
987
|
+
it "removes a formula from the queryable namespace" do
|
|
988
|
+
namo = Namo.new(flow_data)
|
|
989
|
+
namo[:revenue] = proc{|r| r[:buys]}
|
|
990
|
+
namo.detach(:revenue)
|
|
991
|
+
_(namo.derived_dimensions).wont_include :revenue
|
|
992
|
+
_(namo.values(:revenue)).must_equal [nil, nil, nil]
|
|
993
|
+
end
|
|
994
|
+
|
|
995
|
+
it "leaves other formulae resolving" do
|
|
996
|
+
namo = Namo.new(flow_data)
|
|
997
|
+
namo[:revenue] = proc{|r| r[:buys]}
|
|
998
|
+
namo[:tally] = proc{|r| r[:sells]}
|
|
999
|
+
namo.detach(:revenue)
|
|
1000
|
+
_(namo.values(:tally)).must_equal [40, 120, 75]
|
|
1001
|
+
end
|
|
1002
|
+
|
|
1003
|
+
it "removes an attached formulary's methods via the Module arm" do
|
|
1004
|
+
flows.detach(delta)
|
|
1005
|
+
_(flows.derived_dimensions).must_equal []
|
|
1006
|
+
end
|
|
1007
|
+
|
|
1008
|
+
it "never exposed the formulary's private helper" do
|
|
1009
|
+
_(flows.derived_dimensions).wont_include :helper
|
|
1010
|
+
end
|
|
1011
|
+
|
|
1012
|
+
it "never touches data — a data dimension is a no-op" do
|
|
1013
|
+
flows.detach(:date)
|
|
1014
|
+
_(flows.data_dimensions).must_include :date
|
|
1015
|
+
_(flows.values(:date)).must_equal [1, 2, 3]
|
|
1016
|
+
end
|
|
1017
|
+
|
|
1018
|
+
it "is a no-op on an absent name, returning the Namo" do
|
|
1019
|
+
_(flows.detach(:missing)).must_be_same_as flows
|
|
1020
|
+
_(flows.values(:signed_volume)).must_equal [20, -40, 0]
|
|
1021
|
+
end
|
|
1022
|
+
|
|
1023
|
+
it "raises TypeError on a non-Symbol non-Module" do
|
|
1024
|
+
_(proc{flows.detach("signed_volume")}).must_raise TypeError
|
|
1025
|
+
end
|
|
1026
|
+
|
|
1027
|
+
it "raises ArgumentError on an untagged module" do
|
|
1028
|
+
_(proc{flows.detach(untagged)}).must_raise ArgumentError
|
|
1029
|
+
end
|
|
1030
|
+
end
|
|
981
1031
|
end
|
|
982
1032
|
|
|
983
1033
|
describe "formularies via class include" do
|