activerecord-duplicator 0.1.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: 4bf58194f42894cf69c9ff83e02cd0f9f76fe38565e671e24ebb232221e8fa31
4
- data.tar.gz: 1d20e02d28ef2bffcf831654d2b71fe73461d3eee0fa3b11ce5e95a15bac259a
3
+ metadata.gz: ee612e84f80aefec6371c6e900d058e9934b7b7d095a981e2f52d22291586210
4
+ data.tar.gz: 2aafb86816d74188a18a82e36c74e1a6c1dcb65b52523c3ee4bafa9db45e1fa3
5
5
  SHA512:
6
- metadata.gz: ba02707cbdfdf479e2663bfcbf9b7d4012e1b1d43a9a483a041b76debff00d013e8b4f36efd7e81cb8e2e09d30a60b374a87345f625216dad0c75fee6898f1f0
7
- data.tar.gz: 6871e902554ea9fc435b17bfa83e978a1034898bae00cf744edb61b191a89dc05bc479d316c6785a172a722f7d87b794114e173b819ed8176ccdffca380bd430
6
+ metadata.gz: f167f08df7e50cc605ff85d3663f3e51cb1b2a11ea88201bf50726b20721e263b8b9f96fee38ad64e42b97cd895f2217fb1968760bdf085351959632c7fec0d1
7
+ data.tar.gz: 652d33cc38a670dc0eafad296157d6f56c5fd3e4a435082b1bec45706c9a01624730be36992ebd22ac88f335a70def57aad3234480d77816297f879460462cda
data/CHANGELOG.md CHANGED
@@ -1,8 +1,55 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.3.0] - 2026-07-06
4
+
5
+ ### Changed (breaking)
6
+
7
+ - `Session#run` now takes a `transaction_options: {}` keyword and forwards
8
+ it verbatim to `root_record.transaction`. The previous hard-coded
9
+ `requires_new: true, joinable: false` defaults are removed; callers that
10
+ relied on them must pass those flags explicitly:
11
+ `session.run(root, transaction_options: { requires_new: true, joinable: false })`.
12
+
13
+ ### Added
14
+
15
+ - `Duplicator#mark_skip_class(*klasses)` / `Session#mark_skip_class` /
16
+ `HandlerApi#mark_skip_class` — class-wide shorthand for `mark_skip`.
17
+ Behaviour is exactly as if every record of the class had been passed
18
+ to `mark_skip` beforehand: handlers registered for the class still
19
+ fire, the default `bulk_insert` path becomes a no-op, and
20
+ `fetch_new_id(klass, old_id: ...)` echoes `old_id` back unchanged.
21
+ The Duplicator-level registration is inherited by every subsequent
22
+ `duplicate` run; Session-level additions apply only to the current run.
23
+
24
+ ## [0.2.0] - 2026-07-06
25
+
3
26
  - Drop support for Ruby 3.2 (EOL)
4
27
  - Drop support for Rails 7.2; assume ActiveRecord >= 8.0 so `in_batches(cursor:)` is always available
5
28
 
29
+ ### Removed (breaking)
30
+
31
+ - STI (Single Table Inheritance) dispatch in `Session#duplicate_records`.
32
+ Handlers are now looked up by the input Relation's `klass` (or the first
33
+ element's class for Arrays), with no ancestor walk and no `group_by(&:class)`.
34
+ If you were relying on registering a handler on a base class and expecting
35
+ it to fire for subclass records mixed in one Relation, split your input by
36
+ subclass and register handlers on each concrete class instead.
37
+
38
+ ### Changed (breaking)
39
+
40
+ - Handlers now receive an `ActiveRecord::Relation` (not an Array) when the
41
+ input to duplication was a Relation. This lets `api.bulk_insert(klass,
42
+ records, cursor: [...])` take the `in_batches(cursor:)` path and enables
43
+ cursor-based keyset pagination for large child sets.
44
+ - Default (handler-less) dispatch of a Relation now uses `find_in_batches`
45
+ internally, reducing peak memory for large child sets.
46
+
47
+ ### Migration
48
+
49
+ - Handlers that previously used Array-only methods (`records.reject!`, etc.)
50
+ on the third argument must convert to Array explicitly:
51
+ `records = records.to_a`.
52
+
6
53
  ## [0.1.0] - 2026-07-02
