deep_cloneable 2.4.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,16 +1,16 @@
1
1
  # This file was generated by Appraisal
2
2
 
3
- source "http://rubygems.org"
3
+ source 'http://rubygems.org'
4
4
 
5
- gem "activerecord", "~> 5.0.0"
6
- gem "highline", "~> 1.6.0", :group => :test
7
- gem "rake", "~> 10.4", :group => :test
8
- gem "rack", "~> 1.6", :group => :test
9
- gem "git", "~> 1.2.9", :group => :test
10
- gem "minitest", :group => :test
11
- gem "appraisal", :group => :test
12
- gem "sqlite3", :group => :test
13
- gem "rdoc", ">= 2.4.2", "< 6", :group => :test
14
- gem "nokogiri", "~> 1.5.0", :group => :test
15
- gem "jeweler", :group => :test
16
- gem "i18n", "~> 0.7.0"
5
+ gem 'activerecord', '~> 5.0.0'
6
+ gem 'appraisal', :group => :test
7
+ gem 'git', '~> 1.2.9', :group => :test
8
+ gem 'highline', '~> 1.6.0', :group => :test
9
+ gem 'i18n', '~> 0.7.0'
10
+ gem 'jeweler', :group => :test
11
+ gem 'minitest', :group => :test
12
+ gem 'nokogiri', '~> 1.5.0', :group => :test
13
+ gem 'rack', '~> 1.6', :group => :test
14
+ gem 'rake', '~> 10.4', :group => :test
15
+ gem 'rdoc', '>= 2.4.2', '< 6', :group => :test
16
+ gem 'sqlite3', :group => :test
@@ -1,16 +1,16 @@
1
1
  # This file was generated by Appraisal
2
2
 
3
- source "http://rubygems.org"
3
+ source 'http://rubygems.org'
4
4
 
5
- gem "activerecord", "~> 5.1.0"
6
- gem "highline", "~> 1.6.0", :group => :test
7
- gem "rake", "~> 10.4", :group => :test
8
- gem "rack", "~> 1.6", :group => :test
9
- gem "git", "~> 1.2.9", :group => :test
10
- gem "minitest", :group => :test
11
- gem "appraisal", :group => :test
12
- gem "sqlite3", :group => :test
13
- gem "rdoc", ">= 2.4.2", "< 6", :group => :test
14
- gem "nokogiri", "~> 1.5.0", :group => :test
15
- gem "jeweler", :group => :test
16
- gem "i18n", "~> 0.7.0"
5
+ gem 'activerecord', '~> 5.1.0'
6
+ gem 'appraisal', :group => :test
7
+ gem 'git', '~> 1.2.9', :group => :test
8
+ gem 'highline', '~> 1.6.0', :group => :test
9
+ gem 'i18n', '~> 0.7.0'
10
+ gem 'jeweler', :group => :test
11
+ gem 'minitest', :group => :test
12
+ gem 'nokogiri', '~> 1.5.0', :group => :test
13
+ gem 'rack', '~> 1.6', :group => :test
14
+ gem 'rake', '~> 10.4', :group => :test
15
+ gem 'rdoc', '>= 2.4.2', '< 6', :group => :test
16
+ gem 'sqlite3', :group => :test
@@ -1,15 +1,15 @@
1
1
  # This file was generated by Appraisal
2
2
 
3
- source "http://rubygems.org"
3
+ source 'http://rubygems.org'
4
4
 
