deep_cloneable 3.0.0 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2019 Reinier de Lange
1
+ Copyright (c) 2021 Reinier de Lange
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/Rakefile CHANGED
@@ -1,25 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rubygems'
2
4
  require 'bundler/setup'
3
5
  require 'appraisal'
4
-
5
- begin
6
- require 'jeweler'
7
- Jeweler::Tasks.new do |gem|
8
- gem.name = 'deep_cloneable'
9
- gem.summary = %(This gem gives every ActiveRecord::Base object the possibility to do a deep clone.)
10
- gem.description = %(Extends the functionality of ActiveRecord::Base#dup to perform a deep clone that includes user specified associations. )
11
- gem.email = 'rjdelange@icloud.com'
12
- gem.homepage = 'http://github.com/moiristo/deep_cloneable'
13
- gem.authors = ['Reinier de Lange']
14
- gem.license = 'MIT'
15
- # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
16
- end
17
- Jeweler::GemcutterTasks.new
18
- rescue LoadError
19
- puts 'Jeweler (or a dependency) not available. Install it with: gem install jeweler'
20
- end
21
-
22
6
  require 'rake/testtask'
7
+
23
8
  Rake::TestTask.new(:test) do |test|
24
9
  test.libs << 'lib' << 'test'
25
10
  test.pattern = 'test/**/test_*.rb'
@@ -1,79 +1,21 @@
1
- # Generated by jeweler
2
- # DO NOT EDIT THIS FILE DIRECTLY
3
- # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
- # -*- encoding: utf-8 -*-
5
- # stub: deep_cloneable 3.0.0 ruby lib
1
+ # frozen_string_literal: true
6
2
 
7
- Gem::Specification.new do |s|
8
- s.name = "deep_cloneable".freeze
9
- s.version = "3.0.0"
10
-
11
- s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
12
- s.require_paths = ["lib".freeze]
13
- s.authors = ["Reinier de Lange".freeze]
14
- s.date = "2019-08-18"
15
- s.description = "Extends the functionality of ActiveRecord::Base#dup to perform a deep clone that includes user specified associations. ".freeze
16
- s.email = "rjdelange@icloud.com".freeze
17
- s.extra_rdoc_files = [
18
- "LICENSE"
19
- ]
20
- s.files = [
21
- ".document",
22
- ".rubocop.yml",
23
- ".travis.yml",
24
- "Appraisals",
25
- "CHANGELOG.md",
26
- "Gemfile",
27
- "Gemfile.lock",
28
- "LICENSE",
29
- "Rakefile",
30
- "VERSION",
31
- "deep_cloneable.gemspec",
32
- "gemfiles/3.1.gemfile",
33
- "gemfiles/3.1.gemfile.lock",
34
- "gemfiles/3.2.gemfile",
35
- "gemfiles/3.2.gemfile.lock",
36
- "gemfiles/4.0.gemfile",
37
- "gemfiles/4.0.gemfile.lock",
38
- "gemfiles/4.1.gemfile",
39
- "gemfiles/4.1.gemfile.lock",
40
- "gemfiles/4.2.gemfile",
41
- "gemfiles/4.2.gemfile.lock",
42
- "gemfiles/5.0.gemfile",
43
- "gemfiles/5.0.gemfile.lock",
44
- "gemfiles/5.1.gemfile",
45
- "gemfiles/5.1.gemfile.lock",
46
- "gemfiles/5.2.gemfile",
47
- "gemfiles/5.2.gemfile.lock",
48
- "gemfiles/6.0.gemfile",
49
- "gemfiles/6.0.gemfile.lock",
50
- "init.rb",
51
- "lib/deep_cloneable.rb",
52
- "lib/deep_cloneable/association_not_found_exception.rb",
53
- "lib/deep_cloneable/deep_clone.rb",
54
- "lib/deep_cloneable/skip_validations.rb",
55
- "readme.md",
56
- "test/database.yml",
57
- "test/models.rb",
58
- "test/schema.rb",
59
- "test/test_deep_cloneable.rb",
60
- "test/test_helper.rb"
61
- ]
62
- s.homepage = "http://github.com/moiristo/deep_cloneable".freeze
63
- s.licenses = ["MIT".freeze]
64
- s.rubygems_version = "3.0.2".freeze
65
- s.summary = "This gem gives every ActiveRecord::Base object the possibility to do a deep clone.".freeze
66
-
67
- if s.respond_to? :specification_version then
68
- s.specification_version = 4
3
+ $:.unshift File.expand_path('../lib', __FILE__)
4
+ require 'deep_cloneable/version'
69
5
 
70
- if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
71
- s.add_runtime_dependency(%q<activerecord>.freeze, [">= 3.1.0", "< 7"])
72
- else
73
- s.add_dependency(%q<activerecord>.freeze, [">= 3.1.0", "< 7"])
74
- end
75
- else
76
- s.add_dependency(%q<activerecord>.freeze, [">= 3.1.0", "< 7"])
77
- end
6
+ Gem::Specification.new do |s|
7
+ s.name = 'deep_cloneable'
8
+ s.version = DeepCloneable::VERSION
9
+ s.authors = ['Reinier de Lange']
10
+ s.description = 'Extends the functionality of ActiveRecord::Base#dup to perform a deep clone that includes user specified associations. '
11
+ s.summary = 'This gem gives every ActiveRecord::Base object the possibility to do a deep clone.'
12
+ s.email = 'rjdelange@icloud.com'
13
+ s.extra_rdoc_files = ['LICENSE']
14
+ s.files = Dir.glob('{bin/*,lib/**/*,[A-Z]*}')
15
+ s.homepage = 'https://github.com/moiristo/deep_cloneable'
16
+ s.licenses = ['MIT']
17
+ s.platform = Gem::Platform::RUBY
18
+ s.required_ruby_version = '>= 1.9.3'
19
+ s.require_paths = ['lib']
20
+ s.add_runtime_dependency('activerecord', ['>= 3.1.0', '< 7'])
78
21
  end
79
-
data/init.rb CHANGED
@@ -1 +1,3 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'deep_cloneable'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_record'
2
4
  require 'active_support/lazy_load_hooks'
3
5
  require 'active_support/core_ext/array/wrap'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DeepCloneable
2
4
  class AssociationNotFoundException < StandardError
3
5
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DeepCloneable
2
4
  module DeepClone
3
5
  # Deep dups an ActiveRecord model. See README.rdoc
@@ -5,7 +7,7 @@ module DeepCloneable
5
7
  options = args[0] || {}
6
8
 
7
9
  dictionary = options[:dictionary]
8
- dictionary ||= {} if options.delete(:use_dictionary)
10
+ dictionary ||= {} if options[:use_dictionary]
9
11
 
10
12
  kopy = if dictionary
11
13
  find_in_dictionary_or_dup(dictionary)
@@ -13,6 +15,8 @@ module DeepCloneable
13
15
  dup
14
16
  end
15
17
 
18
+ options[:preprocessor].call(self, kopy) if options.key?(:preprocessor)
19
+
16
20
  deep_exceptions = {}
17
21
  if options[:except]
18
22
  exceptions = Array.wrap(options[:except])
@@ -44,13 +48,21 @@ module DeepCloneable
44
48
  association = association.keys.first
45
49
  end
46
50
 
47
- if conditions_or_deep_associations.is_a?(Hash)
51
+ case conditions_or_deep_associations
52
+ when Hash
48
53
  conditions_or_deep_associations = conditions_or_deep_associations.dup
49
54
  conditions[:if] = conditions_or_deep_associations.delete(:if) if conditions_or_deep_associations[:if]
50
55
  conditions[:unless] = conditions_or_deep_associations.delete(:unless) if conditions_or_deep_associations[:unless]
51
- elsif conditions_or_deep_associations.is_a?(Array)
52
- conditions_or_deep_associations = conditions_or_deep_associations.dup
53
- conditions_or_deep_associations.delete_if { |entry| conditions.merge!(entry) if entry.is_a?(Hash) && (entry.key?(:if) || entry.key?(:unless)) }
56
+ when Array
57
+ conditions_or_deep_associations = conditions_or_deep_associations.map { |entry| entry.is_a?(Hash) ? entry.dup : entry }
58
+ conditions_or_deep_associations.each_with_index do |entry, index|
59
+ if entry.is_a?(Hash)
60
+ conditions[:if] = entry.delete(:if) if entry[:if]
61
+ conditions[:unless] = entry.delete(:unless) if entry[:unless]
62
+ end
63
+
64
+ conditions_or_deep_associations.delete_at(index) if entry.empty?
65
+ end
54
66
  end
55
67
 
56
68
  dup_options = {}
@@ -59,7 +71,7 @@ module DeepCloneable
59
71
  dup_options[:only] = deep_onlinesses[association] if deep_onlinesses[association]
60
72
  dup_options[:dictionary] = dictionary if dictionary
61
73
 
62
- [:skip_missing_associations, :validate].each do |option|
74
+ [:skip_missing_associations, :validate, :preprocessor, :postprocessor].each do |option|
63
75
  dup_options[option] = options[option] if options.key?(option)
64
76
  end
65
77
 
@@ -81,6 +93,7 @@ module DeepCloneable
81
93
  end
82
94
 
83
95
  yield(self, kopy) if block
96
+ options[:postprocessor].call(self, kopy) if options.key?(:postprocessor)
84
97
 
85
98
  kopy
86
99
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DeepCloneable
2
4
  module SkipValidations
3
5
  def perform_validations(options = {})
@@ -0,0 +1,3 @@
1
+ module DeepCloneable
2
+ VERSION = '3.1.0'
3
+ end
data/readme.md CHANGED
@@ -6,16 +6,17 @@ This gem gives every ActiveRecord::Base object the possibility to do a deep clon
6
6
 
7
7
  ## Requirements
8
8
 
9
- * Ruby 1.9.3, 2.0.0, 2.1.5, 2.2.2, 2.3.0, 2.4.4, 2.5.5 (tested)
10
- * Activerecord 3.1, 3.2, 4.0, 4.1, 4.2, 5.0, 5.1, 5.2, 6.0 (tested)
11
- * Rails 2.x/3.0 users, please check out the 'rails2.x-3.0' branch
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
12
13
 
13
14
  ## Installation
14
15
 
15
- * Add deep_cloneable to your Gemfile:
16
+ - Add deep_cloneable to your Gemfile:
16
17
 
17
18
  ```ruby
18
- gem 'deep_cloneable', '~> 3.0.0'
19
+ gem 'deep_cloneable', '~> 3.1.0'
19
20
  ```
20
21
 
21
22
  ## Upgrade details
@@ -24,8 +25,8 @@ gem 'deep_cloneable', '~> 3.0.0'
24
25
 
25
26
  There are two breaking changes that you might need to pay attention to:
26
27
 
27
- * 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.
28
- * 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`.
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`.
29
30
 
30
31
  ### Upgrading from v1
31
32
 
@@ -33,7 +34,7 @@ The `dup` method with arguments has been replaced in deep_cloneable 2 by the met
33
34
 
34
35
  ## Usage
35
36
 
36
- 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.
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.
37
38
 
38
39
  ### Association inclusion
39
40
 
@@ -85,6 +86,7 @@ pirate.deep_clone include: [ :mateys, { treasures: [ :matey, :gold_pieces ] } ],
85
86
  The `deep_clone` method supports both `except` and `only` for specifying which attributes should be duped:
86
87
 
87
88
  #### Exceptions
89
+
88
90
  ```ruby
89
91
  # Single exception
90
92
  pirate.deep_clone except: :name
@@ -97,6 +99,7 @@ pirate.deep_clone include: :parrot, except: [ :name, { parrot: [ :name ] } ]
97
99
  ```
98
100
 
99
101
  #### Inclusions
102
+
100
103
  ```ruby
101
104
  # Single attribute inclusion
102
105
  pirate.deep_clone only: :name
@@ -109,6 +112,19 @@ pirate.deep_clone include: :parrot, only: [ :name, { parrot: [ :name ] } ]
109
112
 
110
113
  ```
111
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
+
112
128
  ### Optional Block
113
129
 
114
130
  Pass a block to `deep_clone` to modify a duped object after duplication:
@@ -119,9 +135,7 @@ pirate.deep_clone include: :parrot do |original, kopy|
119
135
  end
120
136
  ```
121
137
 
122
- *Note*: The block is invoked before attributes are excluded.
123
-
124
- *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.
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.
125
139
 
126
140
  ### Cloning models with files
127
141
 
@@ -130,6 +144,7 @@ end
130
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.
131
145
 
132
146
  Easiest solution is to add the code in a clone block as described above.
147
+
133
148
  ```ruby
134
149
  pirate.deep_clone include: :parrot do |original, kopy|
135
150
  kopy.thumbnail = original.thumbnail
@@ -143,11 +158,52 @@ For ActiveStorage, you have two options: you can either make a full copy, or sha
143
158
  ##### Full copy example
144
159
 
145
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
146
202
  pirate.deep_clone include: :parrot do |original, kopy|
147
203
  if kopy.is_a?(Pirate) && original.avatar.attached?
148
- ActiveStorage::Downloader.new(original.avatar).download_blob_to_tempfile do |tempfile|
204
+ original.avatar.open do |tempfile|
149
205
  kopy.avatar.attach({
150
- io: tempfile,
206
+ io: File.open(tempfile.path),
151
207
  filename: original.avatar.blob.filename,
152
208
  content_type: original.avatar.blob.content_type
153
209
  })
@@ -156,7 +212,6 @@ pirate.deep_clone include: :parrot do |original, kopy|
156
212
  end
157
213
  ```
158
214
 
159
-
160
215
  ##### Shallow copy example
161
216
 
162
217
  ```ruby
@@ -175,14 +230,14 @@ pirate.deep_clone include: [:parrot, :rum], skip_missing_associations: true
175
230
 
176
231
  ### Note on Patches/Pull Requests
177
232
 
178
- * Fork the project.
179
- * Make your feature addition or bug fix.
180
- * Add tests for it. This is important so I don't break it in a
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
181
236
  future version unintentionally.
182
- * Commit, do not mess with rakefile, version, or history.
237
+ - Commit, do not mess with rakefile, version, or history.
183
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)
184
- * Send me a pull request. Bonus points for topic branches.
239
+ - Send me a pull request. Bonus points for topic branches.
185
240
 