7
54
 
8
55
  - Add `ActiveRecord::Duplicator::AssociationTraversal` — breadth-first traversal of preload-style association specifications
data/README.md CHANGED
@@ -26,6 +26,17 @@ gem install activerecord-duplicator
26
26
  - PostgreSQL (via `pg` gem). The gem relies on `insert_all!` returning inserted
27
27
  primary keys.
28
28
 
29
+ ## Non-goals
30
+
31
+ ### STI dispatch
32
+
33
+ This gem dispatches based on the class of the input Relation (or the first
34
+ element for Arrays). If your Relation mixes STI subclasses and you need
35
+ per-subclass handlers, split the input into per-subclass Relations before
36
+ passing them in. Ancestor-walking dispatch (register a handler on a base class,
37
+ have it fire for subclass records mixed in one Relation) is intentionally not
38
+ supported; see CHANGELOG for details.
39
+
29
40
  ## Usage
30
41
 
31
42
  ### Basic duplication
@@ -60,6 +71,32 @@ duplicator.duplicate(old_project, associations: [tasks: :attachments])
60
71
  Skipped records get `new_id == old_id` in the internal map, so children that
61
72
  reference them are inserted with the original id unchanged.
62
73
 
74
+ ### Skipping a whole model (`mark_skip_class`)
75
+
76
+ When every row of a model should be reused by id (a shared lookup table,
77
+ a tenant-wide catalog, an enum-backed table with dozens of entries), skip
78
+ the class itself instead of enumerating rows. `mark_skip_class` exists at
79
+ both scopes and mirrors `on`: register once on the Duplicator to inherit
80
+ across every run, or add it in the Session block for a single run.
81
+
82
+ ```ruby
83
+ duplicator = ActiveRecord::Duplicator.new
84
+ duplicator.mark_skip_class(Tag) # applies to every subsequent run
85
+
86
+ duplicator.duplicate(old_post, associations: [:taggings])
87
+
88
+ duplicator.duplicate(other_post, associations: [:taggings]) do |session|
89
+ session.mark_skip_class(Currency) # extra skip, this run only
90
+ end
91
+ ```
92
+
93
+ Behaviour is exactly as if you had passed every row to `mark_skip` up front:
94
+ handlers registered for the class still fire (so custom copy logic keeps
95
+ working), but the default `bulk_insert` path becomes a no-op and any
96
+ `fetch_new_id(klass, old_id: ...)` from a child's foreign-key reassignment
97
+ echoes the old id back unchanged. `mark_skip_class` only differs from
98
+ per-record `mark_skip` in cost: no enumeration is needed to opt each row in.
99
+
63
100
  ### Registering an id from outside (`store_new_id` / `fetch_new_id`)
64
101
 
