acts_as_ditto 1.0.0 → 1.1.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: f4431085c0b3038f16f982d08995f253b6142fc757ff81b9577face8a3d7837d
4
- data.tar.gz: 232a203d533035d92ec0d2f3ed76322e9c9296b94e18141b1988513852402591
3
+ metadata.gz: 77bc6854fed3d05ffcb66f3332fa1f7dc125647950c20775363d3161b7310a95
4
+ data.tar.gz: 95008e2cc3ea34ac91f7bd5aa7bccaf9dccc5d4d2e36df774dde2f48d127d1e8
5
5
  SHA512:
6
- metadata.gz: 55d514d95ab78c2e971075d71976460d4360d07aad3fe6e0bbf333bd59ff854017b7fedb82e930d581d1105cedcb7d0e76e3f0a481701dd217f785423f3ec041
7
- data.tar.gz: '091c62145f730ca95efb5a2e20240126c2d2d69d288f995d8ab6a485149741070b393c2a55512997d85fd632b228c3c10abf1b4eb370d3b55bdbacac66f25bd1'
6
+ metadata.gz: 9ba7776ae856f7501784af0516b85bbe3aab66479669f2fd355c3da85d6a07ef4846c5c49d9d7bcc976f9fe0f03717ee468a784b0a8747ca3d58310bf0822d46
7
+ data.tar.gz: 64697ab3b30ee07cd5ec49469c2cccfdca7021e7867b1e855315714c9f93617094d3c25570cb56ba5f3801834de6f72cb8aaec66381a29086a8c47f5090db735
data/CHANGELOG.md CHANGED
@@ -1,4 +1,7 @@
1
- ## [Unreleased]
1
+ ## [1.1.0] - 2026-11-09
2
+
3
+ - Add duplication context to prevent cycle loops when cloning associtations. It tracks already duplicated records within a single `ditto` call and reuses them.
4
+ - `ditto!` now wraps persisting the duplicate graph in a transaction, validation failures now roll back the whole graph, not just the record that failed.
2
5
 
3
6
  ## [1.0.0] - 2026-08-28
4
7
 
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
- # Ditto
1
+ # acts_as_ditto
2
2
 
3
- Ditto adds an `acts_as_ditto` DSL to your ActiveRecord models for duplicating
3
+ Adds an `acts_as_ditto` DSL to your ActiveRecord models for duplicating
4
4
  records with custom control
5
5
 
6
6
  Similar to `.dup`, but with the options like resetting attributes, applying static
@@ -28,6 +28,12 @@ class Invoice < ApplicationRecord
28
28
  end
29
29
  ```
30
30
 
31
+ `#ditto` builds the duplicate (and, with `clone_associations`, its whole
32
+ associated graph) without persisting it.
33
+ `#ditto!` persists that whole graph in a single transaction: if any record in
34
+ it fails validation, nothing is saved and an `ActiveRecord::RecordInvalid` is
35
+ raised.
36
+
31
37
  ### Duplicating associatons
32
38
 
33
39
  Recursively duplicates the given associations along with the record.
@@ -100,15 +106,15 @@ end
100
106
 
101
107
  ## Inspiration
102
108
 