186
241
  ### Copyright
187
242
 
188
- Copyright &copy; 2019 Reinier de Lange. See LICENSE for details.
243
+ Copyright &copy; 2021 Reinier de Lange. See LICENSE for details.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: deep_cloneable
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.0
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: 2019-08-18 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
@@ -38,51 +38,25 @@ extensions: []
38
38
  extra_rdoc_files:
39
39
  - LICENSE
40
40
  files:
41
- - ".document"
42
- - ".rubocop.yml"
43
- - ".travis.yml"
44
41
  - Appraisals
45
42
  - CHANGELOG.md
46
43
  - Gemfile
47
44
  - Gemfile.lock
48
45
  - LICENSE
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
- - gemfiles/5.0.gemfile
63
- - gemfiles/5.0.gemfile.lock
64
- - gemfiles/5.1.gemfile
65
- - gemfiles/5.1.gemfile.lock
66
- - gemfiles/5.2.gemfile
67
- - gemfiles/5.2.gemfile.lock
68
- - gemfiles/6.0.gemfile
69
- - gemfiles/6.0.gemfile.lock
70
48
  - init.rb
71
49
  - lib/deep_cloneable.rb
72
50
  - lib/deep_cloneable/association_not_found_exception.rb
73
51
  - lib/deep_cloneable/deep_clone.rb