5
- gem "activerecord", "~> 5.2.0"
6
- gem "highline", "~> 1.6.0", :group => :test
7
- gem "rake", "~> 10.4", :group => :test
8
- gem "rack", "~> 1.6", :group => :test
9
- gem "git", "~> 1.2.9", :group => :test
10
- gem "minitest", :group => :test
11
- gem "appraisal", :group => :test
12
- gem "sqlite3", :group => :test
13
- gem "rdoc", ">= 2.4.2", "< 6", :group => :test
14
- gem "nokogiri", "~> 1.5.0", :group => :test
15
- gem "jeweler", :group => :test
5
+ gem 'activerecord', '~> 5.2.0'
6
+ gem 'appraisal', :group => :test
7
+ gem 'git', '~> 1.2.9', :group => :test
8
+ gem 'highline', '~> 1.6.0', :group => :test
9
+ gem 'jeweler', :group => :test
10
+ gem 'minitest', :group => :test
11
+ gem 'nokogiri', '~> 1.5.0', :group => :test
12
+ gem 'rack', '~> 1.6', :group => :test
13
+ gem 'rake', '~> 10.4', :group => :test
14
+ gem 'rdoc', '>= 2.4.2', '< 6', :group => :test
15
+ gem 'sqlite3', :group => :test
@@ -0,0 +1,15 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source 'http://rubygems.org'
4
+
5
+ gem 'activerecord', '~> 6.0.0.beta3'
6
+ gem 'appraisal', :group => :test
7
+ gem 'git', '~> 1.2.9', :group => :test
8
+ gem 'highline', '~> 1.6.0', :group => :test
9
+ gem 'jeweler', :group => :test
10
+ gem 'minitest', :group => :test
11
+ gem 'nokogiri', '~> 1.5.0', :group => :test
12
+ gem 'rack', '~> 1.6', :group => :test
13
+ gem 'rake', '~> 10.4', :group => :test
14
+ gem 'rdoc', '>= 2.4.2', '< 6', :group => :test
15
+ gem 'sqlite3', :group => :test
@@ -0,0 +1,91 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ activemodel (6.0.0.beta3)
5
+ activesupport (= 6.0.0.beta3)
6
+ activerecord (6.0.0.beta3)
7
+ activemodel (= 6.0.0.beta3)
8
+ activesupport (= 6.0.0.beta3)
9
+ activesupport (6.0.0.beta3)
10
+ concurrent-ruby (~> 1.0, >= 1.0.2)
11
+ i18n (>= 0.7, < 2)
12
+ minitest (~> 5.1)
13
+ tzinfo (~> 1.1)
14
+ zeitwerk (~> 1.3, >= 1.3.1)
15
+ addressable (2.4.0)
16
+ appraisal (2.2.0)
17
+ bundler
18
+ rake
19
+ thor (>= 0.14.0)
20
+ builder (3.2.3)
21
+ concurrent-ruby (1.1.5)
22
+ descendants_tracker (0.0.4)
23
+ thread_safe (~> 0.3, >= 0.3.1)
24
+ faraday (0.9.2)
25
+ multipart-post (>= 1.2, < 3)
26
+ git (1.2.9.1)
27
+ github_api (0.16.0)
28
+ addressable (~> 2.4.0)
29
+ descendants_tracker (~> 0.0.4)
30
+ faraday (~> 0.8, < 0.10)
31
+ hashie (>= 3.4)
32
+ mime-types (>= 1.16, < 3.0)
33
+ oauth2 (~> 1.0)
34
+ hashie (3.6.0)
35
+ highline (1.6.21)
36
+ i18n (1.6.0)
37
+ concurrent-ruby (~> 1.0)
38
+ jeweler (2.3.9)
39
+ builder
40
+ bundler
41
+ git (>= 1.2.5)
42
+ github_api (~> 0.16.0)
43
+ highline (>= 1.6.15)
44
+ nokogiri (>= 1.5.10)
45
+ psych
46
+ rake
47
+ rdoc
48
+ semver2
49
+ jwt (2.1.0)
50
+ mime-types (2.99.3)
51
+ minitest (5.11.3)
52
+ multi_json (1.13.1)
53
+ multi_xml (0.6.0)
54
+ multipart-post (2.0.0)
55
+ nokogiri (1.5.11)
56
+ oauth2 (1.4.1)
57
+ faraday (>= 0.8, < 0.16.0)
58
+ jwt (>= 1.0, < 3.0)
59
+ multi_json (~> 1.3)
60
+ multi_xml (~> 0.5)
61
+ rack (>= 1.2, < 3)
62
+ psych (3.1.0)
63
+ rack (1.6.11)
64
+ rake (10.5.0)
65
+ rdoc (5.1.0)
66
+ semver2 (3.4.2)
67
+ sqlite3 (1.4.0)
68
+ thor (0.20.3)
69
+ thread_safe (0.3.6)
70
+ tzinfo (1.2.5)
71
+ thread_safe (~> 0.1)
72
+ zeitwerk (1.4.3)
73
+
74
+ PLATFORMS
75
+ ruby
76
+
77
+ DEPENDENCIES
78
+ activerecord (~> 6.0.0.beta3)
79
+ appraisal
80
+ git (~> 1.2.9)
81
+ highline (~> 1.6.0)
82
+ jeweler
83
+ minitest
84
+ nokogiri (~> 1.5.0)
85
+ rack (~> 1.6)
86
+ rake (~> 10.4)
87
+ rdoc (>= 2.4.2, < 6)
88
+ sqlite3
89
+
90
+ BUNDLED WITH
91
+ 1.17.3
data/init.rb CHANGED
@@ -1 +1 @@
1
- require 'deep_cloneable'
1
+ require 'deep_cloneable'
@@ -1,215 +1,16 @@
1
1
  require 'active_record'