103
- Ditto was inspired by [amoeba](https://github.com/amoeba-rb/amoeba), which solves the same problem with similar DSL logic.
109
+ acts_as_ditto was inspired by [amoeba](https://github.com/amoeba-rb/amoeba), which solves the same problem with similar DSL logic.
104
110
 
105
- Ditto is opt-in rather than opt-out: you list exactly which associations to
111
+ acts_as_ditto is opt-in rather than opt-out: you list exactly which associations to
106
112
  clone with `clone_associations`, instead of enabling everything and excluding
107
113
  what you don't want.
108
114
 
109
115
  ## Contributing
110
116
 
111
- Bug reports and pull requests are welcome on GitHub at https://github.com/njaeggi/acts_as_ditto. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/njaeggi/acts_as_ditto/blob/master/CODE_OF_CONDUCT.md).
117
+ Bug reports and pull requests are welcome on GitHub at https://github.com/njaeggi/acts_as_ditto/issues. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/njaeggi/acts_as_ditto/blob/master/CODE_OF_CONDUCT.md).
112
118
 
113
119
  ## License
114
120
 
data/lib/acts_as_ditto.rb CHANGED
@@ -4,13 +4,10 @@ require "active_support"
4
4
 
5
5
  require_relative "ditto/version"
6
6
  require_relative "ditto/configuration"
7
- require_relative "ditto/duplicator"
7
+ require_relative "ditto/duplication/duplication_context"
8
+ require_relative "ditto/duplication/duplicator"
8
9
  require_relative "ditto/acts_as_ditto"
9
10
 
10
- module Ditto
11
- class Error < StandardError; end
12
- end
13
-
14
11
  ActiveSupport.on_load(:active_record) do
15
12
  include Ditto::ActsAsDitto
16
13
  end
@@ -10,7 +10,13 @@ module Ditto
10
10
  end
11
11
 
12
12
  def ditto!
13
- ditto.tap(&:save!)
13
+ duplicate = ditto
14
+
15
+ ActiveRecord::Base.transaction do
16
+ duplicate.save!
17
+ end
18
+
19
+ duplicate
14
20
  end
15
21
  end
16
22
 
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ditto
4
+ # Tracks records that have already been duplicated during a single
5
+ # `ditto` call
6
+ class DuplicationContext
7
+ def initialize
8
+ @duplicates = {}
9
+ end
10
+
11
+ def duplicate_of(record)
12
+ @duplicates[identity_for(record)]
13
+ end
14
+
15
+ def register(record, duplicate)
16
+ @duplicates[identity_for(record)] = duplicate
17
+ end
18
+
19
+ private
20
+
21
+ def identity_for(record)
22
+ record.persisted? ? [record.class.name, record.id] : record.object_id
23
+ end
24
+ end
25
+ end
@@ -3,13 +3,21 @@
3
3
  module Ditto
4
4
  # Builds a duplicate of a record based on `Ditto::Configuration`.
5
5
  class Duplicator
6
- def initialize(record, configuration)
6
+ def initialize(record, configuration, context = nil)
7
7
  @record = record
8
8
  @configuration = configuration
9
+ @context = context || DuplicationContext.new
9
10
  end
10
11
 
11
12
  def duplicate
13
+ @context.duplicate_of(@record) || build_duplicate
14
+ end
15
+
16
+ private
17
+
18
+ def build_duplicate
12
19
  new_record = @record.dup
20
+ @context.register(@record, new_record)
13
21
 
14
22
  reset_attributes_to_default(new_record)
15
23
  nullify_attributes(new_record)
@@ -22,8 +30,6 @@ module Ditto
22
30
  new_record
23
31
  end
24
32
 
25
- private
26
-
27
33
  def reset_attributes_to_default(new_record)
28
34
  @configuration.defaulted_attributes.each do
29
35
  new_record[_1] = new_record.class.column_defaults[_1.to_s]
@@ -93,7 +99,7 @@ module Ditto
93
99
  Configuration.new
94
100
  end
95
101
 
96
- self.class.new(record, configuration).duplicate
102
+ self.class.new(record, configuration, @context).duplicate
97
103
  end
98
104
  end
99
105
  end
data/lib/ditto/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Ditto
4
- VERSION = "1.0.0"
4
+ VERSION = "1.1.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: acts_as_ditto
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Niklas Jäggi
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2026-08-28 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: activesupport
@@ -41,7 +40,8 @@ files:
41
40
  - lib/acts_as_ditto.rb
42
41
  - lib/ditto/acts_as_ditto.rb
43
42
  - lib/ditto/configuration.rb
44
- - lib/ditto/duplicator.rb
43
+ - lib/ditto/duplication/duplication_context.rb
44
+ - lib/ditto/duplication/duplicator.rb
45
45
  - lib/ditto/version.rb
46
46
  - sig/acts_as_ditto.rbs
47
47
  homepage: https://github.com/njaeggi/acts_as_ditto
@@ -49,9 +49,8 @@ licenses:
49
49
  - MIT
50
50
  metadata:
51
51
  homepage_uri: https://github.com/njaeggi/acts_as_ditto
52
- source_code_uri: https://github.com/njaeggi/acts_as_ditto/tree/main
53
- changelog_uri: https://github.com/njaeggi/acts_as_ditto/blob/main/CHANGELOG.md
54
- post_install_message:
52
+ source_code_uri: https://github.com/njaeggi/acts_as_ditto/tree/master
53
+ changelog_uri: https://github.com/njaeggi/acts_as_ditto/blob/master/CHANGELOG.md
55
54
  rdoc_options: []
56
55
  require_paths:
57
56
  - lib
@@ -66,8 +65,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
66
65
  - !ruby/object:Gem::Version
67
66
  version: '0'
68
67
  requirements: []
69
- rubygems_version: 3.5.3
70
- signing_key:
68
+ rubygems_version: 3.6.9
71
69
  specification_version: 4
72
70
  summary: Configurable ActiveRecord duplication.
73
71
  test_files: []