deep_cloneable 2.4.0 → 3.0.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.
@@ -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