namo 0.24.1 → 0.25.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 +12 -0
- data/README.md +4 -0
- data/lib/Namo/VERSION.rb +1 -1
- data/lib/namo.rb +11 -1
- data/test/namo_test.rb +52 -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: cf81d723c5723d7737dd544596abbe5b69323f9f027a1aac7caef2cb857ebf5a
|
|
4
|
+
data.tar.gz: 6b1130eb34b16920641e594b52aaff58e9f275ac2a26b0089b21f62a6d8f6630
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fb7c989aeb34877ed56e83190bf947217370103420c6f5e5186a88316217f35c4c801d0ed1864bd0105129d3391c99a635ed756c1050bb57a6ce33c4fbffa0e5
|
|
7
|
+
data.tar.gz: da0f8fd20ccfa3f33b860059f33a8e65ddfed9bc157c02c1dd73389c3b907b8f4dbe82eeaf89f6a5e28a45274b74d8d03121ea7bdee9855d772242e4c50f8683
|
data/CHANGELOG
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
CHANGELOG
|
|
2
2
|
_________
|
|
3
3
|
|
|
4
|
+
20260703
|
|
5
|
+
0.25.0: + Namo#attach! — attach a formulary, evicting any data column whose name a formulary method collides with, rather than raising.
|
|
6
|
+
|
|
7
|
+
attach! evicts the colliding data columns first (as []='s proc-over-data assignment does), then attaches — the bang is the explicit consent that attach's raise withholds by default. << stays guarded: a Module still routes to attach.
|
|
8
|
+
|
|
9
|
+
1. ~ Namo#attach — extract the collision set into a new private attach_collisions(modul) (modul.public_instance_methods(false) & data_dimensions); behaviour unchanged, still raises ArgumentError naming the collisions when the set is non-empty.
|
|
10
|
+
2, + Namo#attach! — evicts each colliding data column (@data.each{|row| row.delete(name)} per colliding name, the column membership already confirmed by attach_collisions so no key-guard is needed), then forwards to @formulae.attach and returns self. The eviction mirrors []='s proc-branch clear. + private attach_collisions(modul). << is unchanged: a Module still routes to attach (the guarded form), so the operator stays safe and attach! is reached only by explicit call — one safe operator, one explicit forceful method, matching []= having no operator shortcut for its own eviction.
|
|
11
|
+
3. ~ test/namo_test.rb: + an "#attach!" describe — attach! attaches as attach does for a non-colliding module (returns self, methods resolve); attach! evicts a colliding data column and registers the formulary method in its place, with all access paths (values, Row, selection) agreeing on the formula afterward and the name appearing once in derived_dimensions, not in data_dimensions; attach! over several colliding columns evicts all of them; attach! on a vanilla collision-free module leaves the data untouched; the existing attach collision-raise tests are unchanged, pinning that the guarded form still raises where attach! evicts.
|
|
12
|
+
4. ~ README.md: ~ the Formularies section — + attach! as the forceful sibling of attach, with the materialised-snapshot refresh as the motivating case and the bang-is-consent rationale; + the self-eviction-recurses caveat (a formulary method that reads a column its own name evicts recurses on access, exclusive storage leaving no shadowed value behind the formula — the one way attach!'s eviction differs in feel from []='s); note that << stays the guarded form.
|
|
13
|
+
5. ~ ROADMAP.md: + the 0.25.0 release entry; current-state bumped to 0.25.0.
|
|
14
|
+
6. ~ Namo::VERSION: /0.24.1/0.25.0/
|
|
15
|
+
|
|
4
16
|
20260629
|
|
5
17
|
0.24.1: ~ Namo#add_row — guard row-append against a data/formula name collision.
|
|
6
18
|
|
data/README.md
CHANGED
|
@@ -768,6 +768,10 @@ Because each method is copied into the store, the rules already governing `[]=`
|
|
|
768
768
|
- **Materialise-and-drop on projection.** Naming a formulary method in a projection snapshots its values into a data column *and drops the formula*, exactly as for a `[]=` formula: `flows[:date, :signed_volume]` returns a Namo reporting `:signed_volume` as data, not derived, with all access paths agreeing on the stored snapshot.
|
|
769
769
|
- **Carry-through.** Selection, the set and composition operators, and a projection that *omits* the name all keep the formula live, computing against the result's own rows — the carry-through is free, because the formulae are ordinary store entries.
|
|
770
770
|
|
|
771
|
+
`attach!` is the forceful sibling of `attach`. Where `attach` refuses a data collision, `attach!` **evicts** the colliding data columns first — deleting each one exactly as assigning a proc through `[]=` clears a same-named data column — then attaches. The bang is consent: `attach` won't destroy a column you never named, but `attach!` is you saying you meant to. The motivating case is refreshing a formulary over a *materialised snapshot* — once a derived dimension has been projected into a stored column (materialise-and-drop, above), re-attaching the formulary that defined it collides with the now-data column, and `attach!` re-attaches over it in one step where `attach` would require you to contract the column away first. `<<` stays the guarded form: a module through `<<` routes to `attach`, so the operator never evicts — only an explicit `attach!` call does.
|
|
772
|
+
|
|
773
|
+
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
|
+
|
|
771
775
|
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.
|
|
772
776
|
|
|
773
777
|
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/VERSION.rb
CHANGED
data/lib/namo.rb
CHANGED
|
@@ -89,7 +89,7 @@ class Namo
|
|
|
89
89
|
end
|
|
90
90
|
|
|
91
91
|
def attach(modul)
|
|
92
|
-
collisions = modul
|
|
92
|
+
collisions = attach_collisions(modul)
|
|
93
93
|
unless collisions.empty?
|
|
94
94
|
raise ArgumentError, "formulary methods collide with data dimensions: #{collisions.inspect}"
|
|
95
95
|
end
|
|
@@ -97,6 +97,12 @@ class Namo
|
|
|
97
97
|
self
|
|
98
98
|
end
|
|
99
99
|
|
|
100
|
+
def attach!(modul)
|
|
101
|
+
attach_collisions(modul).each{|name| @data.each{|row| row.delete(name)}}
|
|
102
|
+
@formulae.attach(modul)
|
|
103
|
+
self
|
|
104
|
+
end
|
|
105
|
+
|
|
100
106
|
def <<(constituent)
|
|
101
107
|
case constituent
|
|
102
108
|
when Module then attach(constituent)
|
|
@@ -268,6 +274,10 @@ class Namo
|
|
|
268
274
|
self
|
|
269
275
|
end
|
|
270
276
|
|
|
277
|
+
def attach_collisions(modul)
|
|
278
|
+
modul.public_instance_methods(false) & data_dimensions
|
|
279
|
+
end
|
|
280
|
+
|
|
271
281
|
def attach_included_formularies
|
|
272
282
|
self.class.ancestors.reverse.each do |modul|
|
|
273
283
|
next if modul.is_a?(Class) || !modul.include?(Namo::Formulary)
|
data/test/namo_test.rb
CHANGED
|
@@ -747,6 +747,58 @@ describe Namo do
|
|
|
747
747
|
end
|
|
748
748
|
end
|
|
749
749
|
|
|
750
|
+
describe "#attach!" do
|
|
751
|
+
it "attaches as attach does for a non-colliding module" do
|
|
752
|
+
namo = Namo.new(flow_data)
|
|
753
|
+
_(namo.attach!(delta)).must_be_same_as namo
|
|
754
|
+
_(namo.values(:signed_volume)).must_equal [20, -40, 0]
|
|
755
|
+
end
|
|
756
|
+
|
|
757
|
+
it "evicts a colliding data column and registers the formulary method in its place" do
|
|
758
|
+
widened = Namo.new(flow_data.map{|row| row.merge(signed_volume: 999)})
|
|
759
|
+
widened.attach!(delta)
|
|
760
|
+
_(widened.data_dimensions).wont_include :signed_volume
|
|
761
|
+
_(widened.derived_dimensions).must_include :signed_volume
|
|
762
|
+
_(widened.dimensions.count(:signed_volume)).must_equal 1
|
|
763
|
+
end
|
|
764
|
+
|
|
765
|
+
it "agrees across all access paths on the evicted-then-derived name" do
|
|
766
|
+
widened = Namo.new(flow_data.map{|row| row.merge(signed_volume: 999)})
|
|
767
|
+
widened.attach!(delta)
|
|
768
|
+
_(widened.values(:signed_volume)).must_equal [20, -40, 0]
|
|
769
|
+
_(widened.entries.first[:signed_volume]).must_equal 20
|
|
770
|
+
_(widened[signed_volume: 20].values(:date)).must_equal [1]
|
|
771
|
+
end
|
|
772
|
+
|
|
773
|
+
it "evicts every colliding column when several collide" do
|
|
774
|
+
clash = Module.new do
|
|
775
|
+
include Namo::Formulary
|
|
776
|
+
def buys(row); row[:date] * 2; end
|
|
777
|
+
def sells(row); row[:date] * 3; end
|
|
778
|
+
end
|
|
779
|
+
namo = Namo.new(flow_data)
|
|
780
|
+
namo.attach!(clash)
|
|
781
|
+
_(namo.data_dimensions).wont_include :buys
|
|
782
|
+
_(namo.data_dimensions).wont_include :sells
|
|
783
|
+
_(namo.derived_dimensions).must_include :buys
|
|
784
|
+
_(namo.derived_dimensions).must_include :sells
|
|
785
|
+
_(namo.values(:buys)).must_equal [2, 4, 6]
|
|
786
|
+
_(namo.values(:sells)).must_equal [3, 6, 9]
|
|
787
|
+
end
|
|
788
|
+
|
|
789
|
+
it "leaves the data untouched when the module does not collide" do
|
|
790
|
+
namo = Namo.new(flow_data)
|
|
791
|
+
namo.attach!(delta)
|
|
792
|
+
_(namo.values(:buys)).must_equal [60, 80, 75]
|
|
793
|
+
_(namo.values(:sells)).must_equal [40, 120, 75]
|
|
794
|
+
end
|
|
795
|
+
|
|
796
|
+
it "raises through attach (the guarded form) where attach! evicts" do
|
|
797
|
+
widened = Namo.new(flow_data.map{|row| row.merge(signed_volume: 999)})
|
|
798
|
+
_(proc{widened.attach(delta)}).must_raise ArgumentError
|
|
799
|
+
end
|
|
800
|
+
end
|
|
801
|
+
|
|
750
802
|
describe "#<<" do
|
|
751
803
|
it "attaches a formulary, the same as #attach" do
|
|
752
804
|
namo = Namo.new(flow_data)
|