65
102
  If you produce a duplicate yourself (for example a model with a uniqueness
@@ -19,6 +19,11 @@ module ActiveRecord
19
19
  @session.mark_skip(...)
20
20
  end
21
21
 
22
+ #: (*Class) -> void
23
+ def mark_skip_class(...)
24
+ @session.mark_skip_class(...)
25
+ end
26
+
22
27
  #: (Class, old_id: untyped, new_id: untyped) -> void
23
28
  def store_new_id(...)
24
29
  @session.store_new_id(...)
@@ -24,13 +24,16 @@ module ActiveRecord
24
24
  # @rbs @record_id_map: Hash[Class, Hash[untyped, untyped]]
25
25
  # @rbs @handlers: Hash[Class, Proc]
26
26
  # @rbs @user_handlers: Hash[Class, Proc]
27
+ # @rbs @skipped_classes: Set[Class]
27
28
 
28
- #: (?handlers: Hash[Class, Proc]) -> void
29
- def initialize(handlers: {})
29
+ #: (?handlers: Hash[Class, Proc], ?skipped_classes: Set[Class]) -> void
30
+ def initialize(handlers: {}, skipped_classes: Set.new)
30
31
  @record_id_map = Hash.new { |hash, klass| hash[klass] = {} }
31
32
  @handlers = handlers
32
33
  @user_handlers = {}
33
- @handlers_cache = {}
34
+ # dup so that Session#mark_skip_class additions never leak back to the
35
+ # Duplicator-level set shared across every run.
36
+ @skipped_classes = skipped_classes.dup
34
37
  end
35
38
 
36
39
  # Register a handler that overrides Duplicator#on for the current session
@@ -42,8 +45,6 @@ module ActiveRecord
42
45
  # Duplicator-level handler is intentional and does not raise.
43
46
  #: (Class) { (HandlerApi, Class, untyped) -> void } -> void
44
47
  def on(klass, &block)
45
- @handlers_cache.clear
46
-
47
48
  if @user_handlers.key?(klass)
48
49
  raise DuplicateHandlerError, "handler for #{klass} is already overridden in this session"
49
50
  end
@@ -62,6 +63,21 @@ module ActiveRecord
62
63
  end
63
64
  end
64
65
 
66
+ # Class-wide equivalent of mark_skip: it is exactly as if every record of
67
+ # each given class had been passed to mark_skip. Handlers still fire
68
+ # (matching the mark_skip case where a handler for the class is invoked
69
+ # with a Relation that happens to contain pre-skipped records); the
70
+ # default bulk_insert path becomes a no-op, and fetch_new_id echoes the
71
+ # queried old_id back unchanged.
72
+ #
73
+ # Use when the target database is expected to already hold every
74
+ # referenced row (shared lookup tables, tenant-wide catalogs, etc.), so
75
+ # per-record mark_skip would only differ from this in cost, not effect.
76
+ #: (*Class) -> void
77
+ def mark_skip_class(*klasses)
78
+ @skipped_classes.merge(klasses)
79
+ end
80
+
65
81
  # Record a mapping from an old_id to a new_id. Called automatically from
66
82
  # bulk_insert; called explicitly by user code (or a handler) when new
67
83
  # records are produced outside of the default duplication path.
@@ -92,6 +108,8 @@ module ActiveRecord
92
108
  # associations tree or register/skip it explicitly.
93
109
  #: (Class, old_id: untyped) -> untyped
94
110
  def fetch_new_id(klass, old_id:)
111
+ return old_id if @skipped_classes.include?(klass)
112
+
95
113
  @record_id_map[klass].fetch(old_id) do
96
114
  raise MissingNewIdError, "no new_id registered for #{klass}##{old_id.inspect}"
97
115
  end
@@ -100,8 +118,14 @@ module ActiveRecord
100
118
  # Bulk-insert copies of the given records into klass, skipping any that
101
119
  # are already present in the id map. Handlers can reuse this via HandlerApi
102
120
  # to defer a subset of records to the default duplication path.
121
+ #
122
+ # When klass has been passed to mark_skip_class the call is a no-op that
123
+ # never touches the DB: it is equivalent to having pre-registered every
124
+ # incoming record via mark_skip, only without the per-record enumeration.
103
125
  #: (Class, untyped, ?batch_size: Integer, ?cursor: untyped) -> void
104
126
  def bulk_insert(klass, records, batch_size: DEFAULT_BATCH_SIZE, cursor: nil)
127
+ return if @skipped_classes.include?(klass)
128
+
105
129
  batches = enumerate_batches(records, batch_size:, cursor:)
106
130
 
107
131
  batches.each do |batch|
@@ -140,15 +164,21 @@ module ActiveRecord
140
164
  attrs
141
165
  end
142
166
 
143
- # Run the duplication inside a new, non-joinable transaction and return
144
- # the freshly loaded root record. Normally invoked by Duplicator#duplicate.
145
- #: (ActiveRecord::Base, ?associations: untyped) -> ActiveRecord::Base
146
- def run(root_record, associations: [])
147
- root_record.transaction(requires_new: true, joinable: false) do
148
- duplicate_records([root_record])
167
+ # Run the duplication inside a transaction and return the freshly loaded
168
+ # root record. Normally invoked by Duplicator#duplicate.
169
+ #
170
+ # transaction_options is passed through verbatim to
171
+ # `root_record.transaction`, so callers can request `requires_new: true`
172
+ # / `joinable: false` / `isolation: :serializable` etc. as fits the
173
+ # surrounding call site. It defaults to `{}`, i.e. plain
174
+ # `transaction do ... end` behaviour.
175
+ #: (ActiveRecord::Base, ?associations: untyped, ?transaction_options: Hash[Symbol, untyped]) -> ActiveRecord::Base
176
+ def run(root_record, associations: [], transaction_options: {})
177
+ root_record.transaction(**transaction_options) do
178
+ duplicate_records(root_record.class, [root_record])
149
179
 
150
180
  AssociationTraversal.new(root_record, associations).each do |relation|
151
- duplicate_records(relation)
181
+ duplicate_records(relation.klass, relation)
152
182
  end
153
183
 
154
184
  new_id = fetch_new_id(root_record.class, old_id: current_id(root_record))
@@ -158,45 +188,30 @@ module ActiveRecord
158
188
 
159
189
  private
160
190
 
161
- #: (untyped) -> void
162
- def duplicate_records(relation_or_records)
163
- # Group by the real class of each record so a handler registered on an
164
- # STI subclass fires only for records of that subclass. For non-STI
165
- # models every record shares the given `klass`, so a single group is
166
- # produced.
167
- relation_or_records.group_by(&:class).each do |real_class, records|
168
- handler = cache_or_resolve_handler(real_class)
169
-
170
- if handler
171
- handler.call(HandlerApi.new(self), real_class, records)
172
- else
173
- bulk_insert(real_class, records)
174
- end
191
+ # Dispatch to a handler (or the default bulk_insert path) for `klass`.
192
+ # All records in `relation_or_records` are assumed to be of `klass`; the
193
+ # caller is responsible for splitting mixed inputs (e.g. STI subclasses)
194
+ # into per-class Relations and passing the matching class in.
195
+ #
196
+ # mark_skip_class does NOT short-circuit here: handlers must still fire
197
+ # (mirroring the per-record mark_skip case), and the class-wide skip is
198
+ # applied inside bulk_insert instead.
199
+ #: (Class, untyped) -> void
200
+ def duplicate_records(klass, relation_or_records)
201
+ handler = resolve_handler(klass)
202
+
203
+ if handler
204
+ handler.call(HandlerApi.new(self), klass, relation_or_records)
205
+ else
206
+ bulk_insert(klass, relation_or_records)
175
207
  end
176
208
  end
177
209
 
178
- #: (Class) -> Proc?
179
- def cache_or_resolve_handler(klass)
180
- @handlers_cache[klass] = resolve_handler(klass) unless @handlers_cache.key?(klass)
181
-
182
- @handlers_cache[klass]
183
- end
184
-
185
- # Look up a handler for `klass` by walking its ancestor chain. Session
186
- # -local handlers (Session#on) take precedence over any Duplicator-level
187
- # handler, even one registered closer in the ancestor chain: the two
188
- # passes below are deliberate, not a refactor opportunity.
210
+ # Session-local handlers (Session#on) take precedence over any
211
+ # Duplicator-level handler registered for the exact same class.
189
212
  #: (Class) -> Proc?
190
213
  def resolve_handler(klass)
191
- # rubocop:disable Style/CombinableLoops
192
- klass.ancestors.each do |ancestor|
193
- return @user_handlers[ancestor] if @user_handlers.key?(ancestor)
194
- end
195
- klass.ancestors.each do |ancestor|
196
- return @handlers[ancestor] if @handlers.key?(ancestor)
197
- end
198
- # rubocop:enable Style/CombinableLoops
199
- nil
214
+ @user_handlers[klass] || @handlers[klass]
200
215
  end
201
216
 
202
217
  #: (untyped, batch_size: Integer, cursor: untyped) -> untyped
@@ -2,6 +2,6 @@
2
2
 
3
3
  module ActiveRecord
4
4
  class Duplicator
5
- VERSION = "0.1.0"
5
+ VERSION = "0.3.0"
6
6
  end
7
7
  end
@@ -19,10 +19,12 @@ module ActiveRecord
19
19
  # end
20
20
  class Duplicator
21
21
  # @rbs @handlers: Hash[Class, Proc]
22
+ # @rbs @skipped_classes: Set[Class]
22
23
 
23
24
  #: () -> void
24
25
  def initialize
25
26
  @handlers = {}
27
+ @skipped_classes = Set.new
26
28
  end
27
29
 
28
30
  # Register a custom handler for the given model class. See HandlerApi for
@@ -36,11 +38,22 @@ module ActiveRecord
36
38
  @handlers[klass] = block
37
39
  end
38
40
 
41
+ # Declare that every record of the given classes must not be duplicated in
42
+ # any subsequent run. See Session#mark_skip_class for the semantics; the
43
+ # Duplicator-level registration is applied to every fresh Session as its
44
+ # baseline, and Session#mark_skip_class can extend it for a single run.
45
+ # Repeated calls with the same class are idempotent.
46
+ #: (*Class) -> void
47
+ def mark_skip_class(*klasses)
48
+ @skipped_classes.merge(klasses)
49
+ end
50
+
39
51
  # Prevent further registrations. Also freezes the internal handler map so
40
52
  # accidental mutation later fails loudly.
41
53
  #: () -> self
42
54
  def freeze
43
55
  @handlers.freeze
56
+ @skipped_classes.freeze
44
57
  super
45
58
  end
46
59
 
@@ -52,7 +65,7 @@ module ActiveRecord
52
65
  # were duplicated elsewhere.
53
66
  #: (ActiveRecord::Base, ?associations: untyped) ?{ (Session) -> void } -> ActiveRecord::Base
54
67
  def duplicate(root_record, associations: [], &block)
55
- session = Session.new(handlers: @handlers)
68
+ session = Session.new(handlers: @handlers, skipped_classes: @skipped_classes)
56
69
  block&.call(session)
57
70
  session.run(root_record, associations:)
58
71
  end
@@ -15,6 +15,9 @@ module ActiveRecord
15
15
  # : (*ActiveRecord::Base) -> void
16
16
  def mark_skip: (*ActiveRecord::Base) -> void
17
17
 
18
+ # : (*Class) -> void
19
+ def mark_skip_class: (*Class) -> void
20
+
18
21
  # : (Class, old_id: untyped, new_id: untyped) -> void
19
22
  def store_new_id: (Class, old_id: untyped, new_id: untyped) -> void
20
23
 
@@ -20,14 +20,16 @@ module ActiveRecord
20
20
  class Session
21
21
  DEFAULT_BATCH_SIZE: ::Integer
22
22
 
23
+ @skipped_classes: Set[Class]
24
+
23
25
  @user_handlers: Hash[Class, Proc]
24
26
 
25
27
  @handlers: Hash[Class, Proc]
26
28
 
27
29
  @record_id_map: Hash[Class, Hash[untyped, untyped]]
28
30
 
29
- # : (?handlers: Hash[Class, Proc]) -> void
30
- def initialize: (?handlers: Hash[Class, Proc]) -> void
31
+ # : (?handlers: Hash[Class, Proc], ?skipped_classes: Set[Class]) -> void
32
+ def initialize: (?handlers: Hash[Class, Proc], ?skipped_classes: Set[Class]) -> void
31
33
 
32
34
  # Register a handler that overrides Duplicator#on for the current session
33
35
  # only. Useful when a single duplication run needs a special copy of a
@@ -45,6 +47,19 @@ module ActiveRecord
45
47
  # : (*ActiveRecord::Base) -> void
46
48
  def mark_skip: (*ActiveRecord::Base) -> void
47
49
 
50
+ # Class-wide equivalent of mark_skip: it is exactly as if every record of
51
+ # each given class had been passed to mark_skip. Handlers still fire
52
+ # (matching the mark_skip case where a handler for the class is invoked
53
+ # with a Relation that happens to contain pre-skipped records); the
54
+ # default bulk_insert path becomes a no-op, and fetch_new_id echoes the
55
+ # queried old_id back unchanged.
56
+ #
57
+ # Use when the target database is expected to already hold every
58
+ # referenced row (shared lookup tables, tenant-wide catalogs, etc.), so
59
+ # per-record mark_skip would only differ from this in cost, not effect.
60
+ # : (*Class) -> void
61
+ def mark_skip_class: (*Class) -> void
62
+
48
63
  # Record a mapping from an old_id to a new_id. Called automatically from
49
64
  # bulk_insert; called explicitly by user code (or a handler) when new
50
65
  # records are produced outside of the default duplication path.
@@ -69,6 +84,10 @@ module ActiveRecord
69
84
  # Bulk-insert copies of the given records into klass, skipping any that
70
85
  # are already present in the id map. Handlers can reuse this via HandlerApi
71
86
  # to defer a subset of records to the default duplication path.
87
+ #
88
+ # When klass has been passed to mark_skip_class the call is a no-op that
89
+ # never touches the DB: it is equivalent to having pre-registered every
90
+ # incoming record via mark_skip, only without the per-record enumeration.
72
91
  # : (Class, untyped, ?batch_size: Integer, ?cursor: untyped) -> void
73
92
  def bulk_insert: (Class, untyped, ?batch_size: Integer, ?cursor: untyped) -> void
74
93
 
@@ -83,23 +102,32 @@ module ActiveRecord
83
102
  # : (Class, ActiveRecord::Base) -> Hash[String, untyped]
84
103
  def attributes_for: (Class, ActiveRecord::Base) -> Hash[String, untyped]
85
104
 
86
- # Run the duplication inside a new, non-joinable transaction and return
87
- # the freshly loaded root record. Normally invoked by Duplicator#duplicate.
88
- # : (ActiveRecord::Base, ?associations: untyped) -> ActiveRecord::Base
89
- def run: (ActiveRecord::Base, ?associations: untyped) -> ActiveRecord::Base
105
+ # Run the duplication inside a transaction and return the freshly loaded
106
+ # root record. Normally invoked by Duplicator#duplicate.
107
+ #
108
+ # transaction_options is passed through verbatim to
109
+ # `root_record.transaction`, so callers can request `requires_new: true`
110
+ # / `joinable: false` / `isolation: :serializable` etc. as fits the
111
+ # surrounding call site. It defaults to `{}`, i.e. plain
112
+ # `transaction do ... end` behaviour.
113
+ # : (ActiveRecord::Base, ?associations: untyped, ?transaction_options: Hash[Symbol, untyped]) -> ActiveRecord::Base
114
+ def run: (ActiveRecord::Base, ?associations: untyped, ?transaction_options: Hash[Symbol, untyped]) -> ActiveRecord::Base
90
115
 
91
116
  private
92
117
 
93
- # : (untyped) -> void
94
- def duplicate_records: (untyped) -> void
95
-
96
- # : (Class) -> Proc?
97
- def cache_or_resolve_handler: (Class) -> Proc?
98
-
99
- # Look up a handler for `klass` by walking its ancestor chain. Session
100
- # -local handlers (Session#on) take precedence over any Duplicator-level
101
- # handler, even one registered closer in the ancestor chain: the two
102
- # passes below are deliberate, not a refactor opportunity.
118
+ # Dispatch to a handler (or the default bulk_insert path) for `klass`.
119
+ # All records in `relation_or_records` are assumed to be of `klass`; the
120
+ # caller is responsible for splitting mixed inputs (e.g. STI subclasses)
121
+ # into per-class Relations and passing the matching class in.
122
+ #
123
+ # mark_skip_class does NOT short-circuit here: handlers must still fire
124
+ # (mirroring the per-record mark_skip case), and the class-wide skip is
125
+ # applied inside bulk_insert instead.
126
+ # : (Class, untyped) -> void
127
+ def duplicate_records: (Class, untyped) -> void
128
+
129
+ # Session-local handlers (Session#on) take precedence over any
130
+ # Duplicator-level handler registered for the exact same class.
103
131
  # : (Class) -> Proc?
104
132
  def resolve_handler: (Class) -> Proc?
105
133
 
@@ -18,6 +18,8 @@ module ActiveRecord
18
18
  class Duplicator
19
19
  @handlers: Hash[Class, Proc]
20
20
 
21
+ @skipped_classes: Set[Class]
22
+
21
23
  # : () -> void
22
24
  def initialize: () -> void
23
25
 
@@ -28,6 +30,14 @@ module ActiveRecord
28
30
  # : (Class) { (HandlerApi, Class, untyped) -> void } -> void
29
31
  def on: (Class) { (HandlerApi, Class, untyped) -> void } -> void
30
32
 
33
+ # Declare that every record of the given classes must not be duplicated in
34
+ # any subsequent run. See Session#mark_skip_class for the semantics; the
35
+ # Duplicator-level registration is applied to every fresh Session as its
36
+ # baseline, and Session#mark_skip_class can extend it for a single run.
37
+ # Repeated calls with the same class are idempotent.
38
+ # : (*Class) -> void
39
+ def mark_skip_class: (*Class) -> void
40
+
31
41
  # Prevent further registrations. Also freezes the internal handler map so
32
42
  # accidental mutation later fails loudly.
33
43
  # : () -> self
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord-duplicator
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - SmartHR