diff --git a/Gemfile b/Gemfile index ca083775..e75385de 100644 --- a/Gemfile +++ b/Gemfile @@ -24,6 +24,7 @@ gem 'mongoid-compatibility' group :development, :test do gem 'bundler' + gem 'byebug' gem 'pry' gem 'rake', '< 11.0' end diff --git a/lib/mongoid/history/attributes/update.rb b/lib/mongoid/history/attributes/update.rb index 1da10e98..bcec2863 100644 --- a/lib/mongoid/history/attributes/update.rb +++ b/lib/mongoid/history/attributes/update.rb @@ -2,33 +2,85 @@ module Mongoid module History module Attributes class Update < ::Mongoid::History::Attributes::Base + # @example + # + # { + # 'foo' => ['foo_before_changes', 'foo_after_changes'] + # 'nested_bar' => { + # 'baz' => ['nested_bar_baz_before_changes', 'nested_bar_baz_after_changes'] + # } + # } + # + # @return [Hash] Hash of changes + # ? can be either a pair or a hash for embedded documents def attributes - @attributes = {} + require 'byebug' + byebug + changes_from_parent.deep_merge(changes_from_children) + end + + private + + def changes_from_parent + parent_changes = {} changes.each do |k, v| - if trackable_class.tracked_embeds_one?(k) - insert_embeds_one_changes(k, v) - elsif trackable_class.tracked_embeds_many?(k) - insert_embeds_many_changes(k, v) - elsif trackable_class.tracked?(k, :update) - @attributes[k] = format_field(k, v) unless v.all?(&:blank?) + change_value = begin + if trackable_class.tracked_embeds_one?(k) + embeds_one_changes_from_parent(k, v) + elsif trackable_class.tracked_embeds_many?(k) + embeds_many_changes_from_parent(k, v) + elsif trackable_class.tracked?(k, :update) + { k => format_field(k, v) } unless v.all?(&:blank?) + end end + parent_changes.merge!(change_value) if change_value.present? end - @attributes + parent_changes end - private + def changes_from_children + embeds_one_changes_from_embedded_documents + end + + # @return [Hash] changes of embeds_ones from embedded documents + def embeds_one_changes_from_embedded_documents + embedded_doc_changes = {} + trackable_class.tracked_embeds_one.each do |rel| + rel_class = trackable_class.relation_class_of(rel) + paranoia_field = Mongoid::History.trackable_class_settings(rel_class)[:paranoia_field] + paranoia_field = rel_class.aliased_fields.key(paranoia_field) || paranoia_field + rel = aliased_fields.key(rel) || rel + obj = trackable.send(rel) + next if !obj || (obj.respond_to?(paranoia_field) && obj.public_send(paranoia_field).present?) + embedded_doc_field_changes = obj.changes.map do |k,v| + [{ k => v.first }, { k => v.last }] + end + embedded_doc_changes[rel] = embedded_doc_field_changes if embedded_doc_field_changes.any? + end + embedded_doc_changes + end - def insert_embeds_one_changes(relation, value) + # @param [String] relation + # @param [String] value + # + # @return [Hash>] + def embeds_one_changes_from_parent(relation, value) relation = trackable_class.database_field_name(relation) relation_class = trackable_class.relation_class_of(relation) paranoia_field = Mongoid::History.trackable_class_settings(relation_class)[:paranoia_field] original_value = value[0][paranoia_field].present? ? {} : format_embeds_one_relation(relation, value[0]) modified_value = value[1][paranoia_field].present? ? {} : format_embeds_one_relation(relation, value[1]) return if original_value == modified_value - @attributes[relation] = [original_value, modified_value] + [original_value, modified_value] + byebug + { relation => [original_value, modified_value] } end - def insert_embeds_many_changes(relation, value) + # @param [String] relation + # @param [String] value + # + # @return [Hash>] + def embeds_many_changes_from_parent(relation, value) relation = trackable_class.database_field_name(relation) relation_class = trackable_class.relation_class_of(relation) paranoia_field = Mongoid::History.trackable_class_settings(relation_class)[:paranoia_field] @@ -37,7 +89,7 @@ def insert_embeds_many_changes(relation, value) modified_value = value[1].reject { |rel| rel[paranoia_field].present? } .map { |v_attrs| format_embeds_many_relation(relation, v_attrs) } return if original_value == modified_value - @attributes[relation] = [original_value, modified_value] + { relation => [original_value, modified_value] } end end end diff --git a/spec/integration/nested_embedded_documents_tracked_in_parent_spec.rb b/spec/integration/nested_embedded_documents_tracked_in_parent_spec.rb new file mode 100644 index 00000000..51df63a4 --- /dev/null +++ b/spec/integration/nested_embedded_documents_tracked_in_parent_spec.rb @@ -0,0 +1,67 @@ +require 'spec_helper' + +describe Mongoid::History::Tracker do + describe 'Tracking of changes from embedded documents' do + before :each do + # Child model (will be embedded in Parent) + class Child + include Mongoid::Document + include Mongoid::History::Trackable + + store_in collection: :child + + field :name + embedded_in :parent, inverse_of: :child + end + + # Parent model (embeds one Child) + class Parent + include Mongoid::Document + include Mongoid::History::Trackable + + field :name, type: String + embeds_one :child + + store_in collection: :parent + + track_history( + on: %i[fields embedded_relations], + version_field: :version, + track_create: true, + track_update: true, + track_destroy: false, + modifier_field: nil + ) + end + end + + after :each do + Object.send(:remove_const, :Parent) + Object.send(:remove_const, :Child) + end + + it 'tracks history for nested embedded documents in parent' do + p = Parent.new(name: 'bowser') + p.child = Child.new(name: 'todd') + p.save! + expect(p.history_tracks.length).to eq(1) + change = p.history_tracks.last + aggregate_failures do + expect(change.modified['name']).to eq('bowser') + expect(change.modified['child']['name']).to eq('todd') + end + + p.update_attributes(name: 'brow') + expect(p.history_tracks.length).to eq(2) + + p.child.name = 'mario' + p.save! + expect(p.history_tracks.length).to eq(3) + require 'byebug' + aggregate_failures do + expect(p.history_tracks.last.original['child']['name']).to eq('todd') + expect(p.history_tracks.last.modified['child']['name']).to eq('mario') + end + end + end +end