deep_cloneable 2.0.2 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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.