Skip to content

Commit a36ec04

Browse files
committed
Working on a better fix than mongoid#191
1 parent 8ed7ba1 commit a36ec04

3 files changed

Lines changed: 133 additions & 13 deletions

File tree

Gemfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ gem 'mongoid-compatibility'
2424

2525
group :development, :test do
2626
gem 'bundler'
27+
gem 'byebug'
2728
gem 'pry'
2829
gem 'rake', '< 11.0'
2930
end

lib/mongoid/history/attributes/update.rb

Lines changed: 65 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,33 +2,85 @@ module Mongoid
22
module History
33
module Attributes
44
class Update < ::Mongoid::History::Attributes::Base
5+
# @example
6+
#
7+
# {
8+
# 'foo' => ['foo_before_changes', 'foo_after_changes']
9+
# 'nested_bar' => {
10+
# 'baz' => ['nested_bar_baz_before_changes', 'nested_bar_baz_after_changes']
11+
# }
12+
# }
13+
#
14+
# @return [Hash<String, ?>] Hash of changes
15+
# ? can be either a pair or a hash for embedded documents
516
def attributes
6-
@attributes = {}
17+
require 'byebug'
18+
byebug
19+
changes_from_parent.deep_merge(changes_from_children)
20+
end
21+
22+
private
23+
24+
def changes_from_parent
25+
parent_changes = {}
726
changes.each do |k, v|
8-
if trackable_class.tracked_embeds_one?(k)
9-
insert_embeds_one_changes(k, v)
10-
elsif trackable_class.tracked_embeds_many?(k)
11-
insert_embeds_many_changes(k, v)
12-
elsif trackable_class.tracked?(k, :update)
13-
@attributes[k] = format_field(k, v) unless v.all?(&:blank?)
27+
change_value = begin
28+
if trackable_class.tracked_embeds_one?(k)
29+
embeds_one_changes_from_parent(k, v)
30+
elsif trackable_class.tracked_embeds_many?(k)
31+
embeds_many_changes_from_parent(k, v)
32+
elsif trackable_class.tracked?(k, :update)
33+
{ k => format_field(k, v) } unless v.all?(&:blank?)
34+
end
1435
end
36+
parent_changes.merge!(change_value) if change_value.present?
1537
end
16-
@attributes
38+
parent_changes
1739
end
1840

19-
private
41+
def changes_from_children
42+
embeds_one_changes_from_embedded_documents
43+
end
44+
45+
# @return [Hash<String, Array<?,?>] changes of embeds_ones from embedded documents
46+
def embeds_one_changes_from_embedded_documents
47+
embedded_doc_changes = {}
48+
trackable_class.tracked_embeds_one.each do |rel|
49+
rel_class = trackable_class.relation_class_of(rel)
50+
paranoia_field = Mongoid::History.trackable_class_settings(rel_class)[:paranoia_field]
51+
paranoia_field = rel_class.aliased_fields.key(paranoia_field) || paranoia_field
52+
rel = aliased_fields.key(rel) || rel
53+
obj = trackable.send(rel)
54+
next if !obj || (obj.respond_to?(paranoia_field) && obj.public_send(paranoia_field).present?)
55+
embedded_doc_field_changes = obj.changes.map do |k,v|
56+
[{ k => v.first }, { k => v.last }]
57+
end
58+
embedded_doc_changes[rel] = embedded_doc_field_changes if embedded_doc_field_changes.any?
59+
end
60+
embedded_doc_changes
61+
end
2062

21-
def insert_embeds_one_changes(relation, value)
63+
# @param [String] relation <description>
64+
# @param [String] value <description>
65+
#
66+
# @return [Hash<String, Array<(?,?)>>]
67+
def embeds_one_changes_from_parent(relation, value)
2268
relation = trackable_class.database_field_name(relation)
2369
relation_class = trackable_class.relation_class_of(relation)
2470
paranoia_field = Mongoid::History.trackable_class_settings(relation_class)[:paranoia_field]
2571
original_value = value[0][paranoia_field].present? ? {} : format_embeds_one_relation(relation, value[0])
2672
modified_value = value[1][paranoia_field].present? ? {} : format_embeds_one_relation(relation, value[1])
2773
return if original_value == modified_value
28-
@attributes[relation] = [original_value, modified_value]
74+
[original_value, modified_value]
75+
byebug
76+
{ relation => [original_value, modified_value] }
2977
end
3078

31-
def insert_embeds_many_changes(relation, value)
79+
# @param [String] relation <description>
80+
# @param [String] value <description>
81+
#
82+
# @return [Hash<Array<(?,?)>>]
83+
def embeds_many_changes_from_parent(relation, value)
3284
relation = trackable_class.database_field_name(relation)
3385
relation_class = trackable_class.relation_class_of(relation)
3486
paranoia_field = Mongoid::History.trackable_class_settings(relation_class)[:paranoia_field]
@@ -37,7 +89,7 @@ def insert_embeds_many_changes(relation, value)
3789
modified_value = value[1].reject { |rel| rel[paranoia_field].present? }
3890
.map { |v_attrs| format_embeds_many_relation(relation, v_attrs) }
3991
return if original_value == modified_value
40-
@attributes[relation] = [original_value, modified_value]
92+
{ relation => [original_value, modified_value] }
4193
end
4294
end
4395
end
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
require 'spec_helper'
2+
3+
describe Mongoid::History::Tracker do
4+
describe 'Tracking of changes from embedded documents' do
5+
before :each do
6+
# Child model (will be embedded in Parent)
7+
class Child
8+
include Mongoid::Document
9+
include Mongoid::History::Trackable
10+
11+
store_in collection: :child
12+
13+
field :name
14+
embedded_in :parent, inverse_of: :child
15+
end
16+
17+
# Parent model (embeds one Child)
18+
class Parent
19+
include Mongoid::Document
20+
include Mongoid::History::Trackable
21+
22+
field :name, type: String
23+
embeds_one :child
24+
25+
store_in collection: :parent
26+
27+
track_history(
28+
on: %i[fields embedded_relations],
29+
version_field: :version,
30+
track_create: true,
31+
track_update: true,
32+
track_destroy: false,
33+
modifier_field: nil
34+
)
35+
end
36+
end
37+
38+
after :each do
39+
Object.send(:remove_const, :Parent)
40+
Object.send(:remove_const, :Child)
41+
end
42+
43+
it 'tracks history for nested embedded documents in parent' do
44+
p = Parent.new(name: 'bowser')
45+
p.child = Child.new(name: 'todd')
46+
p.save!
47+
expect(p.history_tracks.length).to eq(1)
48+
change = p.history_tracks.last
49+
aggregate_failures do
50+
expect(change.modified['name']).to eq('bowser')
51+
expect(change.modified['child']['name']).to eq('todd')
52+
end
53+
54+
p.update_attributes(name: 'brow')
55+
expect(p.history_tracks.length).to eq(2)
56+
57+
p.child.name = 'mario'
58+
p.save!
59+
expect(p.history_tracks.length).to eq(3)
60+
require 'byebug'
61+
aggregate_failures do
62+
expect(p.history_tracks.last.original['child']['name']).to eq('todd')
63+
expect(p.history_tracks.last.modified['child']['name']).to eq('mario')
64+
end
65+
end
66+
end
67+
end

0 commit comments

Comments
 (0)