deep_cloneable 2.0.2 → 3.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.
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DeepCloneable
4
+ module SkipValidations
5
+ def perform_validations(options = {})
6
+ options[:validate] = false
7
+ super(options)
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,3 @@
1
+ module DeepCloneable
2
+ VERSION = '3.1.0'
3
+ end
data/readme.md ADDED
@@ -0,0 +1,243 @@
1
+ # deep_cloneable
2
+
3
+ [![Build Status](https://travis-ci.org/moiristo/deep_cloneable.svg?branch=master)](https://travis-ci.org/moiristo/deep_cloneable)
4
+
5
+ This gem gives every ActiveRecord::Base object the possibility to do a deep clone that includes user specified associations. It is a rails 3+ upgrade of the [deep_cloning plugin](http://github.com/openminds/deep_cloning).
6
+
7
+ ## Requirements
8
+
9
+ - Ruby 1.9.3, 2.0.0, 2.1.5, 2.2.2, 2.3.0, 2.4.4, 2.5.5, 2.6.3 (tested)
10
+ - TruffleRuby 20.2.0
11
+ - Activerecord 3.1, 3.2, 4.0, 4.1, 4.2, 5.0, 5.1, 5.2, 6.0 (tested)
12
+ - Rails 2.x/3.0 users, please check out the 'rails2.x-3.0' branch
13
+
14
+ ## Installation
15
+
16
+ - Add deep_cloneable to your Gemfile:
17
+
18
+ ```ruby
19
+ gem 'deep_cloneable', '~> 3.1.0'
20
+ ```
21
+
22
+ ## Upgrade details
23
+
24
+ ### Upgrading from v2
25
+
26
+ There are two breaking changes that you might need to pay attention to:
27
+
28
+ - When using an optional block (see below), the block used to be evaluated before `deep_cloneable` had performed its changes (inclusions, exclusions, includes). In v3, the block is evaluated after all processing has been done, just before the copy is about to be returned.
29
+ - When a defined association is not found, `deep_cloneable` raises an exception. The exception class has changed namespace: the class definition used to be `ActiveRecord::Base::DeepCloneable::AssociationNotFoundException` and this has changed to `DeepCloneable::AssociationNotFoundException`.
30
+
31
+ ### Upgrading from v1
32
+
33
+ The `dup` method with arguments has been replaced in deep_cloneable 2 by the method `deep_clone`. Please update your sources accordingly.
34
+
35
+ ## Usage
36
+
37
+ The `deep_clone` method supports a couple options that can be specified by passing an options hash. Without options, the behaviour is the same as ActiveRecord's [`dup`](http://apidock.com/rails/ActiveRecord/Core/dup) method.
38
+
39
+ ### Association inclusion
40
+
41
+ Associations to be included in the dup can be specified with the `include` option:
42
+
43
+ ```ruby
44
+ # Single include
45
+ pirate.deep_clone include: :mateys
46
+
47
+ # Multiple includes
48
+ pirate.deep_clone include: [ :mateys, :treasures ]
49
+
50
+ # Deep includes
51
+ pirate.deep_clone include: { treasures: :gold_pieces }
52
+ pirate.deep_clone include: [ :mateys, { treasures: :gold_pieces } ]
53
+
54
+ # Disable validation for a performance speedup when saving the dup
55
+ pirate.deep_clone include: { treasures: :gold_pieces }, validate: false
56
+
57
+ # Conditional includes
58
+ pirate.deep_clone include: [
59
+ {
60
+ treasures: { gold_pieces: { if: lambda{|piece| piece.is_a?(Parrot) } } } },
61
+ mateys: { unless: lambda{|matey| matey.is_a?(GoldPiece) }
62
+ }
63
+ ]
64
+
65
+ ship.deep_clone include: [
66
+ pirates: [ :treasures, :mateys, if: lambda {|pirate| pirate.name == 'Jack Sparrow' } ]
67
+ ]
68
+ ```
69
+
70
+ #### The Dictionary (Object Reusage)
71
+
72
+ The dictionary ensures that models are not duped multiple times when it is associated to nested models. It does this by storing a mapping of the original object to its duped object. It can be used as follows:
73
+
74
+ ```ruby
75
+ # Enables the dictionary (empty on initialization)
76
+ pirate.deep_clone include: [ :mateys, { treasures: [ :matey, :gold_pieces ] } ], use_dictionary: true
77
+
78
+ # Deep clones with a prefilled dictionary
79
+ dictionary = { mateys: {} }
80
+ pirate.mateys.each{|m| dict[:mateys][m] = m.deep_clone }
81
+ pirate.deep_clone include: [ :mateys, { treasures: [ :matey, :gold_pieces ] } ], dictionary: dictionary
82
+ ```
83
+
84
+ ### Attribute Exceptions & Inclusions
85
+
86
+ The `deep_clone` method supports both `except` and `only` for specifying which attributes should be duped:
87
+
88
+ #### Exceptions
89
+
90
+ ```ruby
91
+ # Single exception
92
+ pirate.deep_clone except: :name
93
+
94
+ # Multiple exceptions
95
+ pirate.deep_clone except: [ :name, :nick_name ]
96
+
97
+ # Nested exceptions
98
+ pirate.deep_clone include: :parrot, except: [ :name, { parrot: [ :name ] } ]
99
+ ```
100
+
101
+ #### Inclusions
102
+
103
+ ```ruby
104
+ # Single attribute inclusion
105
+ pirate.deep_clone only: :name
106
+
107
+ # Multiple attribute inclusions
108
+ pirate.deep_clone only: [ :name, :nick_name ]
109
+
110
+ # Nested attribute inclusions
111
+ pirate.deep_clone include: :parrot, only: [ :name, { parrot: [ :name ] } ]
112
+
113
+ ```
114
+
115
+ ### Pre- and postprocessor
116
+
117
+ You can specify a pre- and/or a postprocessor to modify a duped object after duplication:
118
+
119
+ ```ruby
120
+ pirate.deep_clone(include: :parrot, preprocessor: ->(original, kopy) { kopy.cloned_from_id = original.id if kopy.respond_to?(:cloned_from_id) })
121
+ pirate.deep_clone(include: :parrot, postprocessor: ->(original, kopy) { kopy.cloned_from_id = original.id if kopy.respond_to?(:cloned_from_id) })
122
+ ```
123
+
124
+ _Note_: Specifying a postprocessor is essentially the same as specifying an optional block (see below).
125
+
126
+ _Note_: Using `deep_clone` with a processors will pass all associated objects that are being cloned to the processor, so be sure to check whether the object actually responds to your method of choice.
127
+
128
+ ### Optional Block
129
+
130
+ Pass a block to `deep_clone` to modify a duped object after duplication:
131
+
132
+ ```ruby
133
+ pirate.deep_clone include: :parrot do |original, kopy|
134
+ kopy.cloned_from_id = original.id if kopy.respond_to?(:cloned_from_id)
135
+ end
136
+ ```
137
+
138
+ _Note_: Using `deep_clone` with a block will also pass the associated objects that are being cloned to the block, so be sure to check whether the object actually responds to your method of choice.
139
+
140
+ ### Cloning models with files
141
+
142
+ #### Carrierwave
143
+
144
+ If you are cloning models that have associated files through Carrierwave these will not get transferred automatically. To overcome the issue you need to explicitly set the file attribute.
145
+
146
+ Easiest solution is to add the code in a clone block as described above.
147
+
148
+ ```ruby
149
+ pirate.deep_clone include: :parrot do |original, kopy|
150
+ kopy.thumbnail = original.thumbnail
151
+ end
152
+ ```
153
+
154
+ #### ActiveStorage
155
+
156
+ For ActiveStorage, you have two options: you can either make a full copy, or share data blobs between two records.
157
+
158
+ ##### Full copy example
159
+
160
+ ```ruby
161
+ # Rails 5.2, has_one_attached example 1
162
+ pirate.deep_clone include: [:parrot, :avatar_attachment, :avatar_blob]
163
+
164
+ # Rails 5.2, has_one_attached example 2
165
+ pirate.deep_clone include: :parrot do |original, kopy|
166
+ if kopy.is_a?(Pirate) && original.avatar.attached?
167
+ attachment = original.avatar
168
+ kopy.avatar.attach \
169
+ :io => StringIO.new(attachment.download),
170
+ :filename => attachment.filename,
171
+ :content_type => attachment.content_type
172
+ end
173
+ end
174
+
175
+ # Rails 5.2, has_many_attached example 1 (attach one by one)
176
+ pirate.deep_clone include: :parrot do |original, kopy|
177
+ if kopy.is_a?(Pirate) && original.crew_members_images.attached?
178
+ original.crew_members_images.each do |attachment|
179
+ kopy.crew_members_images.attach \
180
+ :io => StringIO.new(attachment.download),
181
+ :filename => attachment.filename,
182
+ :content_type => attachment.content_type
183
+ end
184
+ end
185
+ end
186
+
187
+ # Rails 5.2, has_many_attached example 2 (attach bulk)
188
+ pirate.deep_clone include: :parrot do |original, kopy|
189
+ if kopy.is_a?(Pirate) && original.crew_members_images.attached?
190
+ all_attachments_arr = original.crew_members_images.map do |attachment|
191
+ {
192
+ :io => StringIO.new(attachment.download),
193
+ :filename => attachment.filename,
194
+ :content_type => attachment.content_type
195
+ }
196
+ end
197
+ kopy.crew_members_images.attach(all_attachments_arr) # attach all at once
198
+ end
199
+ end
200
+
201
+ # Rails 6
202
+ pirate.deep_clone include: :parrot do |original, kopy|
203
+ if kopy.is_a?(Pirate) && original.avatar.attached?
204
+ original.avatar.open do |tempfile|
205
+ kopy.avatar.attach({
206
+ io: File.open(tempfile.path),
207
+ filename: original.avatar.blob.filename,
208
+ content_type: original.avatar.blob.content_type
209
+ })
210
+ end
211
+ end
212
+ end
213
+ ```
214
+
215
+ ##### Shallow copy example
216
+
217
+ ```ruby
218
+ pirate.deep_clone include: :parrot do |original, kopy|
219
+ kopy.avatar.attach(original.avatar.blob) if kopy.is_a?(Pirate) && original.avatar.attached?
220
+ end
221
+ ```
222
+
223
+ ### Skipping missing associations
224
+
225
+ By default, deep_cloneable will throw a `DeepCloneable::AssociationNotFoundException` error when an association cannot be found. You can also skip missing associations by specifying `skip_missing_associations` if needed, for example when you have associations on some (but not all) subclasses of an STI model:
226
+
227
+ ```ruby
228
+ pirate.deep_clone include: [:parrot, :rum], skip_missing_associations: true
229
+ ```
230
+
231
+ ### Note on Patches/Pull Requests
232
+
233
+ - Fork the project.
234
+ - Make your feature addition or bug fix.
235
+ - Add tests for it. This is important so I don't break it in a
236
+ future version unintentionally.
237
+ - Commit, do not mess with rakefile, version, or history.
238
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
239
+ - Send me a pull request. Bonus points for topic branches.
240
+
241
+ ### Copyright
242
+
243
+ Copyright © 2021 Reinier de Lange. See LICENSE for details.
metadata CHANGED
@@ -1,75 +1,62 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: deep_cloneable
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.2
4
+ version: 3.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Reinier de Lange
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-12-17 00:00:00.000000000 Z
11
+ date: 2021-02-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "<"
18
- - !ruby/object:Gem::Version
19
- version: 5.0.0
20
17
  - - ">="
21
18
  - !ruby/object:Gem::Version
22
19
  version: 3.1.0
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '7'
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
26
26
  requirements:
27
- - - "<"
28
- - !ruby/object:Gem::Version
29
- version: 5.0.0
30
27
  - - ">="
31
28
  - !ruby/object:Gem::Version
32
29
  version: 3.1.0
33
- description: 'Extends the functionality of ActiveRecord::Base#clone to perform a deep
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '7'
33
+ description: 'Extends the functionality of ActiveRecord::Base#dup to perform a deep
34
34
  clone that includes user specified associations. '
35
- email: r.j.delange@nedforce.nl
35
+ email: rjdelange@icloud.com
36
36
  executables: []
37
37
  extensions: []
38
38
  extra_rdoc_files:
39
39
  - LICENSE
40
- - README.rdoc
41
40
  files:
42
- - ".document"
43
- - ".travis.yml"
44
41
  - Appraisals
42
+ - CHANGELOG.md
45
43
  - Gemfile
46
44
  - Gemfile.lock
47
45
  - LICENSE
48
- - README.rdoc
49
46
  - Rakefile
50
- - VERSION
51
47
  - deep_cloneable.gemspec
52
- - gemfiles/3.1.gemfile
53
- - gemfiles/3.1.gemfile.lock
54
- - gemfiles/3.2.gemfile
55
- - gemfiles/3.2.gemfile.lock
56
- - gemfiles/4.0.gemfile
57
- - gemfiles/4.0.gemfile.lock
58
- - gemfiles/4.1.gemfile
59
- - gemfiles/4.1.gemfile.lock
60
- - gemfiles/4.2.gemfile
61
- - gemfiles/4.2.gemfile.lock
62
48
  - init.rb
63
49
  - lib/deep_cloneable.rb
64
- - test/database.yml
65
- - test/models.rb
66
- - test/schema.rb
67
- - test/test_deep_cloneable.rb
68
- - test/test_helper.rb
69
- homepage: http://github.com/moiristo/deep_cloneable
70
- licenses: []
50
+ - lib/deep_cloneable/association_not_found_exception.rb
51
+ - lib/deep_cloneable/deep_clone.rb
52
+ - lib/deep_cloneable/skip_validations.rb
53
+ - lib/deep_cloneable/version.rb
54
+ - readme.md
55
+ homepage: https://github.com/moiristo/deep_cloneable
56
+ licenses:
57
+ - MIT
71
58
  metadata: {}
72
- post_install_message:
59
+ post_install_message:
73
60
  rdoc_options: []
74
61
  require_paths:
75
62
  - lib
@@ -77,16 +64,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
77
64
  requirements:
78
65
  - - ">="
79
66
  - !ruby/object:Gem::Version
80
- version: '0'
67
+ version: 1.9.3
81
68
  required_rubygems_version: !ruby/object:Gem::Requirement
82
69
  requirements:
83
70
  - - ">="
84
71
  - !ruby/object:Gem::Version
85
72
  version: '0'
86
73
  requirements: []
87
- rubyforge_project:
88
- rubygems_version: 2.4.2
89
- signing_key:
74
+ rubygems_version: 3.0.3
75
+ signing_key:
90
76
  specification_version: 4
91
77
  summary: This gem gives every ActiveRecord::Base object the possibility to do a deep
92
78
  clone.
data/.document DELETED
@@ -1,5 +0,0 @@
1
- README.rdoc
2
- lib/**/*.rb
3
- bin/*
4
- features/**/*.feature
5
- LICENSE
data/.travis.yml DELETED
@@ -1,28 +0,0 @@
1
- rvm:
2
- - 1.8.7
3
- - 1.9.2
4
- - 1.9.3
5
- - 2.0.0
6
- - 2.1.5
7
-
8
- gemfile:
9
- - gemfiles/3.1.gemfile
10
- - gemfiles/3.2.gemfile
11
- - gemfiles/4.0.gemfile
12
- - gemfiles/4.1.gemfile
13
- - gemfiles/4.2.gemfile
14
-
15
- matrix:
16
- exclude:
17
- - rvm: 1.8.7
18
- gemfile: gemfiles/4.0.gemfile
19
- - rvm: 1.8.7
20
- gemfile: gemfiles/4.1.gemfile
21
- - rvm: 1.8.7
22
- gemfile: gemfiles/4.2.gemfile
23
- - rvm: 1.9.2
24
- gemfile: gemfiles/4.0.gemfile
25
- - rvm: 1.9.2
26
- gemfile: gemfiles/4.1.gemfile
27
- - rvm: 1.9.2
28
- gemfile: gemfiles/4.2.gemfile
data/README.rdoc DELETED
@@ -1,95 +0,0 @@
1
- = Deep_cloneable
2
-
3
- {<img src="https://travis-ci.org/moiristo/deep_cloneable.png?branch=master" alt="Build Status" />}[https://travis-ci.org/moiristo/deep_cloneable]
4
-
5
- This gem gives every ActiveRecord::Base object the possibility to do a deep clone. It is a rails3 upgrade of the deep_cloning plugin (http://github.com/openminds/deep_cloning).
6
-
7
- == Requirements
8
-
9
- * Ruby 1.8.7, 1.9.2, 1.9.3, 2.0.0, 2.1.5 (tested)
10
-
11
- * Activerecord 3.1, 3.2, 4.0, 4.1, 4.2.0.rc3
12
-
13
- * Rails 2.x/3.0 users, please check out the 'rails2.x-3.0' branch.
14
-
15
- == Installation
16
-
17
- * In your Gemfile:
18
-
19
- gem 'deep_cloneable', '~> 2.0.2'
20
-
21
- == Upgrading from v1
22
-
23
- The 'dup' method with arguments has been replaced in deep_cloneable 2 by the method 'deep_clone'. Please update your sources accordingly.
24
-
25
- == Example
26
-
27
- === Cloning one single association
28
- pirate.deep_clone :include => :mateys
29
-
30
- === Cloning multiple associations
31
- pirate.deep_clone :include => [:mateys, :treasures]
32
-
33
- === Cloning really deep
34
- pirate.deep_clone :include => {:treasures => :gold_pieces}
35
-
36
- === Cloning really deep with multiple associations
37
- pirate.deep_clone :include => [:mateys, {:treasures => :gold_pieces}]
38
-
39
- === Cloning really deep with multiple associations and a dictionary
40
-
41
- A dictionary ensures that models are not cloned multiple times when it is associated to nested models.
42
- When using a dictionary, ensure recurring associations are cloned first:
43
-
44
- pirate.deep_clone :include => [:mateys, {:treasures => [:matey, :gold_pieces]}], :use_dictionary => true
45
-
46
- If this is not an option for you, it is also possible to populate the dictionary manually in advance:
47
-
48
- dict = { :mateys => {} }
49
- pirate.mateys.each{|m| dict[:mateys][m] = m.deep_clone }
50
- pirate.deep_clone :include => [:mateys, {:treasures => [:matey, :gold_pieces]}], :dictionary => dict
51
-
52
- When an object isn't found in the dictionary, it will be populated. By passing in an empty dictionary you can populate it automatically and reuse it in subsequent deep_clones to avoid creating multiples of the same object where you have overlapping associations.
53
-
54
- === Cloning a model without an attribute
55
- pirate.deep_clone :except => :name
56
-
57
- === Cloning a model without multiple attributes
58
- pirate.deep_clone :except => [:name, :nick_name]
59
-
60
- === Cloning a model without an attribute or nested multiple attributes
61
- pirate.deep_clone :include => :parrot, :except => [:name, { :parrot => [:name] }]
62
-
63
- === Cloning with a block
64
- pirate.deep_clone :include => :parrot do |original, kopy|
65
- kopy.cloned_from_id = original.id if kopy.respond_to?(:cloned_from_id)
66
- end
67
-
68
- *Note*: Using deep_clone with a block will also pass the associated objects that are being cloned to the block,
69
- so be sure to check whether the object actually responds to your method of choice.
70
-
71
- === Cloning without validations
72
- pirate.deep_clone :include => {:treasures => :gold_pieces}, :validate => false
73
-
74
- === Cloning a model with only explicitly assigned attribute
75
- pirate.deep_clone :only => :name
76
-
77
- === Cloning a model with only multiple explicitly assigned attributes
78
- pirate.deep_clone :only => [:name, :nick_name]
79
-
80
- === Cloning a model with explicitly assigned attributes or nested multiple attributes
81
- pirate.deep_clone :include => :parrot, :only => [:name, { :parrot => [:name] }]
82
-
83
- == Note on Patches/Pull Requests
84
-
85
- * Fork the project.
86
- * Make your feature addition or bug fix.
87
- * Add tests for it. This is important so I don't break it in a
88
- future version unintentionally.
89
- * Commit, do not mess with rakefile, version, or history.
90
- (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
91
- * Send me a pull request. Bonus points for topic branches.
92
-
93
- == Copyright
94
-
95
- Copyright (c) 2014 Reinier de Lange. See LICENSE for details.