Merge pull request #730 from theRealNG/issue_503
Support for reifying belongs_to relationships
This commit is contained in:
commit
0fdee5c49c
|
@ -746,7 +746,8 @@ the association that are created in the same transaction.
|
||||||
|
|
||||||
To restore Has-One associations as they were at the time, pass option `:has_one
|
To restore Has-One associations as they were at the time, pass option `:has_one
|
||||||
=> true` to `reify`. To restore Has-Many and Has-Many-Through associations, use
|
=> true` to `reify`. To restore Has-Many and Has-Many-Through associations, use
|
||||||
option `:has_many => true`. For example:
|
option `:has_many => true`. To restore Belongs-To association, use
|
||||||
|
option `:belongs_to => true`. For example:
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
class Location < ActiveRecord::Base
|
class Location < ActiveRecord::Base
|
||||||
|
|
|
@ -13,6 +13,7 @@ module PaperTrail
|
||||||
mark_for_destruction: false,
|
mark_for_destruction: false,
|
||||||
has_one: false,
|
has_one: false,
|
||||||
has_many: false,
|
has_many: false,
|
||||||
|
belongs_to: false,
|
||||||
unversioned_attributes: :nil
|
unversioned_attributes: :nil
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -60,15 +61,6 @@ module PaperTrail
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def reify_associations(model, options, version)
|
|
||||||
if options[:has_one]
|
|
||||||
reify_has_ones version.transaction_id, model, options
|
|
||||||
end
|
|
||||||
if options[:has_many]
|
|
||||||
reify_has_manys version.transaction_id, model, options
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Set all the attributes in this version on the model.
|
# Set all the attributes in this version on the model.
|
||||||
def reify_attributes(model, version, attrs)
|
def reify_attributes(model, version, attrs)
|
||||||
enums = model.class.respond_to?(:defined_enums) ? model.class.defined_enums : {}
|
enums = model.class.respond_to?(:defined_enums) ? model.class.defined_enums : {}
|
||||||
|
@ -114,7 +106,7 @@ module PaperTrail
|
||||||
elsif version.event == "create"
|
elsif version.event == "create"
|
||||||
options[:mark_for_destruction] ? record.tap(&:mark_for_destruction) : nil
|
options[:mark_for_destruction] ? record.tap(&:mark_for_destruction) : nil
|
||||||
else
|
else
|
||||||
version.reify(options.merge(has_many: false, has_one: false))
|
version.reify(options.merge(has_many: false, has_one: false, belongs_to: false))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -123,7 +115,7 @@ module PaperTrail
|
||||||
# associations.
|
# associations.
|
||||||
array.concat(
|
array.concat(
|
||||||
versions.values.map { |v|
|
versions.values.map { |v|
|
||||||
v.reify(options.merge(has_many: false, has_one: false))
|
v.reify(options.merge(has_many: false, has_one: false, belongs_to: false))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -132,6 +124,14 @@ module PaperTrail
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def reify_associations(model, options, version)
|
||||||
|
reify_has_ones version.transaction_id, model, options if options[:has_one]
|
||||||
|
|
||||||
|
reify_belongs_tos version.transaction_id, model, options if options[:belongs_to]
|
||||||
|
|
||||||
|
reify_has_manys version.transaction_id, model, options if options[:has_many]
|
||||||
|
end
|
||||||
|
|
||||||
# Restore the `model`'s has_one associations as they were when this
|
# Restore the `model`'s has_one associations as they were when this
|
||||||
# version was superseded by the next (because that's what the user was
|
# version was superseded by the next (because that's what the user was
|
||||||
# looking at when they made the change).
|
# looking at when they made the change).
|
||||||
|
@ -156,7 +156,7 @@ module PaperTrail
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
child = version.reify(options.merge(has_many: false, has_one: false))
|
child = version.reify(options.merge(has_many: false, has_one: false, belongs_to: false))
|
||||||
model.appear_as_new_record do
|
model.appear_as_new_record do
|
||||||
without_persisting(child) do
|
without_persisting(child) do
|
||||||
model.send "#{assoc.name}=", child
|
model.send "#{assoc.name}=", child
|
||||||
|
@ -166,6 +166,30 @@ module PaperTrail
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def reify_belongs_tos(transaction_id, model, options = {})
|
||||||
|
associations = model.class.reflect_on_all_associations(:belongs_to)
|
||||||
|
|
||||||
|
associations.each do |assoc|
|
||||||
|
next unless assoc.klass.paper_trail_enabled_for_model?
|
||||||
|
collection_key = model.send(assoc.association_foreign_key)
|
||||||
|
|
||||||
|
version = assoc.klass.paper_trail_version_class.
|
||||||
|
where("item_type = ?", assoc.class_name).
|
||||||
|
where("item_id = ?", collection_key).
|
||||||
|
where("created_at >= ? OR transaction_id = ?", options[:version_at], transaction_id).
|
||||||
|
order("id").limit(1).first
|
||||||
|
|
||||||
|
collection = if version.nil?
|
||||||
|
assoc.klass.where(assoc.klass.primary_key => collection_key).first
|
||||||
|
else
|
||||||
|
version.reify(options.merge(has_many: false, has_one: false,
|
||||||
|
belongs_to: false))
|
||||||
|
end
|
||||||
|
|
||||||
|
model.send("#{assoc.name}=".to_sym, collection)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# Restore the `model`'s has_many associations as they were at version_at
|
# Restore the `model`'s has_many associations as they were at version_at
|
||||||
# timestamp We lookup the first child versions after version_at timestamp or
|
# timestamp We lookup the first child versions after version_at timestamp or
|
||||||
# in same transaction.
|
# in same transaction.
|
||||||
|
|
|
@ -769,4 +769,126 @@ class AssociationsTest < ActiveSupport::TestCase
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context "belongs_to associations" do
|
||||||
|
context "Wotsit and Widget" do
|
||||||
|
setup { @widget = Widget.create(name: "widget_0") }
|
||||||
|
|
||||||
|
context "where the association is created between model versions" do
|
||||||
|
setup do
|
||||||
|
@wotsit = Wotsit.create(name: "wotsit_0")
|
||||||
|
Timecop.travel 1.second.since
|
||||||
|
@wotsit.update_attributes widget_id: @widget.id, name: "wotsit_1"
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when reified" do
|
||||||
|
setup { @wotsit_0 = @wotsit.versions.last.reify(belongs_to: true) }
|
||||||
|
|
||||||
|
should "see the associated as it was at the time" do
|
||||||
|
assert_equal nil, @wotsit_0.widget
|
||||||
|
end
|
||||||
|
|
||||||
|
should "not persist changes to the live association" do
|
||||||
|
assert_equal @widget, @wotsit.reload.widget
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "and then the associated is updated between model versions" do
|
||||||
|
setup do
|
||||||
|
@widget.update_attributes name: "widget_1"
|
||||||
|
@widget.update_attributes name: "widget_2"
|
||||||
|
Timecop.travel 1.second.since
|
||||||
|
@wotsit.update_attributes name: "wotsit_2"
|
||||||
|
@widget.update_attributes name: "widget_3"
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when reified" do
|
||||||
|
setup { @wotsit_1 = @wotsit.versions.last.reify(belongs_to: true) }
|
||||||
|
|
||||||
|
should "see the associated as it was at the time" do
|
||||||
|
assert_equal "widget_2", @wotsit_1.widget.name
|
||||||
|
end
|
||||||
|
|
||||||
|
should "not persist changes to the live association" do
|
||||||
|
assert_equal "widget_3", @wotsit.reload.widget.name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when reified opting out of belongs_to reification" do
|
||||||
|
setup { @wotsit_1 = @wotsit.versions.last.reify(belongs_to: false) }
|
||||||
|
|
||||||
|
should "see the associated as it is live" do
|
||||||
|
assert_equal "widget_3", @wotsit_1.widget.name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "and then the associated is destroyed" do
|
||||||
|
setup do
|
||||||
|
@wotsit.update_attributes name: "wotsit_2"
|
||||||
|
@widget.destroy
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when reified" do
|
||||||
|
setup { @wotsit_2 = @wotsit.versions.last.reify(belongs_to: true) }
|
||||||
|
|
||||||
|
should "see the associated as it was at the time" do
|
||||||
|
assert_equal @widget, @wotsit_2.widget
|
||||||
|
end
|
||||||
|
|
||||||
|
should "not persist changes to the live association" do
|
||||||
|
assert_nil @wotsit.reload.widget
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "and then the model is updated" do
|
||||||
|
setup do
|
||||||
|
Timecop.travel 1.second.since
|
||||||
|
@wotsit.update_attributes name: "wotsit_3"
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when reified" do
|
||||||
|
setup { @wotsit_2 = @wotsit.versions.last.reify(belongs_to: true) }
|
||||||
|
|
||||||
|
should "see the associated as it was the time" do
|
||||||
|
assert_nil @wotsit_2.widget
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "where the association is changed between model versions" do
|
||||||
|
setup do
|
||||||
|
@wotsit = @widget.create_wotsit(name: "wotsit_0")
|
||||||
|
Timecop.travel 1.second.since
|
||||||
|
@new_widget = Widget.create(name: "new_widget")
|
||||||
|
@wotsit.update_attributes(widget_id: @new_widget.id, name: "wotsit_1")
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when reified" do
|
||||||
|
setup { @wotsit_0 = @wotsit.versions.last.reify(belongs_to: true) }
|
||||||
|
|
||||||
|
should "see the association as it was at the time" do
|
||||||
|
assert_equal "widget_0", @wotsit_0.widget.name
|
||||||
|
end
|
||||||
|
|
||||||
|
should "not persist changes to the live association" do
|
||||||
|
assert_equal @new_widget, @wotsit.reload.widget
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when reified with option mark_for_destruction" do
|
||||||
|
setup do
|
||||||
|
@wotsit_0 = @wotsit.versions.last.
|
||||||
|
reify(belongs_to: true, mark_for_destruction: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
should "should not mark the new associated for destruction" do
|
||||||
|
assert_equal false, @new_widget.marked_for_destruction?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue