mirror of
https://github.com/paper-trail-gem/paper_trail.git
synced 2022-11-09 11:33:19 -05:00
reify has_many through; add option mark_for_destruction; disable recursive reification of has_many; add tests
This commit is contained in:
parent
86046a6c2f
commit
0e6b1119ae
9 changed files with 446 additions and 55 deletions
|
@ -107,23 +107,27 @@ module PaperTrail
|
|||
|
||||
# Restore the item from this version.
|
||||
#
|
||||
# This will automatically restore all :has_one associations as they were "at the time",
|
||||
# if they are also being versioned by PaperTrail. NOTE: this isn't always guaranteed
|
||||
# to work so you can either change the lookback period (from the default 3 seconds) or
|
||||
# opt out.
|
||||
# Optionally this can also restore all :has_one and :has_many (including has_many :through) associations as
|
||||
# they were "at the time", if they are also being versioned by PaperTrail.
|
||||
#
|
||||
# Options:
|
||||
# :has_one set to `false` to opt out of has_one reification.
|
||||
# set to a float to change the lookback time (check whether your db supports
|
||||
# sub-second datetimes if you want them).
|
||||
# :dup `false` default behavior
|
||||
# `true` it always create a new object instance. It is useful for comparing two versions of the same object
|
||||
# :has_one set to `true` to also reify has_one associations. Default is `false`.
|
||||
# :has_many set to `true` to also reify has_many and has_many :through associations.
|
||||
# Default is `false`.
|
||||
# :version_at the time as at that to reify the has_one/has_many associations.
|
||||
# Default is the time this version is created
|
||||
# :mark_for_destruction set to `true` to mark the has_one/has_many associations that did not exist in the
|
||||
# reified version for destruction, instead of remove them. Default is `false`.
|
||||
# This option is handy for people who want to persist the reified version.
|
||||
# :dup `false` default behavior
|
||||
# `true` it always create a new object instance. It is useful for comparing two versions of the same object
|
||||
def reify(options = {})
|
||||
return nil if object.nil?
|
||||
|
||||
without_identity_map do
|
||||
options.reverse_merge!(
|
||||
:version_at => created_at,
|
||||
:mark_for_destruction => false,
|
||||
:has_one => false,
|
||||
:has_many => false
|
||||
)
|
||||
|
@ -267,13 +271,13 @@ module PaperTrail
|
|||
order("#{version_table_name}.id ASC").first
|
||||
if version
|
||||
if version.event == 'create'
|
||||
if child = version.item
|
||||
child.mark_for_destruction
|
||||
if options[:mark_for_destruction]
|
||||
model.send(assoc.name).mark_for_destruction if model.send(assoc.name, true)
|
||||
else
|
||||
model.send "#{assoc.name}=", nil
|
||||
end
|
||||
else
|
||||
child = version.reify options
|
||||
logger.info "Reify #{child}"
|
||||
child = version.reify(options.merge(has_many: false, has_one: false))
|
||||
model.appear_as_new_record do
|
||||
model.send "#{assoc.name}=", child
|
||||
end
|
||||
|
@ -285,36 +289,83 @@ module PaperTrail
|
|||
# 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 in same transaction.
|
||||
def reify_has_manys(model, options = {})
|
||||
assoc_has_many_through, assoc_has_many_directly =
|
||||
model.class.reflect_on_all_associations(:has_many).partition { |assoc| assoc.options[:through] }
|
||||
reify_has_many_directly(assoc_has_many_directly, model, options)
|
||||
reify_has_many_through(assoc_has_many_through, model, options)
|
||||
end
|
||||
|
||||
# Restore the `model`'s has_many associations not associated through another association
|
||||
def reify_has_many_directly(associations, model, options = {})
|
||||
version_table_name = model.class.paper_trail_version_class.table_name
|
||||
model.class.reflect_on_all_associations(:has_many).each do |assoc|
|
||||
associations.each do |assoc|
|
||||
next if assoc.name == model.class.versions_association_name
|
||||
version_id_subquery = PaperTrail::VersionAssociation.joins(model.class.version_association_name).
|
||||
select("MIN(version_id)").
|
||||
where("foreign_key_name = ?", assoc.foreign_key).
|
||||
where("foreign_key_id = ?", model.id).
|
||||
where("#{version_table_name}.item_type = ?", assoc.class_name).
|
||||
where("created_at >= ? OR transaction_id = ?", options[:version_at], transaction_id).
|
||||
group("item_id").to_sql
|
||||
versions = model.class.paper_trail_version_class.where("id IN (#{version_id_subquery})")
|
||||
select("MIN(version_id)").
|
||||
where("foreign_key_name = ?", assoc.foreign_key).
|
||||
where("foreign_key_id = ?", model.id).
|
||||
where("#{version_table_name}.item_type = ?", assoc.class_name).
|
||||
where("created_at >= ? OR transaction_id = ?", options[:version_at], transaction_id).
|
||||
group("item_id").to_sql
|
||||
versions = model.class.paper_trail_version_class.where("id IN (#{version_id_subquery})").inject({}) do |acc, v|
|
||||
acc.merge!(v.item_id => v)
|
||||
end
|
||||
|
||||
# Pass true to force the model to load
|
||||
collection = Array.new model.send(assoc.name, true)
|
||||
|
||||
# Iterate all the child records to replace them with the previous values
|
||||
versions.each do |version|
|
||||
collection << version.reify(options) if version.event == 'destroy'
|
||||
collection.map! do |c|
|
||||
if version.event == 'create'
|
||||
c.mark_for_destruction if version.item && version.item.id == c.id
|
||||
c
|
||||
else
|
||||
child = version.reify(options)
|
||||
c.id == child.id ? child : c
|
||||
end
|
||||
# Iterate each child to replace it with the previous value if there is a version after the timestamp
|
||||
collection.map! do |c|
|
||||
if (version = versions.delete(c.id)).nil?
|
||||
c
|
||||
elsif version.event == 'create'
|
||||
options[:mark_for_destruction] ? c.tap { |r| r.mark_for_destruction } : nil
|
||||
else
|
||||
version.reify(options.merge(has_many: false, has_one: false))
|
||||
end
|
||||
end
|
||||
|
||||
model.send(assoc.name).proxy_association.target = collection
|
||||
# Reify the rest of the versions and add them to the collection
|
||||
collection += versions.map { |version| version.reify(options.merge(has_many: false, has_one: false)) }
|
||||
|
||||
model.send(assoc.name).proxy_association.target = collection.compact
|
||||
end
|
||||
end
|
||||
|
||||
# Restore the `model`'s has_many associations through another association
|
||||
# This must be called after the direct has_manys have been reified (reify_has_many_directly)
|
||||
def reify_has_many_through(associations, model, options = {})
|
||||
associations.each do |assoc|
|
||||
through_collection = model.send(assoc.options[:through])
|
||||
collection_keys = through_collection.map { |through_model| through_model.send(assoc.foreign_key) }
|
||||
|
||||
version_id_subquery = assoc.klass.paper_trail_version_class.
|
||||
select("MIN(id)").
|
||||
where("item_type = ?", assoc.class_name).
|
||||
where("item_id IN (?)", collection_keys).
|
||||
where("created_at >= ? OR transaction_id = ?", options[:version_at], transaction_id).
|
||||
group("item_id").to_sql
|
||||
versions = assoc.klass.paper_trail_version_class.where("id IN (#{version_id_subquery})").inject({}) do |acc, v|
|
||||
acc.merge!(v.item_id => v)
|
||||
end
|
||||
|
||||
collection = Array.new assoc.klass.where(assoc.klass.primary_key => collection_keys)
|
||||
|
||||
# Iterate each child to replace it with the previous value if there is a version after the timestamp
|
||||
collection.map! do |c|
|
||||
if (version = versions.delete(c.id)).nil?
|
||||
c
|
||||
elsif version.event == 'create'
|
||||
options[:mark_for_destruction] ? c.tap { |r| r.mark_for_destruction } : nil
|
||||
else
|
||||
version.reify(options.merge(has_many: false, has_one: false))
|
||||
end
|
||||
end
|
||||
|
||||
# Reify the rest of the versions and add them to the collection
|
||||
collection += versions.map { |version| version.reify(options.merge(has_many: false, has_one: false)) }
|
||||
|
||||
model.send(assoc.name).proxy_association.target = collection.compact
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -33,6 +33,7 @@ Gem::Specification.new do |s|
|
|||
s.add_development_dependency 'generator_spec'
|
||||
s.add_development_dependency 'database_cleaner', '~> 1.2'
|
||||
s.add_development_dependency 'pry-byebug'
|
||||
s.add_development_dependency 'timecop'
|
||||
|
||||
# JRuby support for the test ENV
|
||||
unless defined?(JRUBY_VERSION)
|
||||
|
|
4
test/dummy/app/models/customer.rb
Normal file
4
test/dummy/app/models/customer.rb
Normal file
|
@ -0,0 +1,4 @@
|
|||
class Customer < ActiveRecord::Base
|
||||
has_many :orders, :dependent => :destroy
|
||||
has_paper_trail
|
||||
end
|
4
test/dummy/app/models/line_item.rb
Normal file
4
test/dummy/app/models/line_item.rb
Normal file
|
@ -0,0 +1,4 @@
|
|||
class LineItem < ActiveRecord::Base
|
||||
belongs_to :order, :dependent => :destroy
|
||||
has_paper_trail
|
||||
end
|
5
test/dummy/app/models/order.rb
Normal file
5
test/dummy/app/models/order.rb
Normal file
|
@ -0,0 +1,5 @@
|
|||
class Order < ActiveRecord::Base
|
||||
belongs_to :customer
|
||||
has_many :line_items
|
||||
has_paper_trail
|
||||
end
|
|
@ -132,6 +132,20 @@ class SetUpTestTables < ActiveRecord::Migration
|
|||
t.string :brand
|
||||
t.timestamps
|
||||
end
|
||||
|
||||
create_table :customers, :force => true do |t|
|
||||
t.string :name
|
||||
end
|
||||
|
||||
create_table :orders, :force => true do |t|
|
||||
t.integer :customer_id
|
||||
t.string :order_date
|
||||
end
|
||||
|
||||
create_table :line_items, :force => true do |t|
|
||||
t.integer :order_id
|
||||
t.string :product
|
||||
end
|
||||
end
|
||||
|
||||
def self.down
|
||||
|
|
|
@ -34,6 +34,10 @@ ActiveRecord::Schema.define(version: 20110208155312) do
|
|||
t.string "title"
|
||||
end
|
||||
|
||||
create_table "customers", force: true do |t|
|
||||
t.string "name"
|
||||
end
|
||||
|
||||
create_table "documents", force: true do |t|
|
||||
t.string "name"
|
||||
end
|
||||
|
@ -55,15 +59,25 @@ ActiveRecord::Schema.define(version: 20110208155312) do
|
|||
t.integer "version"
|
||||
end
|
||||
|
||||
create_table "line_items", force: true do |t|
|
||||
t.integer "order_id"
|
||||
t.string "product"
|
||||
end
|
||||
|
||||
create_table "orders", force: true do |t|
|
||||
t.integer "customer_id"
|
||||
t.string "order_date"
|
||||
end
|
||||
|
||||
create_table "people", force: true do |t|
|
||||
t.string "name"
|
||||
t.string "time_zone"
|
||||
end
|
||||
|
||||
create_table "post_versions", force: true do |t|
|
||||
t.string "item_type", null: false
|
||||
t.integer "item_id", null: false
|
||||
t.string "event", null: false
|
||||
t.string "item_type", null: false
|
||||
t.integer "item_id", null: false
|
||||
t.string "event", null: false
|
||||
t.string "whodunnit"
|
||||
t.text "object"
|
||||
t.integer "transaction_id"
|
||||
|
|
|
@ -14,6 +14,7 @@ require "rails/test_help"
|
|||
require 'shoulda'
|
||||
require 'ffaker'
|
||||
require 'database_cleaner'
|
||||
require 'timecop'
|
||||
|
||||
Rails.backtrace_cleaner.remove_silencers!
|
||||
|
||||
|
|
|
@ -1,13 +1,5 @@
|
|||
require 'test_helper'
|
||||
|
||||
# Updates `model`'s last version so it looks like the version was
|
||||
# created 2 seconds ago.
|
||||
def make_last_version_earlier(model)
|
||||
PaperTrail::Version.record_timestamps = false
|
||||
model.versions.last.update_attributes :created_at => 2.seconds.ago
|
||||
PaperTrail::Version.record_timestamps = true
|
||||
end
|
||||
|
||||
class HasPaperTrailModelTest < ActiveSupport::TestCase
|
||||
|
||||
context "A record with defined 'only' and 'ignore' attributes" do
|
||||
|
@ -1315,6 +1307,7 @@ class HasPaperTrailModelTransactionalTest < ActiveSupport::TestCase
|
|||
end
|
||||
|
||||
teardown do
|
||||
Timecop.return
|
||||
# This would have been done in test_helper.rb if using_mysql? is true
|
||||
DatabaseCleaner.clean unless using_mysql?
|
||||
end
|
||||
|
@ -1329,7 +1322,7 @@ class HasPaperTrailModelTransactionalTest < ActiveSupport::TestCase
|
|||
end
|
||||
|
||||
context 'when reified' do
|
||||
setup { @widget_0 = @widget.versions.last.reify(:has_one => 1) }
|
||||
setup { @widget_0 = @widget.versions.last.reify(:has_one => true) }
|
||||
|
||||
should 'see the associated as it was at the time' do
|
||||
assert_nil @widget_0.wotsit
|
||||
|
@ -1340,13 +1333,12 @@ class HasPaperTrailModelTransactionalTest < ActiveSupport::TestCase
|
|||
context 'where the association is created between model versions' do
|
||||
setup do
|
||||
@wotsit = @widget.create_wotsit :name => 'wotsit_0'
|
||||
make_last_version_earlier @wotsit
|
||||
|
||||
Timecop.travel 1.second.since
|
||||
@widget.update_attributes :name => 'widget_1'
|
||||
end
|
||||
|
||||
context 'when reified' do
|
||||
setup { @widget_0 = @widget.versions.last.reify(:has_one => 1) }
|
||||
setup { @widget_0 = @widget.versions.last.reify(:has_one => true) }
|
||||
|
||||
should 'see the associated as it was at the time' do
|
||||
assert_equal 'wotsit_0', @widget_0.wotsit.name
|
||||
|
@ -1356,16 +1348,14 @@ class HasPaperTrailModelTransactionalTest < ActiveSupport::TestCase
|
|||
context 'and then the associated is updated between model versions' do
|
||||
setup do
|
||||
@wotsit.update_attributes :name => 'wotsit_1'
|
||||
make_last_version_earlier @wotsit
|
||||
@wotsit.update_attributes :name => 'wotsit_2'
|
||||
make_last_version_earlier @wotsit
|
||||
|
||||
Timecop.travel 1.second.since
|
||||
@widget.update_attributes :name => 'widget_2'
|
||||
@wotsit.update_attributes :name => 'wotsit_3'
|
||||
end
|
||||
|
||||
context 'when reified' do
|
||||
setup { @widget_1 = @widget.versions.last.reify(:has_one => 1) }
|
||||
setup { @widget_1 = @widget.versions.last.reify(:has_one => true) }
|
||||
|
||||
should 'see the associated as it was at the time' do
|
||||
assert_equal 'wotsit_2', @widget_1.wotsit.name
|
||||
|
@ -1384,13 +1374,12 @@ class HasPaperTrailModelTransactionalTest < ActiveSupport::TestCase
|
|||
context 'and then the associated is destroyed between model versions' do
|
||||
setup do
|
||||
@wotsit.destroy
|
||||
make_last_version_earlier @wotsit
|
||||
|
||||
Timecop.travel 1.second.since
|
||||
@widget.update_attributes :name => 'widget_3'
|
||||
end
|
||||
|
||||
context 'when reified' do
|
||||
setup { @widget_2 = @widget.versions.last.reify(:has_one => 1) }
|
||||
setup { @widget_2 = @widget.versions.last.reify(:has_one => true) }
|
||||
|
||||
should 'see the associated as it was at the time' do
|
||||
assert_nil @widget_2.wotsit
|
||||
|
@ -1399,4 +1388,312 @@ class HasPaperTrailModelTransactionalTest < ActiveSupport::TestCase
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'A model with a has_many association' do
|
||||
setup { @customer = Customer.create :name => 'customer_0' }
|
||||
|
||||
context 'updated before the associated was created' do
|
||||
setup do
|
||||
@customer.update_attributes! :name => 'customer_1'
|
||||
@customer.orders.create! :order_date => Date.today
|
||||
end
|
||||
|
||||
context 'when reified' do
|
||||
setup { @customer_0 = @customer.versions.last.reify(:has_many => true) }
|
||||
|
||||
should 'see the associated as it was at the time' do
|
||||
assert_equal [], @customer_0.orders
|
||||
end
|
||||
end
|
||||
|
||||
context 'when reified with option mark_for_destruction' do
|
||||
setup { @customer_0 = @customer.versions.last.reify(:has_many => true, :mark_for_destruction => true) }
|
||||
|
||||
should 'mark the associated for destruction' do
|
||||
assert_equal [true], @customer_0.orders.map(&:marked_for_destruction?)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'where the association is created between model versions' do
|
||||
setup do
|
||||
@order = @customer.orders.create! :order_date => 'order_date_0'
|
||||
Timecop.travel 1.second.since
|
||||
@customer.update_attributes :name => 'customer_1'
|
||||
end
|
||||
|
||||
context 'when reified' do
|
||||
setup { @customer_0 = @customer.versions.last.reify(:has_many => true) }
|
||||
|
||||
should 'see the associated as it was at the time' do
|
||||
assert_equal ['order_date_0'], @customer_0.orders.map(&:order_date)
|
||||
end
|
||||
end
|
||||
|
||||
context 'and then a nested has_many association is created' do
|
||||
setup do
|
||||
@order.line_items.create! :product => 'product_0'
|
||||
end
|
||||
|
||||
context 'when reified' do
|
||||
setup { @customer_0 = @customer.versions.last.reify(:has_many => true) }
|
||||
|
||||
should 'see the live version of the nested association' do
|
||||
assert_equal ['product_0'], @customer_0.orders.first.line_items.map(&:product)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'and then the associated is updated between model versions' do
|
||||
setup do
|
||||
@order.update_attributes :order_date => 'order_date_1'
|
||||
@order.update_attributes :order_date => 'order_date_2'
|
||||
Timecop.travel 1.second.since
|
||||
@customer.update_attributes :name => 'customer_2'
|
||||
@order.update_attributes :order_date => 'order_date_3'
|
||||
end
|
||||
|
||||
context 'when reified' do
|
||||
setup { @customer_1 = @customer.versions.last.reify(:has_many => true) }
|
||||
|
||||
should 'see the associated as it was at the time' do
|
||||
assert_equal ['order_date_2'], @customer_1.orders.map(&:order_date)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when reified opting out of has_many reification' do
|
||||
setup { @customer_1 = @customer.versions.last.reify(:has_many => false) }
|
||||
|
||||
should 'see the associated as it is live' do
|
||||
assert_equal ['order_date_3'], @customer_1.orders.map(&:order_date)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'and then the associated is destroyed between model versions' do
|
||||
setup do
|
||||
@order.destroy
|
||||
Timecop.travel 1.second.since
|
||||
@customer.update_attributes :name => 'customer_2'
|
||||
end
|
||||
|
||||
context 'when reified' do
|
||||
setup { @customer_1 = @customer.versions.last.reify(:has_many => true) }
|
||||
|
||||
should 'see the associated as it was at the time' do
|
||||
assert_equal [], @customer_1.orders
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'and then another association is added' do
|
||||
setup do
|
||||
@customer.orders.create! :order_date => 'order_date_1'
|
||||
end
|
||||
|
||||
context 'when reified' do
|
||||
setup { @customer_0 = @customer.versions.last.reify(:has_many => true) }
|
||||
|
||||
should 'see the associated as it was at the time' do
|
||||
assert_equal ['order_date_0'], @customer_0.orders.map(&:order_date)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when reified with option mark_for_destruction' do
|
||||
setup { @customer_0 = @customer.versions.last.reify(:has_many => true, :mark_for_destruction => true) }
|
||||
|
||||
should 'mark the newly associated for destruction' do
|
||||
assert @customer_0.orders.detect { |o| o.order_date == 'order_date_1'}.marked_for_destruction?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'A model with a has_many through association' do
|
||||
setup { @book = Book.create :title => 'book_0' }
|
||||
|
||||
context 'updated before the associated was created' do
|
||||
setup do
|
||||
@book.update_attributes! :title => 'book_1'
|
||||
@book.authors.create! :name => 'author_0'
|
||||
end
|
||||
|
||||
context 'when reified' do
|
||||
setup { @book_0 = @book.versions.last.reify(:has_many => true) }
|
||||
|
||||
should 'see the associated as it was at the time' do
|
||||
assert_equal [], @book_0.authors
|
||||
end
|
||||
end
|
||||
|
||||
context 'when reified with option mark_for_destruction' do
|
||||
setup { @book_0 = @book.versions.last.reify(:has_many => true, :mark_for_destruction => true) }
|
||||
|
||||
should 'mark the associated for destruction' do
|
||||
assert_equal [true], @book_0.authors.map(&:marked_for_destruction?)
|
||||
end
|
||||
|
||||
should 'mark the associated-through for destruction' do
|
||||
assert_equal [true], @book_0.authorships.map(&:marked_for_destruction?)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'updated before it is associated with an existing one' do
|
||||
setup do
|
||||
person_existing = Person.create(:name => 'person_existing')
|
||||
Timecop.travel 1.second.since
|
||||
@book.update_attributes! :title => 'book_1'
|
||||
@book.authors << person_existing
|
||||
end
|
||||
|
||||
context 'when reified' do
|
||||
setup { @book_0 = @book.versions.last.reify(:has_many => true) }
|
||||
|
||||
should 'see the associated as it was at the time' do
|
||||
assert_equal [], @book_0.authors
|
||||
end
|
||||
end
|
||||
|
||||
context 'when reified with option mark_for_destruction' do
|
||||
setup { @book_0 = @book.versions.last.reify(:has_many => true, :mark_for_destruction => true) }
|
||||
|
||||
should 'not mark the associated for destruction' do
|
||||
assert_equal [false], @book_0.authors.map(&:marked_for_destruction?)
|
||||
end
|
||||
|
||||
should 'mark the associated-through for destruction' do
|
||||
assert_equal [true], @book_0.authorships.map(&:marked_for_destruction?)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'where the association is created between model versions' do
|
||||
setup do
|
||||
@author = @book.authors.create! :name => 'author_0'
|
||||
@person_existing = Person.create(:name => 'person_existing')
|
||||
Timecop.travel 1.second.since
|
||||
@book.update_attributes! :title => 'book_1'
|
||||
end
|
||||
|
||||
context 'when reified' do
|
||||
setup { @book_0 = @book.versions.last.reify(:has_many => true) }
|
||||
|
||||
should 'see the associated as it was at the time' do
|
||||
assert_equal ['author_0'], @book_0.authors.map(&:name)
|
||||
end
|
||||
end
|
||||
|
||||
context 'and then the associated is updated between model versions' do
|
||||
setup do
|
||||
@author.update_attributes :name => 'author_1'
|
||||
@author.update_attributes :name => 'author_2'
|
||||
Timecop.travel 1.second.since
|
||||
@book.update_attributes :title => 'book_2'
|
||||
@author.update_attributes :name => 'author_3'
|
||||
end
|
||||
|
||||
context 'when reified' do
|
||||
setup { @book_1 = @book.versions.last.reify(:has_many => true) }
|
||||
|
||||
should 'see the associated as it was at the time' do
|
||||
assert_equal ['author_2'], @book_1.authors.map(&:name)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when reified opting out of has_many reification' do
|
||||
setup { @book_1 = @book.versions.last.reify(:has_many => false) }
|
||||
|
||||
should 'see the associated as it is live' do
|
||||
assert_equal ['author_3'], @book_1.authors.map(&:name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'and then the associated is destroyed between model versions' do
|
||||
setup do
|
||||
@author.destroy
|
||||
Timecop.travel 1.second.since
|
||||
@book.update_attributes :title => 'book_2'
|
||||
end
|
||||
|
||||
context 'when reified' do
|
||||
setup { @book_1 = @book.versions.last.reify(:has_many => true) }
|
||||
|
||||
should 'see the associated as it was at the time' do
|
||||
assert_equal [], @book_1.authors
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'and then the associated is dissociated between model versions' do
|
||||
setup do
|
||||
@book.authors = []
|
||||
Timecop.travel 1.second.since
|
||||
@book.update_attributes :title => 'book_2'
|
||||
end
|
||||
|
||||
context 'when reified' do
|
||||
setup { @book_1 = @book.versions.last.reify(:has_many => true) }
|
||||
|
||||
should 'see the associated as it was at the time' do
|
||||
assert_equal [], @book_1.authors
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'and then another associated is created' do
|
||||
setup do
|
||||
@book.authors.create! :name => 'author_1'
|
||||
end
|
||||
|
||||
context 'when reified' do
|
||||
setup { @book_0 = @book.versions.last.reify(:has_many => true) }
|
||||
|
||||
should 'only see the first associated' do
|
||||
assert_equal ['author_0'], @book_0.authors.map(&:name)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when reified with option mark_for_destruction' do
|
||||
setup { @book_0 = @book.versions.last.reify(:has_many => true, :mark_for_destruction => true) }
|
||||
|
||||
should 'mark the newly associated for destruction' do
|
||||
assert @book_0.authors.detect { |a| a.name == 'author_1' }.marked_for_destruction?
|
||||
end
|
||||
|
||||
should 'mark the newly associated-through for destruction' do
|
||||
assert @book_0.authorships.detect { |as| as.person.name == 'author_1' }.marked_for_destruction?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'and then an existing one is associated' do
|
||||
setup do
|
||||
@book.authors << @person_existing
|
||||
end
|
||||
|
||||
context 'when reified' do
|
||||
setup { @book_0 = @book.versions.last.reify(:has_many => true) }
|
||||
|
||||
should 'only see the first associated' do
|
||||
assert_equal ['author_0'], @book_0.authors.map(&:name)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when reified with option mark_for_destruction' do
|
||||
setup { @book_0 = @book.versions.last.reify(:has_many => true, :mark_for_destruction => true) }
|
||||
|
||||
should 'not mark the newly associated for destruction' do
|
||||
assert !@book_0.authors.detect { |a| a.name == 'person_existing' }.marked_for_destruction?
|
||||
end
|
||||
|
||||
should 'mark the newly associated-through for destruction' do
|
||||
assert @book_0.authorships.detect { |as| as.person.name == 'person_existing' }.marked_for_destruction?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue