@@ -36,8 +36,11 @@ def upsert(*)
3636 end
3737 end
3838
39- def from_database ( *)
40- super . tap ( &:clear_changes_information )
39+ def from_database ( attributes_from_database )
40+ super . tap do |model |
41+ model . clear_changes_information
42+ model . assign_attributes_from_database ( DeepDupper . dup_attributes ( model . attributes , model . class ) )
43+ end
4144 end
4245 end
4346
@@ -108,7 +111,7 @@ def changed
108111 #
109112 # @return [ActiveSupport::HashWithIndifferentAccess]
110113 def changes
111- ActiveSupport ::HashWithIndifferentAccess [ changed . map { |name | [ name , attribute_change ( name ) ] } ]
114+ ActiveSupport ::HashWithIndifferentAccess [ changed_attributes . map { |name , old_value | [ name , [ old_value , read_attribute ( name ) ] ] } ]
112115 end
113116
114117 # Returns a hash of attributes that were changed before the model was saved.
@@ -132,26 +135,31 @@ def previous_changes
132135 #
133136 # @return [ActiveSupport::HashWithIndifferentAccess]
134137 def changed_attributes
135- @changed_attributes ||= ActiveSupport :: HashWithIndifferentAccess . new
138+ attributes_changed_by_setter . merge ( attributes_changed_in_place )
136139 end
137140
138141 # Clear all dirty data: current changes and previous changes.
139142 def clear_changes_information
140143 @previously_changed = ActiveSupport ::HashWithIndifferentAccess . new
141- @changed_attributes = ActiveSupport ::HashWithIndifferentAccess . new
144+ @attributes_changed_by_setter = ActiveSupport ::HashWithIndifferentAccess . new
145+ @attributes_from_database = HashWithIndifferentAccess . new ( DeepDupper . dup_attributes ( @attributes , self . class ) )
142146 end
143147
144148 # Clears dirty data and moves +changes+ to +previous_changes+.
145149 def changes_applied
146150 @previously_changed = changes
147- @changed_attributes = ActiveSupport ::HashWithIndifferentAccess . new
151+ @attributes_changed_by_setter = ActiveSupport ::HashWithIndifferentAccess . new
152+ @attributes_from_database = HashWithIndifferentAccess . new ( DeepDupper . dup_attributes ( @attributes , self . class ) )
148153 end
149154
150155 # Remove changes information for the provided attributes.
151156 #
152157 # @param attributes [Array[String]] - a list of attributes to clear changes for
153158 def clear_attribute_changes ( names )
154159 attributes_changed_by_setter . except! ( *names )
160+
161+ slice = HashWithIndifferentAccess . new ( @attributes ) . slice ( *names )
162+ attributes_from_database . merge! ( DeepDupper . dup_attributes ( slice , self . class ) )
155163 end
156164
157165 # Handle <tt>*_changed?</tt> for +method_missing+.
@@ -220,12 +228,16 @@ def attribute_previous_change(name)
220228 previous_changes [ name ] if attribute_previously_changed? ( name )
221229 end
222230
231+ # @private
232+ def assign_attributes_from_database ( attributes_from_database )
233+ @attributes_from_database = HashWithIndifferentAccess . new ( attributes_from_database )
234+ end
235+
223236 private
224237
225238 def changes_include? ( name )
226- attributes_changed_by_setter . include ?( name )
239+ attribute_changed_by_setter? ( name ) || attribute_changed_in_place ?( name )
227240 end
228- alias attribute_changed_by_setter? changes_include?
229241
230242 # Handle <tt>*_change</tt> for +method_missing+.
231243 def attribute_change ( name )
@@ -259,13 +271,76 @@ def previous_changes_include?(name)
259271 previous_changes . include? ( name )
260272 end
261273
262- # This is necessary because `changed_attributes` might be overridden in
263- # other implemntations (e.g. in `ActiveRecord`)
264- alias attributes_changed_by_setter changed_attributes
274+ def attributes_changed_by_setter
275+ @attributes_changed_by_setter ||= ActiveSupport ::HashWithIndifferentAccess . new
276+ end
277+
278+ def attribute_changed_by_setter? ( name )
279+ attributes_changed_by_setter . include? ( name )
280+ end
281+
282+ def attributes_from_database
283+ @attributes_from_database ||= ActiveSupport ::HashWithIndifferentAccess . new
284+ end
265285
266286 # Force an attribute to have a particular "before" value
267287 def set_attribute_was ( name , old_value )
268288 attributes_changed_by_setter [ name ] = old_value
269289 end
290+
291+ def attributes_changed_in_place
292+ attributes_from_database . select do |name , _ |
293+ attribute_changed_in_place? ( name )
294+ end
295+ end
296+
297+ def attribute_changed_in_place? ( name )
298+ return false if attribute_changed_by_setter? ( name )
299+
300+ value_from_database = attributes_from_database [ name ]
301+ return false if value_from_database . nil?
302+
303+ value = read_attribute ( name )
304+ value != value_from_database
305+ end
306+
307+ module DeepDupper
308+ def self . dup_attributes ( attributes , klass )
309+ attributes . map do |name , value |
310+ type_options = klass . attributes [ name . to_sym ]
311+ value_duplicate = dup_attribute ( value , type_options )
312+ [ name , value_duplicate ]
313+ end . to_h
314+ end
315+
316+ def self . dup_attribute ( value , type_options )
317+ type , of = type_options . values_at ( :type , :of )
318+
319+ case value
320+ when NilClass , TrueClass , FalseClass , Numeric , Symbol , IO
321+ # till Ruby 2.4 these immutable objects could not be duplicated
322+ # IO objects cannot be duplicated - is used for binary fields
323+ value
324+ when String
325+ value . dup
326+ when Array
327+ if of . is_a? Class # custom type
328+ value . map { |e | dup_attribute ( e , type : of ) }
329+ else
330+ value . deep_dup
331+ end
332+ when Set
333+ Set . new ( value . map { |e | dup_attribute ( e , type : of ) } )
334+ when Hash
335+ value . deep_dup
336+ else
337+ if type . is_a? Class # custom type
338+ Marshal . load ( Marshal . dump ( value ) ) # dup instance variables
339+ else
340+ value . dup # date, datetime
341+ end
342+ end
343+ end
344+ end
270345 end
271346end
0 commit comments