2
+ require 'active_support/lazy_load_hooks'
3
+ require 'active_support/core_ext/array/wrap'
2
4
 
3
- class ActiveRecord::Base
4
- module DeepCloneable
5
- # Deep dups an ActiveRecord model. See README.rdoc
6
- def deep_clone(*args, &block)
7
- options = args[0] || {}
5
+ require 'deep_cloneable/association_not_found_exception'
6
+ require 'deep_cloneable/skip_validations'
7
+ require 'deep_cloneable/deep_clone'
8
8
 
9
- dictionary = options[:dictionary]
10
- dictionary ||= {} if options.delete(:use_dictionary)
11
-
12
- kopy = if dictionary
13
- find_in_dictionary_or_dup(dictionary)
14
- else
15
- dup
16
- end
17
-
18
- yield(self, kopy) if block
19
-
20
- deep_exceptions = {}
21
- if options[:except]
22
- exceptions = array_wrap(options[:except])
23
- exceptions.each do |attribute|
24
- dup_default_attribute_value_to(kopy, attribute, self) unless attribute.is_a?(Hash)
25
- end
26
- deep_exceptions = exceptions.select { |e| e.is_a?(Hash) }.inject({}) { |m, h| m.merge(h) }
27
- end
28
-
29
- deep_onlinesses = {}
30
- if options[:only]
31
- onlinesses = array_wrap(options[:only])
32
- object_attrs = kopy.attributes.keys.collect(&:to_sym)
33
- exceptions = object_attrs - onlinesses
34
- exceptions.each do |attribute|
35
- dup_default_attribute_value_to(kopy, attribute, self) unless attribute.is_a?(Hash)
36
- end
37
- deep_onlinesses = onlinesses.select { |e| e.is_a?(Hash) }.inject({}) { |m, h| m.merge(h) }
38
- end
39
-
40
- if options[:include]
41
- normalized_includes_list(options[:include]).each do |association, conditions_or_deep_associations|
42
- conditions = {}
43
-
44
- if association.is_a? Hash
45
- conditions_or_deep_associations = association[association.keys.first]
46
- association = association.keys.first
47
- end
48
-
49
- if conditions_or_deep_associations.is_a?(Hash)
50
- conditions_or_deep_associations = conditions_or_deep_associations.dup
51
- conditions[:if] = conditions_or_deep_associations.delete(:if) if conditions_or_deep_associations[:if]
52
- conditions[:unless] = conditions_or_deep_associations.delete(:unless) if conditions_or_deep_associations[:unless]
53
- elsif conditions_or_deep_associations.is_a?(Array)
54
- conditions_or_deep_associations = conditions_or_deep_associations.dup
55
- conditions_or_deep_associations.delete_if { |entry| conditions.merge!(entry) if entry.is_a?(Hash) && (entry.key?(:if) || entry.key?(:unless)) }
56
- end
57
-
58
- dup_options = {}
59
- dup_options[:include] = conditions_or_deep_associations if conditions_or_deep_associations.present?
60
- dup_options[:except] = deep_exceptions[association] if deep_exceptions[association]
61
- dup_options[:only] = deep_onlinesses[association] if deep_onlinesses[association]
62
- dup_options[:dictionary] = dictionary if dictionary
63
- dup_options[:skip_missing_associations] = options[:skip_missing_associations] if options[:skip_missing_associations]
64
-
65
- if (association_reflection = self.class.reflect_on_association(association))
66
- if options[:validate] == false
67
- kopy.instance_eval do
68
- # Force :validate => false on all saves.
69
- def perform_validations(options = {})
70
- options[:validate] = false
71
- super(options)
72
- end
73
- end
74
- end
75
-
76
- association_type = association_reflection.macro
77
- association_type = "#{association_type}_through" if association_reflection.is_a?(ActiveRecord::Reflection::ThroughReflection)
78
-
79
- duped_object = send(
80
- "dup_#{association_type}_association",
81
- { :reflection => association_reflection, :association => association, :copy => kopy, :conditions => conditions, :dup_options => dup_options },
82
- &block
83
- )
84
-
85
- kopy.send("#{association}=", duped_object)
86
- elsif !options[:skip_missing_associations]
87
- raise AssociationNotFoundException, "#{self.class}##{association}"
88
- end
89
- end
90
- end
91
-
92
- kopy
93
- end
94
-
95
- protected
96
-
97
- def find_in_dictionary_or_dup(dictionary, dup_on_miss = true)
98
- tableized_class = self.class.name.tableize.to_sym
99
- dictionary[tableized_class] ||= {}
100
- dict_val = dictionary[tableized_class][self]
101
- dict_val.nil? && dup_on_miss ? dictionary[tableized_class][self] = dup : dict_val
102
- end
103
-
104
- private
105
-
106
- def dup_belongs_to_association(options, &block)
107
- object = deep_cloneable_object_for(options[:association], options[:conditions])
108
- object && object.deep_clone(options[:dup_options], &block)
109
- end
110
-
111
- def dup_has_one_association(options, &block)
112
- dup_belongs_to_association options, &block
113
- end
114
-
115
- def dup_has_many_association(options, &block)
116
- foreign_key = options[:reflection].foreign_key.to_s
117
- reverse_association = find_reverse_association(options[:reflection], foreign_key, :belongs_to)
118
- objects = deep_cloneable_objects_for(options[:association], options[:conditions])
119
-
120
- objects.map do |object|
121
- object = object.deep_clone(options[:dup_options], &block)
122
- object.send("#{foreign_key}=", nil)
123
- object.send("#{reverse_association.name}=", options[:copy]) if reverse_association
124
- object
125
- end
126
- end
127
-
128
- def dup_has_one_through_association(options, &block)
129
- foreign_key = options[:reflection].through_reflection.foreign_key.to_s
130
- reverse_association = find_reverse_association(options[:reflection], foreign_key, :has_one, :association_foreign_key)
131
-
132
- object = deep_cloneable_object_for(options[:association], options[:conditions])
133
- object && process_joined_object_for_deep_clone(object, options.merge(:reverse_association => reverse_association), &block)
134
- end
135
-
136
- def dup_has_many_through_association(options, &block)
137
- foreign_key = options[:reflection].through_reflection.foreign_key.to_s
138
- reverse_association = find_reverse_association(options[:reflection], foreign_key, :has_many, :association_foreign_key)
139
-
140
- objects = deep_cloneable_objects_for(options[:association], options[:conditions])
141
- objects.map { |object| process_joined_object_for_deep_clone(object, options.merge(:reverse_association => reverse_association), &block) }
142
- end
143
-
144
- def dup_has_and_belongs_to_many_association(options, &block)
145
- foreign_key = options[:reflection].foreign_key.to_s
146
- reverse_association = find_reverse_association(options[:reflection], foreign_key, :has_and_belongs_to_many, :association_foreign_key)
147
-
148
- objects = deep_cloneable_objects_for(options[:association], options[:conditions])
149
- objects.map { |object| process_joined_object_for_deep_clone(object, options.merge(:reverse_association => reverse_association), &block) }
150
- end
151
-
152
- def find_reverse_association(source_reflection, primary_key_name, macro, matcher = :foreign_key)
153
- if source_reflection.inverse_of.present?
154
- source_reflection.inverse_of
155
- else
156
- source_reflection.klass.reflect_on_all_associations.detect do |reflection|
157
- reflection != source_reflection && (macro.nil? || reflection.macro == macro) && (reflection.send(matcher).to_s == primary_key_name)
158
- end
159
- end
160
- end
161
-
162
- def deep_cloneable_object_for(single_association, conditions)
163
- object = send(single_association)
164
- evaluate_conditions(object, conditions) && object
165
- end
166
-
167
- def deep_cloneable_objects_for(many_association, conditions)
168
- send(many_association).select { |object| evaluate_conditions(object, conditions) }
169
- end
170
-
171
- def process_joined_object_for_deep_clone(object, options, &block)
172
- if (dictionary = options[:dup_options][:dictionary]) && object.find_in_dictionary_or_dup(dictionary, false)
173
- object = object.deep_clone(options[:dup_options], &block)
174
- elsif options[:reverse_association]
175
- object.send(options[:reverse_association].name).target << options[:copy]
176
- end
177
- object
178
- end
179
-
180
- def evaluate_conditions(object, conditions)
181
- conditions.none? || (conditions[:if] && conditions[:if].call(object)) || (conditions[:unless] && !conditions[:unless].call(object))
182
- end
183
-
184
- def dup_default_attribute_value_to(kopy, attribute, origin)
185
- kopy[attribute] = origin.class.column_defaults.dup[attribute.to_s]
186
- end
187
-
188
- def normalized_includes_list(includes)
189
- list = []
190
- Array(includes).each do |item|
191
- if item.is_a?(Hash) && item.size > 1
192
- item.each { |key, value| list << { key => value } }
193
- else
194
- list << item
195
- end
196
- end
197
-
198
- list
199
- end
200
-
201
- def array_wrap(object)
202
- if object.respond_to?(:to_ary)
203
- object.to_ary || [object]
204
- else
205
- [object]
206
- end
207
- end
208
-
209
- class AssociationNotFoundException < StandardError; end
9
+ module DeepCloneable
10
+ end
210
11
 
