deckard 0.2.0 → 0.3.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: d27aab47bd5883ad518fe7910ae3faf5f7a3c617430bd0668345dacd652252a6
4
- data.tar.gz: 8b232443e73afa31f061a082327c5b3c541912f83127f18ba968e99659f0f167
3
+ metadata.gz: a0545ee7c006b54aaedd28b162ee6eb06647b0812bfbbdcb3b5846a78d6cdd68
4
+ data.tar.gz: 9cf0a020aaeabaf9d659549b3045fe9ba2b4a758ee40106163a6aead95c7a778
5
5
  SHA512:
6
- metadata.gz: e851c6424c4c790437c294461f64e2444a34297c67507b3fa53c36cdc3af2008462ee41f12465908f4187bbd8b02b0ea6ea5cfa9c8f3509d31cbc86de21f69f3
7
- data.tar.gz: 354bd7706454a95d042b85d2d80166215cfa0cd9c8ae31c5dfee06f3de0d16f51b277505eaec6a992bdd689ff189044ea536a0220bde49a99484603d2fcd585f
6
+ metadata.gz: 33e82c2fbba3856af1019a61bc1cf181e6f74383824895ab667d76aedd1994a04af5e69534c73f379ba2b64f333748319767290511f97eaefb13fbb75e674b83
7
+ data.tar.gz: dacd253d2915316b2549f6ea4196354055f0abd4b504b14c9e6980ecab11f83d676a1868e81751c785ee813fd242bd256361daaae475c5078eda6c227364d0aa
data/CHANGELOG.md CHANGED
@@ -5,7 +5,22 @@ All notable changes to deckard are recorded here. The format follows
5
5
  [Semantic Versioning](https://semver.org/). Before 1.0, minor versions may
6
6
  change the stream format and the Ruby API.
7
7
 
8
- ## [0.2.0] - Unreleased
8
+ ## [0.3.0] - Unreleased
9
+
10
+ ### Added
11
+
12
+ - `natural_keys` inside a `replicate` block: one table of class name to
13
+ attribute name or names, the short form for a reached class that only
14
+ needs a natural key. `model "Name" do natural_key ... end` still works
15
+ for an entry that also names associations or omissions.
16
+
17
+ ### Changed
18
+
19
+ - A plan gives each class one natural key. Declaring it twice in the same
20
+ plan is reported by validation as a `ConfigurationError`; before, the
21
+ later declaration silently replaced the earlier one.
22
+
23
+ ## [0.2.0] - 2026-09-30
9
24
 
10
25
  ### Changed
11
26
 
data/README.md CHANGED
@@ -92,7 +92,7 @@ gives every order the full plan, and a record two dumps share lands once.
92
92
 
93
93
  ## Plans
94
94
 
95
- The `replicate` block offers five methods:
95
+ The `replicate` block offers six methods:
96
96
 
97
97
  ```ruby
98
98
  replicate do
@@ -101,17 +101,25 @@ replicate do
101
101
  omit_fields :internal_notes # keep a field out of the stream
102
102
  omit_associations :warehouse # do not traverse this association
103
103
 
104
- model "LineItem" do # the same four, for a class the dump reaches
104
+ natural_keys "Product" => :sku, # how each reached class is matched
105
+ "Warehouse" => [:region, :code]
106
+
107
+ model "LineItem" do # the first four, for a class the dump reaches
105
108
  associations :adjustments
106
109
  end
107
110
  end
108
111
  ```
109
112
 
113
+ Most reached classes need nothing but a natural key, and `natural_keys` says
114
+ that for all of them in one table. A `model` block is for the rest: a
115
+ collection to bring along, a field to leave out. A plan gives each class one
116
+ key, so naming it in both places is a plan problem, not an override.
117
+
110
118
  Classes are named as strings, so the root never forces them to load first,
111
119
  and an STI subclass follows the entry for its nearest declared ancestor. Each
112
120
  record travels with the natural key its plan gave it, so the destination
113
121
  needs no plan of its own. That also means a natural key is repeated in every
114
- plan whose dumps reach the class: a Customer entry in Order's plan and one in
122
+ plan whose dumps reach the class: a Customer line in Order's plan and one in
115
123
  Invoice's, each saying how a customer is matched.
116
124
 
117
125
  Before dumping, the `deckard` executable eager loads the application so
@@ -4,10 +4,10 @@ require "active_support/core_ext/string/inflections"
4
4
 
5
5
  module Deckard
6
6
  # A replication plan: a root model's `replicate do ... end` block. The plan
7
- # is an entry for the root plus, through `model`, an entry for each class
8
- # its dumps reach, all in one table shared by the plan's entries. A class
9
- # finds its entry through its ancestors, so an STI subclass follows its
10
- # nearest declared ancestor. See docs/traversal.md.
7
+ # is an entry for the root plus an entry for each class its dumps reach,
8
+ # declared through `natural_keys` or `model`, all in one table shared by
9
+ # the plan's entries. A class finds its entry through its ancestors, so an
10
+ # STI subclass follows its nearest declared ancestor. See docs/traversal.md.
11
11
  class ModelConfig
12
12
  attr_reader :name, :extra_associations, :natural_key_attributes, :omitted_fields, :omitted_associations
13
13
 
@@ -47,11 +47,17 @@ module Deckard
47
47
  end
48
48
 
49
49
  # DSL: attributes identifying an existing destination record to reuse.
50
- # Calling it again replaces the key.
50
+ # Declaring it twice in one plan is a problem, not an override.
51
51
  def natural_key(*attributes)
52
+ @redeclared_natural_key = !@natural_key_attributes.empty?
52
53
  @natural_key_attributes = attributes.map(&:to_s)
53
54
  end
54
55
 
56
+ # DSL: class name to natural key, for reached classes needing nothing else.
57
+ def natural_keys(keys)
58
+ keys.each { |name, attributes| model(name) { natural_key(*attributes) } }
59
+ end
60
+
55
61
  # DSL: fields to exclude from dumped attributes. Additive across calls.
56
62
  def omit_fields(*names)
57
63
  @omitted_fields |= names.map(&:to_s)
@@ -104,9 +110,11 @@ module Deckard
104
110
  missing = attributes - model.attribute_names
105
111
  problems += missing.map { |attribute| "#{model} has no #{attribute.inspect} attribute" }
106
112
  end
107
- problems + (@natural_key_attributes & @omitted_fields).map do |attribute|
113
+ problems += (@natural_key_attributes & @omitted_fields).map do |attribute|
108
114
  "#{model} names #{attribute.inspect} as both a natural key attribute and an omitted field"
109
115
  end
116
+ problems << "#{model} is given a natural key more than once in the same plan" if @redeclared_natural_key
117
+ problems
110
118
  end
111
119
 
112
120
  # The plan as data, root entry first: what PlanReport renders.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Deckard
4
- VERSION = "0.2.0"
4
+ VERSION = "0.3.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: deckard
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rob Sanheim