74
52
  - lib/deep_cloneable/skip_validations.rb
53
+ - lib/deep_cloneable/version.rb
75
54
  - readme.md
76
- - test/database.yml
77
- - test/models.rb
78
- - test/schema.rb
79
- - test/test_deep_cloneable.rb
80
- - test/test_helper.rb
81
- homepage: http://github.com/moiristo/deep_cloneable
55
+ homepage: https://github.com/moiristo/deep_cloneable
82
56
  licenses:
83
57
  - MIT
84
58
  metadata: {}
85
- post_install_message:
59
+ post_install_message:
86
60
  rdoc_options: []
87
61
  require_paths:
88
62
  - lib
@@ -90,15 +64,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
90
64
  requirements:
91
65
  - - ">="
92
66
  - !ruby/object:Gem::Version
93
- version: '0'
67
+ version: 1.9.3
94
68
  required_rubygems_version: !ruby/object:Gem::Requirement
95
69
  requirements:
96
70
  - - ">="
97
71
  - !ruby/object:Gem::Version
98
72
  version: '0'
99
73
  requirements: []
100
- rubygems_version: 3.0.2
101
- signing_key:
74
+ rubygems_version: 3.0.3
75
+ signing_key:
102
76
  specification_version: 4
103
77
  summary: This gem gives every ActiveRecord::Base object the possibility to do a deep
104
78
  clone.