211
- ActiveRecord::Base.class_eval { protected :initialize_dup } if ActiveRecord::VERSION::MAJOR == 3 && ActiveRecord::VERSION::MINOR == 1
212
- end
12
+ ActiveSupport.on_load :active_record do
13
+ protected :initialize_dup if ActiveRecord::VERSION::MAJOR == 3 && ActiveRecord::VERSION::MINOR == 1
213
14
 
214
- include DeepCloneable
15
+ include DeepCloneable::DeepClone
215
16
  end
@@ -0,0 +1,4 @@
1
+ module DeepCloneable
2
+ class AssociationNotFoundException < StandardError
3
+ end
4
+ end
@@ -0,0 +1,194 @@
1
+ module DeepCloneable
2
+ module DeepClone
3
+ # Deep dups an ActiveRecord model. See README.rdoc
4
+ def deep_clone(*args, &block)
5
+ options = args[0] || {}
6
+
7
+ dictionary = options[:dictionary]
8
+ dictionary ||= {} if options.delete(:use_dictionary)
9
+
10
+ kopy = if dictionary
11
+ find_in_dictionary_or_dup(dictionary)
12
+ else
13
+ dup
14
+ end
15
+
16
+ deep_exceptions = {}
17
+ if options[:except]
18
+ exceptions = Array.wrap(options[:except])
19
+ exceptions.each do |attribute|
20
+ dup_default_attribute_value_to(kopy, attribute, self) unless attribute.is_a?(Hash)
21
+ end
22
+ deep_exceptions = exceptions.select { |e| e.is_a?(Hash) }.inject({}) { |m, h| m.merge(h) }
23
+ end
24
+
25
+ deep_onlinesses = {}
26
+ if options[:only]
27
+ onlinesses = Array.wrap(options[:only])
28
+ object_attrs = kopy.attributes.keys.collect(&:to_sym)
29
+ exceptions = object_attrs - onlinesses
30
+ exceptions.each do |attribute|
31
+ dup_default_attribute_value_to(kopy, attribute, self) unless attribute.is_a?(Hash)
32
+ end
33
+ deep_onlinesses = onlinesses.select { |e| e.is_a?(Hash) }.inject({}) { |m, h| m.merge(h) }
34
+ end
35
+
36
+ kopy.instance_eval { extend ::DeepCloneable::SkipValidations } if options[:validate] == false
37
+
38
+ if options[:include]
39
+ normalized_includes_list(options[:include]).each do |association, conditions_or_deep_associations|
40
+ conditions = {}
41
+
42
+ if association.is_a? Hash
43
+ conditions_or_deep_associations = association[association.keys.first]
44
+ association = association.keys.first
45
+ end
46
+
47
+ if conditions_or_deep_associations.is_a?(Hash)
48
+ conditions_or_deep_associations = conditions_or_deep_associations.dup
49
+ conditions[:if] = conditions_or_deep_associations.delete(:if) if conditions_or_deep_associations[:if]
50
+ 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)) }
54
+ end
55
+
56
+ dup_options = {}
57
+ dup_options[:include] = conditions_or_deep_associations if conditions_or_deep_associations.present?
58
+ dup_options[:except] = deep_exceptions[association] if deep_exceptions[association]
59
+ dup_options[:only] = deep_onlinesses[association] if deep_onlinesses[association]
60
+ dup_options[:dictionary] = dictionary if dictionary
61
+
62
+ [:skip_missing_associations, :validate].each do |option|
63
+ dup_options[option] = options[option] if options.key?(option)
64
+ end
65
+
66
+ if (association_reflection = self.class.reflect_on_association(association))
67
+ association_type = association_reflection.macro
68
+ association_type = "#{association_type}_through" if association_reflection.is_a?(ActiveRecord::Reflection::ThroughReflection)
69
+
70
+ duped_object = send(
71
+ "dup_#{association_type}_association",
72
+ { :reflection => association_reflection, :association => association, :copy => kopy, :conditions => conditions, :dup_options => dup_options },
73
+ &block
74
+ )
75
+
76
+ kopy.send("#{association}=", duped_object)
77
+ elsif !options[:skip_missing_associations]
78
+ raise ::DeepCloneable::AssociationNotFoundException, "#{self.class}##{association}"
79
+ end
80
+ end
81
+ end
82
+
83
+ yield(self, kopy) if block
84
+
85
+ kopy
86
+ end
87
+
88
+ protected
89
+
90
+ def find_in_dictionary_or_dup(dictionary, dup_on_miss = true)
91
+ tableized_class = self.class.name.tableize.to_sym
92
+ dictionary[tableized_class] ||= {}
93
+ dict_val = dictionary[tableized_class][self]
94
+ dict_val.nil? && dup_on_miss ? dictionary[tableized_class][self] = dup : dict_val
95
+ end
96
+
97
+ private
98
+
99
+ def dup_belongs_to_association(options, &block)
100
+ object = deep_cloneable_object_for(options[:association], options[:conditions])
101
+ object && object.deep_clone(options[:dup_options], &block)
102
+ end
103
+
104
+ def dup_has_one_association(options, &block)
105
+ dup_belongs_to_association options, &block
106
+ end
107
+
108
+ def dup_has_many_association(options, &block)
109
+ foreign_key = options[:reflection].foreign_key.to_s
110
+ reverse_association = find_reverse_association(options[:reflection], foreign_key, :belongs_to)
111
+ objects = deep_cloneable_objects_for(options[:association], options[:conditions])
112
+
113
+ objects.map do |object|
114
+ object = object.deep_clone(options[:dup_options], &block)
115
+ object.send("#{foreign_key}=", nil)
116
+ object.send("#{reverse_association.name}=", options[:copy]) if reverse_association
117
+ object
118
+ end
119
+ end
120
+
121
+ def dup_has_one_through_association(options, &block)
122
+ foreign_key = options[:reflection].through_reflection.foreign_key.to_s
123
+ reverse_association = find_reverse_association(options[:reflection], foreign_key, :has_one, :association_foreign_key)
124
+
125
+ object = deep_cloneable_object_for(options[:association], options[:conditions])
126
+ object && process_joined_object_for_deep_clone(object, options.merge(:reverse_association => reverse_association), &block)
127
+ end
128
+
129
+ def dup_has_many_through_association(options, &block)
130
+ foreign_key = options[:reflection].through_reflection.foreign_key.to_s
131
+ reverse_association = find_reverse_association(options[:reflection], foreign_key, :has_many, :association_foreign_key)
132
+
133
+ objects = deep_cloneable_objects_for(options[:association], options[:conditions])
134
+ objects.map { |object| process_joined_object_for_deep_clone(object, options.merge(:reverse_association => reverse_association), &block) }
135
+ end
136
+
137
+ def dup_has_and_belongs_to_many_association(options, &block)
138
+ foreign_key = options[:reflection].foreign_key.to_s
139
+ reverse_association = find_reverse_association(options[:reflection], foreign_key, :has_and_belongs_to_many, :association_foreign_key)
140
+
141
+ objects = deep_cloneable_objects_for(options[:association], options[:conditions])
142
+ objects.map { |object| process_joined_object_for_deep_clone(object, options.merge(:reverse_association => reverse_association), &block) }
143
+ end
144
+
145
+ def find_reverse_association(source_reflection, primary_key_name, macro, matcher = :foreign_key)
146
+ if source_reflection.inverse_of.present?
147
+ source_reflection.inverse_of
148
+ else
149
+ source_reflection.klass.reflect_on_all_associations.detect do |reflection|
150
+ reflection != source_reflection && (macro.nil? || reflection.macro == macro) && (reflection.send(matcher).to_s == primary_key_name)
151
+ end
152
+ end
153
+ end
154
+
155
+ def deep_cloneable_object_for(single_association, conditions)
156
+ object = send(single_association)
157
+ evaluate_conditions(object, conditions) ? object : nil
158
+ end
159
+
160
+ def deep_cloneable_objects_for(many_association, conditions)
161
+ send(many_association).select { |object| evaluate_conditions(object, conditions) }
162
+ end
163
+
164
+ def process_joined_object_for_deep_clone(object, options, &block)
165
+ if (dictionary = options[:dup_options][:dictionary]) && object.find_in_dictionary_or_dup(dictionary, false)
166
+ object = object.deep_clone(options[:dup_options], &block)
167
+ elsif options[:reverse_association]
168
+ object.send(options[:reverse_association].name).target << options[:copy]
169
+ end
170
+ object
171
+ end
172
+
173
+ def evaluate_conditions(object, conditions)
174
+ conditions.none? || (conditions[:if] && conditions[:if].call(object)) || (conditions[:unless] && !conditions[:unless].call(object))
175
+ end
176
+
177
+ def dup_default_attribute_value_to(kopy, attribute, origin)
178
+ kopy[attribute] = origin.class.column_defaults.dup[attribute.to_s]
179
+ end
180
+
181
+ def normalized_includes_list(includes)
182
+ list = []
183
+ Array(includes).each do |item|
184
+ if item.is_a?(Hash) && item.size > 1
185
+ item.each { |key, value| list << { key => value } }
186
+ else
187
+ list << item
188
+ end
189
+ end
190
+
191
+ list
192
+ end
193
+ end
194
+ end