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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 017c3006832fa49d8e63c5de42d927e0e2bb661f05ff322b002a3d1ba18758e2
4
- data.tar.gz: 65a1c6b04337361f3b692423e85e8066f598ddbd028e0349e4c85b6ebabc183f
3
+ metadata.gz: cf81d723c5723d7737dd544596abbe5b69323f9f027a1aac7caef2cb857ebf5a
4
+ data.tar.gz: 6b1130eb34b16920641e594b52aaff58e9f275ac2a26b0089b21f62a6d8f6630
5
5
  SHA512:
6
- metadata.gz: 989fbb45527739717202af6f6b74df7549488045c41acd3429ea190f7ec5ac4b54e527f684bba86201704ffd3647f2446b282c8f0cc48f2ca68677548e9e1c8b
7
- data.tar.gz: '00494397772996dd29cf099c7c019d0909d5d61eafaa2dec42a827cc79cad7db0c60fc3ab5172a63acab77dd2034b4a6ad7a7a6440c94485870754563ba94e9f'
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
@@ -2,5 +2,5 @@
2
2
  # Namo::VERSION
3
3
 
4
4
  class Namo
5
- VERSION = '0.24.1'
5
+ VERSION = '0.25.0'
6
6
  end
data/lib/namo.rb CHANGED
@@ -89,7 +89,7 @@ class Namo
89
89
  end
90
90
 
91
91
  def attach(modul)
92
- collisions = modul.public_instance_methods(false) & data_dimensions
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)
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.24.1
4
+ version: 0.25.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - thoran