lib/paper_trail/events/base.rb in paper_trail-10.2.0 vs lib/paper_trail/events/base.rb in paper_trail-10.2.1

- old
+ new

@@ -57,20 +57,35 @@ @record.attribute_changed?(attr_name.to_s) end end # @api private - def attributes_before_change(is_touch) - Hash[@record.attributes.map do |k, v| - if @record.class.column_names.include?(k) - [k, attribute_in_previous_version(k, is_touch)] - else - [k, v] + def nonskipped_attributes_before_change(is_touch) + cache_changed_attributes do + record_attributes = @record.attributes.except(*@record.paper_trail_options[:skip]) + + record_attributes.each_key do |k| + if @record.class.column_names.include?(k) + record_attributes[k] = attribute_in_previous_version(k, is_touch) + end end - end] + end end + # Rails 5.1 changed the API of `ActiveRecord::Dirty`. + # @api private + def cache_changed_attributes + if RAILS_GTE_5_1 + # Everything works fine as it is + yield + else + # Any particular call to `changed_attributes` produces the huge memory allocation. + # Lets use the generic AR workaround for that. + @record.send(:cache_changed_attributes) { yield } + end + end + # Rails 5.1 changed the API of `ActiveRecord::Dirty`. See # https://github.com/paper-trail-gem/paper_trail/pull/899 # # Event can be any of the three (create, update, destroy). # @@ -106,22 +121,26 @@ (changed_in_latest_version - ignore) - skip end # @api private def changed_in_latest_version - changes_in_latest_version.keys + # Memoized to reduce memory usage + @changed_in_latest_version ||= changes_in_latest_version.keys end # Rails 5.1 changed the API of `ActiveRecord::Dirty`. See # https://github.com/paper-trail-gem/paper_trail/pull/899 # # @api private def changes_in_latest_version - if @in_after_callback && RAILS_GTE_5_1 - @record.saved_changes - else - @record.changes + # Memoized to reduce memory usage + @changes_in_latest_version ||= begin + if @in_after_callback && RAILS_GTE_5_1 + @record.saved_changes + else + @record.changes + end end end # An attributed is "ignored" if it is listed in the `:ignore` option # and/or the `:skip` option. Returns true if an ignored attribute has @@ -198,29 +217,31 @@ } end # @api private def notably_changed - only = @record.paper_trail_options[:only].dup - # Remove Hash arguments and then evaluate whether the attributes (the - # keys of the hash) should also get pushed into the collection. - only.delete_if do |obj| - obj.is_a?(Hash) && - obj.each { |attr, condition| - only << attr if condition.respond_to?(:call) && condition.call(@record) - } + # Memoized to reduce memory usage + @notably_changed ||= begin + only = @record.paper_trail_options[:only].dup + # Remove Hash arguments and then evaluate whether the attributes (the + # keys of the hash) should also get pushed into the collection. + only.delete_if do |obj| + obj.is_a?(Hash) && + obj.each { |attr, condition| + only << attr if condition.respond_to?(:call) && condition.call(@record) + } + end + only.empty? ? changed_and_not_ignored : (changed_and_not_ignored & only) end - only.empty? ? changed_and_not_ignored : (changed_and_not_ignored & only) end # Returns hash of attributes (with appropriate attributes serialized), # omitting attributes to be skipped. # # @api private def object_attrs_for_paper_trail(is_touch) - attrs = attributes_before_change(is_touch). - except(*@record.paper_trail_options[:skip]) + attrs = nonskipped_attributes_before_change(is_touch) AttributeSerializers::ObjectAttribute.new(@record.class).serialize(attrs) attrs end # @api private @@ -235,13 +256,18 @@ # a postgres `json` column, then a hash can be used in the assignment, # otherwise the column is a `text` column, and we must perform the # serialization here, using `PaperTrail.serializer`. # # @api private + # @param changes HashWithIndifferentAccess def recordable_object_changes(changes) if PaperTrail.config.object_changes_adapter&.respond_to?(:diff) - changes = PaperTrail.config.object_changes_adapter.diff(changes) + # We'd like to avoid the `to_hash` here, because it increases memory + # usage, but that would be a breaking change because + # `object_changes_adapter` expects a plain `Hash`, not a + # `HashWithIndifferentAccess`. + changes = PaperTrail.config.object_changes_adapter.diff(changes.to_hash) end if @record.class.paper_trail.version_class.object_changes_col_is_json? changes else @@ -282,10 +308,13 @@ # @api private def serialize_object_changes(changes) AttributeSerializers::ObjectChangesAttribute. new(@record.class). serialize(changes) - changes.to_hash + + # We'd like to convert this `HashWithIndifferentAccess` to a plain + # `Hash`, but we don't, to save memory. + changes end end end end