Skip to content

Commit edcb27b

Browse files
committed
support for reify of only belongs_to relationships
1 parent 16a07c2 commit edcb27b

2 files changed

Lines changed: 121 additions & 3 deletions

File tree

lib/paper_trail/reifier.rb

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ def reify(version, options)
1313
mark_for_destruction: false,
1414
has_one: false,
1515
has_many: false,
16+
belongs_to: false,
1617
unversioned_attributes: :nil
1718
)
1819

@@ -78,6 +79,10 @@ def reify(version, options)
7879
reify_has_ones version.transaction_id, model, options
7980
end
8081

82+
unless options[:belongs_to] == false
83+
reify_belongs_tos version.transaction_id, model, options
84+
end
85+
8186
unless options[:has_many] == false
8287
reify_has_manys version.transaction_id, model, options
8388
end
@@ -108,7 +113,7 @@ def prepare_array_for_has_many(array, options, versions)
108113
elsif version.event == "create"
109114
options[:mark_for_destruction] ? record.tap(&:mark_for_destruction) : nil
110115
else
111-
version.reify(options.merge(has_many: false, has_one: false))
116+
version.reify(options.merge(has_many: false, has_one: false, belongs_to: false))
112117
end
113118
end
114119

@@ -117,7 +122,7 @@ def prepare_array_for_has_many(array, options, versions)
117122
# associations.
118123
array.concat(
119124
versions.values.map { |v|
120-
v.reify(options.merge(has_many: false, has_one: false))
125+
v.reify(options.merge(has_many: false, has_one: false, belongs_to: false))
121126
}
122127
)
123128

@@ -150,7 +155,7 @@ def reify_has_ones(transaction_id, model, options = {})
150155
end
151156
end
152157
else
153-
child = version.reify(options.merge(has_many: false, has_one: false))
158+
child = version.reify(options.merge(has_many: false, has_one: false, belongs_to: false))
154159
model.appear_as_new_record do
155160
without_persisting(child) do
156161
model.send "#{assoc.name}=", child
@@ -160,6 +165,32 @@ def reify_has_ones(transaction_id, model, options = {})
160165
end
161166
end
162167

168+
def reify_belongs_tos(transaction_id, model, options = {})
169+
associations = model.class.reflect_on_all_associations(:belongs_to)
170+
171+
associations.each do |assoc|
172+
next unless assoc.klass.paper_trail_enabled_for_model?
173+
collection_key = model.send(assoc.association_foreign_key)
174+
175+
version = assoc.klass.paper_trail_version_class.
176+
where("item_type = ?", assoc.class_name).
177+
where("item_id = ?", collection_key).
178+
where("created_at >= ? OR transaction_id = ?", options[:version_at], transaction_id).
179+
order("id").limit(1).first
180+
181+
collection = if version.nil?
182+
assoc.klass.where(assoc.klass.primary_key => collection_key).first
183+
elsif version.event == "create"
184+
options[:mark_for_destruction] ? collection.mark_for_destruction : nil
185+
else
186+
version.reify(options.merge(has_many: false, has_one: false,
187+
belongs_to: false))
188+
end
189+
190+
model.send("#{assoc.name}=".to_sym, collection)
191+
end
192+
end
193+
163194
# Restore the `model`'s has_many associations as they were at version_at
164195
# timestamp We lookup the first child versions after version_at timestamp or
165196
# in same transaction.

test/unit/associations_test.rb

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -769,4 +769,91 @@ class AssociationsTest < ActiveSupport::TestCase
769769
end
770770
end
771771
end
772+
773+
context "belongs_to associations" do
774+
context "Wotsit and Widget" do
775+
setup { @widget = Widget.create(name: "widget_0") }
776+
777+
context "where the association is created between model versions" do
778+
setup do
779+
@wotsit = @widget.create_wotsit(name: "wotsit_0")
780+
Timecop.travel 1.second.since
781+
@wotsit.update_attributes name: "wotsit_1"
782+
end
783+
784+
context "when reified" do
785+
setup { @wotsit_0 = @wotsit.versions.last.reify(belongs_to: true) }
786+
787+
should "see the associated as it was at the time" do
788+
assert_equal "widget_0", @wotsit_0.widget.name
789+
end
790+
791+
should "not persist changes to the live association" do
792+
assert_equal @widget, @wotsit.reload.widget
793+
end
794+
end
795+
796+
context "and then the associated is updated between model versions" do
797+
setup do
798+
@widget.update_attributes name: "widget_1"
799+
@widget.update_attributes name: "widget_2"
800+
Timecop.travel 1.second.since
801+
@wotsit.update_attributes name: "wotsit_2"
802+
@widget.update_attributes name: "widget_3"
803+
end
804+
805+
context "when reified" do
806+
setup { @wotsit_1 = @wotsit.versions.last.reify(belongs_to: true) }
807+
808+
should "see the associated as it was at the time" do
809+
assert_equal "widget_2", @wotsit_1.widget.name
810+
end
811+
812+
should "not persist changes to the live association" do
813+
assert_equal "widget_3", @wotsit.reload.widget.name
814+
end
815+
end
816+
817+
context "when reified opting out of belongs_to reification" do
818+
setup { @wotsit_1 = @wotsit.versions.last.reify(belongs_to: false) }
819+
820+
should "see the associated as it is live" do
821+
assert_equal "widget_3", @wotsit_1.widget.name
822+
end
823+
end
824+
end
825+
826+
context "and then the associated is destroyed" do
827+
setup { @widget.destroy }
828+
829+
context "when reify" do
830+
setup { @wotsit_1 = @wotsit.versions.last.reify(belongs_to: true) }
831+
832+
should "see the associated as it was at the time" do
833+
assert_equal @widget, @wotsit_1.widget
834+
end
835+
836+
should "not persist changes to the live association" do
837+
assert_nil @wotsit.reload.widget
838+
end
839+
end
840+
841+
context "and then the model is updated" do
842+
setup do
843+
Timecop.travel 1.second.since
844+
@wotsit.update_attributes name: "wotsit_3"
845+
end
846+
847+
context "when reified" do
848+
setup { @wotsit_2 = @wotsit.versions.last.reify(belongs_to: true) }
849+
850+
should "see the associated as it was the time" do
851+
assert_nil @wotsit_2.widget
852+
end
853+
end
854+
end
855+
end
856+
end
857+
end
858+
end
772859
end

0 commit comments

Comments
 (0)