1
0
Fork 0
mirror of https://github.com/paper-trail-gem/paper_trail.git synced 2022-11-09 11:33:19 -05:00
paper-trail-gem--paper_trail/test/unit/model_test.rb
2016-03-05 17:07:32 -05:00

1476 lines
48 KiB
Ruby

require "test_helper"
require "time_travel_helper"
class HasPaperTrailModelTest < ActiveSupport::TestCase
context "A record with defined 'only' and 'ignore' attributes" do
setup { @article = Article.create }
should "creation should change the number of versions" do
assert_equal(1, PaperTrail::Version.count)
end
context "which updates an ignored column" do
should "not change the number of versions" do
@article.update_attributes title: "My first title"
assert_equal(1, PaperTrail::Version.count)
end
end
context "which updates an ignored column with truly Proc" do
should "not change the number of versions" do
@article.update_attributes abstract: "ignore abstract"
assert_equal(1, PaperTrail::Version.count)
end
end
context "which updates an ignored column with falsy Proc" do
should "change the number of versions" do
@article.update_attributes abstract: "do not ignore abstract!"
assert_equal(2, PaperTrail::Version.count)
end
end
context "which updates an ignored column, ignored with truly Proc and a selected column" do
setup do
@article.update_attributes(
title: "My first title",
content: "Some text here.",
abstract: "ignore abstract"
)
end
should "change the number of versions" do
assert_equal(2, PaperTrail::Version.count)
end
should "show the new version in the model's `versions` association" do
assert_equal(2, @article.versions.size)
end
should "have stored only non-ignored attributes" do
expected = { "content" => [nil, "Some text here."] }
assert_equal expected, @article.versions.last.changeset
end
end
context "which updates an ignored column, ignored with falsy Proc and a selected column" do
setup do
@article.update_attributes(
title: "My first title",
content: "Some text here.",
abstract: "do not ignore abstract"
)
end
should "change the number of versions" do
assert_equal(2, PaperTrail::Version.count)
end
should "show the new version in the model's `versions` association" do
assert_equal(2, @article.versions.size)
end
should "have stored only non-ignored attributes" do
expected = {
"content" => [nil, "Some text here."],
"abstract" => [nil, "do not ignore abstract"]
}
assert_equal expected, @article.versions.last.changeset
end
end
context "which updates a selected column" do
setup { @article.update_attributes content: "Some text here." }
should "change the number of versions" do
assert_equal(2, PaperTrail::Version.count)
end
should "show the new version in the model's `versions` association" do
assert_equal(2, @article.versions.size)
end
end
context "which updates a non-ignored and non-selected column" do
should "not change the number of versions" do
@article.update_attributes abstract: "Other abstract"
assert_equal(1, PaperTrail::Version.count)
end
end
context "which updates a skipped column" do
should "not change the number of versions" do
@article.update_attributes file_upload: "Your data goes here"
assert_equal(1, PaperTrail::Version.count)
end
end
context "which updates a skipped column and a selected column" do
setup do
@article.update_attributes(
file_upload: "Your data goes here",
content: "Some text here."
)
end
should "change the number of versions" do
assert_equal(2, PaperTrail::Version.count)
end
should "show the new version in the model's `versions` association" do
assert_equal(2, @article.versions.size)
end
should "have stored only non-skipped attributes" do
assert_equal ({"content" => [nil, "Some text here."]}),
@article.versions.last.changeset
end
context "and when updated again" do
setup do
@article.update_attributes(
file_upload: "More data goes here",
content: "More text here."
)
@old_article = @article.versions.last
end
should "have removed the skipped attributes when saving the previous version" do
assert_equal nil, PaperTrail.serializer.load(@old_article.object)["file_upload"]
end
should "have kept the non-skipped attributes in the previous version" do
assert_equal "Some text here.", PaperTrail.serializer.load(@old_article.object)["content"]
end
end
end
context "which gets destroyed" do
setup { @article.destroy }
should "change the number of versions" do assert_equal(2, PaperTrail::Version.count) end
should "show the new version in the model's `versions` association" do
assert_equal(2, @article.versions.size)
end
end
end
context "A record with defined 'ignore' attribute" do
setup { @legacy_widget = LegacyWidget.create }
context "which updates an ignored column" do
setup { @legacy_widget.update_attributes version: 1 }
should "not change the number of versions" do assert_equal(1, PaperTrail::Version.count) end
end
end
context 'A record with defined "if" and "unless" attributes' do
setup { @translation = Translation.new headline: "Headline" }
context "for non-US translations" do
setup { @translation.save }
should "not change the number of versions" do assert_equal(0, PaperTrail::Version.count) end
context "after update" do
setup { @translation.update_attributes content: "Content" }
should "not change the number of versions" do assert_equal(0, PaperTrail::Version.count) end
end
context "after destroy" do
setup { @translation.destroy }
should "not change the number of versions" do assert_equal(0, PaperTrail::Version.count) end
end
end
context "for US translations" do
setup { @translation.language_code = "US" }
context "that are drafts" do
setup do
@translation.type = "DRAFT"
@translation.save
end
should "not change the number of versions" do
assert_equal(0, PaperTrail::Version.count)
end
context "after update" do
setup { @translation.update_attributes content: "Content" }
should "not change the number of versions" do
assert_equal(0, PaperTrail::Version.count)
end
end
end
context "that are not drafts" do
setup { @translation.save }
should "change the number of versions" do
assert_equal(1, PaperTrail::Version.count)
end
context "after update" do
setup { @translation.update_attributes content: "Content" }
should "change the number of versions" do
assert_equal(2, PaperTrail::Version.count)
end
should "show the new version in the model's `versions` association" do
assert_equal(2, @translation.versions.size)
end
end
context "after destroy" do
setup { @translation.destroy }
should "change the number of versions" do
assert_equal(2, PaperTrail::Version.count)
end
should "show the new version in the model's `versions` association" do
assert_equal(2, @translation.versions.size)
end
end
end
end
end
context "A new record" do
setup { @widget = Widget.new }
should "not have any previous versions" do
assert_equal [], @widget.versions
end
should "be live" do
assert @widget.live?
end
context "which is then created" do
setup { @widget.update_attributes name: "Henry", created_at: Time.now - 1.day }
should "have one previous version" do
assert_equal 1, @widget.versions.length
end
should "be nil in its previous version" do
assert_nil @widget.versions.first.object
assert_nil @widget.versions.first.reify
end
should "record the correct event" do
assert_match(/create/i, @widget.versions.first.event)
end
should "be live" do
assert @widget.live?
end
should "use the widget `updated_at` as the version's `created_at`" do
assert_equal @widget.updated_at.to_i, @widget.versions.first.created_at.to_i
end
should "have changes" do
# TODO: Postgres does not appear to pass back
# ActiveSupport::TimeWithZone, so choosing the lowest common denominator
# to test.
changes = {
"name" => [nil, "Henry"],
"created_at" => [nil, @widget.created_at.to_time.utc],
"updated_at" => [nil, @widget.updated_at.to_time.utc],
"id" => [nil, @widget.id]
}
assert_kind_of Time, @widget.versions.last.changeset["updated_at"][1]
assert_changes_equal changes, @widget.versions.last.changeset
end
context "and then updated without any changes" do
setup { @widget.touch }
should "not have a new version" do
assert_equal 1, @widget.versions.length
end
end
context "and then updated with changes" do
setup { @widget.update_attributes name: "Harry" }
should "have two previous versions" do
assert_equal 2, @widget.versions.length
end
should "be available in its previous version" do
assert_equal "Harry", @widget.name
assert_not_nil @widget.versions.last.object
widget = @widget.versions.last.reify
assert_equal "Henry", widget.name
assert_equal "Harry", @widget.name
end
should "have the same ID in its previous version" do
assert_equal @widget.id, @widget.versions.last.reify.id
end
should "record the correct event" do
assert_match(/update/i, @widget.versions.last.event)
end
should "have versions that are not live" do
assert @widget.versions.map(&:reify).compact.all? { |w| !w.live? }
end
should "have stored changes" do
# Behavior for ActiveRecord 4 is different than ActiveRecord 3;
# AR4 includes the `updated_at` column in changes for updates, which
# is why we reject it from the right side of this assertion.
last_obj_changes = @widget.versions.last.object_changes
actual = PaperTrail.serializer.load(last_obj_changes).reject { |k, _v|
k.to_sym == :updated_at
}
assert_equal ({"name" => %w(Henry Harry)}), actual
actual = @widget.versions.last.changeset.reject { |k, _v|
k.to_sym == :updated_at
}
assert_equal ({"name" => %w(Henry Harry)}), actual
end
should "return changes with indifferent access" do
assert_equal %w(Henry Harry), @widget.versions.last.changeset[:name]
assert_equal %w(Henry Harry), @widget.versions.last.changeset["name"]
end
context "and has one associated object" do
setup do
@wotsit = @widget.create_wotsit name: "John"
end
should "not copy the has_one association by default when reifying" do
reified_widget = @widget.versions.last.reify
# association hasn't been affected by reifying
assert_equal @wotsit, reified_widget.wotsit
# confirm that the association is correct
assert_equal @wotsit, @widget.reload.wotsit
end
should "copy the has_one association when reifying with :has_one => true" do
reified_widget = @widget.versions.last.reify(has_one: true)
# wotsit wasn't there at the last version
assert_nil reified_widget.wotsit
# wotsit should still exist on live object
assert_equal @wotsit, @widget.reload.wotsit
end
end
context "and has many associated objects" do
setup do
@f0 = @widget.fluxors.create name: "f-zero"
@f1 = @widget.fluxors.create name: "f-one"
@reified_widget = @widget.versions.last.reify
end
should "copy the has_many associations when reifying" do
assert_equal @widget.fluxors.length, @reified_widget.fluxors.length
assert_same_elements @widget.fluxors, @reified_widget.fluxors
assert_equal @widget.versions.length, @reified_widget.versions.length
assert_same_elements @widget.versions, @reified_widget.versions
end
end
context "and has many associated polymorphic objects" do
setup do
@f0 = @widget.whatchamajiggers.create name: "f-zero"
@f1 = @widget.whatchamajiggers.create name: "f-zero"
@reified_widget = @widget.versions.last.reify
end
should "copy the has_many associations when reifying" do
assert_equal @widget.whatchamajiggers.length, @reified_widget.whatchamajiggers.length
assert_same_elements @widget.whatchamajiggers, @reified_widget.whatchamajiggers
assert_equal @widget.versions.length, @reified_widget.versions.length
assert_same_elements @widget.versions, @reified_widget.versions
end
end
context "polymorphic objects by themselves" do
setup do
@widget = Whatchamajigger.new name: "f-zero"
end
should "not fail with a nil pointer on the polymorphic association" do
@widget.save!
end
end
context "and then destroyed" do
setup do
@fluxor = @widget.fluxors.create name: "flux"
@widget.destroy
@reified_widget = PaperTrail::Version.last.reify
end
should "record the correct event" do
assert_match(/destroy/i, PaperTrail::Version.last.event)
end
should "have three previous versions" do
assert_equal 3, PaperTrail::Version.with_item_keys("Widget", @widget.id).length
end
should "be available in its previous version" do
assert_equal @widget.id, @reified_widget.id
assert_attributes_equal @widget.attributes, @reified_widget.attributes
end
should "be re-creatable from its previous version" do
assert @reified_widget.save
end
should "restore its associations on its previous version" do
@reified_widget.save
assert_equal 1, @reified_widget.fluxors.length
end
should "not have changes" do
assert_equal({}, @widget.versions.last.changeset)
end
end
end
end
end
# Test the serialisation and deserialisation.
# TODO: binary
context "A record's papertrail" do
setup do
@date_time = DateTime.now.utc
@time = Time.now
@date = Date.new 2009, 5, 29
@widget = Widget.create(
name: "Warble",
a_text: "The quick brown fox",
an_integer: 42,
a_float: 153.01,
a_decimal: 2.71828,
a_datetime: @date_time,
a_time: @time,
a_date: @date,
a_boolean: true
)
@widget.update_attributes(
name: nil,
a_text: nil,
an_integer: nil,
a_float: nil,
a_decimal: nil,
a_datetime: nil,
a_time: nil,
a_date: nil,
a_boolean: false
)
@previous = @widget.versions.last.reify
end
should "handle strings" do
assert_equal "Warble", @previous.name
end
should "handle text" do
assert_equal "The quick brown fox", @previous.a_text
end
should "handle integers" do
assert_equal 42, @previous.an_integer
end
should "handle floats" do
assert_in_delta 153.01, @previous.a_float, 0.001
end
should "handle decimals" do
assert_in_delta 2.7183, @previous.a_decimal, 0.0001
end
should "handle datetimes" do
assert_equal @date_time.to_time.utc.to_i, @previous.a_datetime.to_time.utc.to_i
end
should "handle times" do
assert_equal @time.utc.to_i, @previous.a_time.utc.to_i
end
should "handle dates" do
assert_equal @date, @previous.a_date
end
should "handle booleans" do
assert @previous.a_boolean
end
context "after a column is removed from the record's schema" do
setup do
change_schema
Widget.connection.schema_cache.clear!
Widget.reset_column_information
assert_raise(NoMethodError) { Widget.new.sacrificial_column }
@last = @widget.versions.last
end
teardown do
restore_schema
end
should "reify previous version" do
assert_kind_of Widget, @last.reify
end
should "restore all forward-compatible attributes" do
assert_equal "Warble", @last.reify.name
assert_equal "The quick brown fox", @last.reify.a_text
assert_equal 42, @last.reify.an_integer
assert_in_delta 153.01, @last.reify.a_float, 0.001
assert_in_delta 2.7183, @last.reify.a_decimal, 0.0001
assert_equal @date_time.to_time.utc.to_i, @last.reify.a_datetime.to_time.utc.to_i
assert_equal @time.utc.to_i, @last.reify.a_time.utc.to_i
assert_equal @date, @last.reify.a_date
assert @last.reify.a_boolean
end
end
end
context "A record" do
setup { @widget = Widget.create name: "Zaphod" }
context "with PaperTrail globally disabled" do
setup do
PaperTrail.enabled = false
@count = @widget.versions.length
end
teardown { PaperTrail.enabled = true }
context "when updated" do
setup { @widget.update_attributes name: "Beeblebrox" }
should "not add to its trail" do
assert_equal @count, @widget.versions.length
end
end
end
context "with its paper trail turned off" do
setup do
Widget.paper_trail_off!
@count = @widget.versions.length
end
teardown { Widget.paper_trail_on! }
context "when updated" do
setup { @widget.update_attributes name: "Beeblebrox" }
should "not add to its trail" do
assert_equal @count, @widget.versions.length
end
end
context 'when destroyed "without versioning"' do
should "leave paper trail off after call" do
@widget.without_versioning :destroy
assert !Widget.paper_trail_enabled_for_model?
end
end
context "and then its paper trail turned on" do
setup { Widget.paper_trail_on! }
context "when updated" do
setup { @widget.update_attributes name: "Ford" }
should "add to its trail" do
assert_equal @count + 1, @widget.versions.length
end
end
context 'when updated "without versioning"' do
setup do
@widget.without_versioning do
@widget.update_attributes name: "Ford"
end
# The model instance should yield itself for convenience purposes
@widget.without_versioning { |w| w.update_attributes name: "Nixon" }
end
should "not create new version" do
assert_equal @count, @widget.versions.length
end
should "enable paper trail after call" do
assert Widget.paper_trail_enabled_for_model?
end
end
context "when receiving a method name as an argument" do
setup { @widget.without_versioning(:touch_with_version) }
should "not create new version" do
assert_equal @count, @widget.versions.length
end
should "enable paper trail after call" do
assert Widget.paper_trail_enabled_for_model?
end
end
end
end
end
context "A papertrail with somebody making changes" do
setup do
@widget = Widget.new name: "Fidget"
end
context "when a record is created" do
setup do
PaperTrail.whodunnit = "Alice"
@widget.save
@version = @widget.versions.last # only 1 version
end
should "track who made the change" do
assert_equal "Alice", @version.whodunnit
assert_nil @version.paper_trail_originator
assert_equal "Alice", @version.terminator
assert_equal "Alice", @widget.paper_trail_originator
end
context "when a record is updated" do
setup do
PaperTrail.whodunnit = "Bob"
@widget.update_attributes name: "Rivet"
@version = @widget.versions.last
end
should "track who made the change" do
assert_equal "Bob", @version.whodunnit
assert_equal "Alice", @version.paper_trail_originator
assert_equal "Bob", @version.terminator
assert_equal "Bob", @widget.paper_trail_originator
end
context "when a record is destroyed" do
setup do
PaperTrail.whodunnit = "Charlie"
@widget.destroy
@version = PaperTrail::Version.last
end
should "track who made the change" do
assert_equal "Charlie", @version.whodunnit
assert_equal "Bob", @version.paper_trail_originator
assert_equal "Charlie", @version.terminator
assert_equal "Charlie", @widget.paper_trail_originator
end
end
end
end
end
context "Timestamps" do
setup do
@wotsit = Wotsit.create! name: "wotsit"
end
should "record timestamps" do
@wotsit.update_attributes! name: "changed"
assert_not_nil @wotsit.versions.last.reify.created_at
assert_not_nil @wotsit.versions.last.reify.updated_at
end
# Tests that it doesn't try to write created_on as an attribute just because
# a created_on method exists.
#
# - Deprecation warning in Rails 3.2
# - ActiveModel::MissingAttributeError in Rails 4
#
# In rails 5, `capture` is deprecated in favor of `capture_io`.
#
should "not generate warning" do
assert_update_raises_nothing = -> {
assert_nothing_raised {
@wotsit.update_attributes! name: "changed"
}
}
warnings =
if respond_to?(:capture_io)
capture_io { assert_update_raises_nothing.call }.last
else
capture(:stderr) { assert_update_raises_nothing.call }
end
assert_equal "", warnings
end
end
context "A subclass" do
setup do
@foo = FooWidget.create
@foo.update_attributes! name: "Foo"
end
should "reify with the correct type" do
# For some reason this test appears to be broken on AR4 in the test env.
# Executing it manually in the Rails console seems to work.. not sure what
# the issues is here.
assert_kind_of FooWidget, @foo.versions.last.reify if ActiveRecord::VERSION::MAJOR < 4
assert_equal @foo.versions.first, PaperTrail::Version.last.previous
assert_nil PaperTrail::Version.last.next
end
should "should return the correct originator" do
PaperTrail.whodunnit = "Ben"
@foo.update_attribute(:name, "Geoffrey")
assert_equal PaperTrail.whodunnit, @foo.paper_trail_originator
end
context "when destroyed" do
setup { @foo.destroy }
should "reify with the correct type" do
assert_kind_of FooWidget, @foo.versions.last.reify
assert_equal @foo.versions[1], PaperTrail::Version.last.previous
assert_nil PaperTrail::Version.last.next
end
end
end
context "An item with versions" do
setup do
@widget = Widget.create name: "Widget"
@widget.update_attributes name: "Fidget"
@widget.update_attributes name: "Digit"
end
context "which were created over time" do
setup do
@created = 2.days.ago
@first_update = 1.day.ago
@second_update = 1.hour.ago
@widget.versions[0].update_attributes created_at: @created
@widget.versions[1].update_attributes created_at: @first_update
@widget.versions[2].update_attributes created_at: @second_update
@widget.update_attribute :updated_at, @second_update
end
should "return nil for version_at before it was created" do
assert_nil @widget.version_at(@created - 1)
end
should "return how it looked when created for version_at its creation" do
assert_equal "Widget", @widget.version_at(@created).name
end
should "return how it looked before its first update" do
assert_equal "Widget", @widget.version_at(@first_update - 1).name
end
should "return how it looked after its first update" do
assert_equal "Fidget", @widget.version_at(@first_update).name
end
should "return how it looked before its second update" do
assert_equal "Fidget", @widget.version_at(@second_update - 1).name
end
should "return how it looked after its second update" do
assert_equal "Digit", @widget.version_at(@second_update).name
end
should "return the current object for version_at after latest update" do
assert_equal "Digit", @widget.version_at(1.day.from_now).name
end
context "passing in a string representation of a timestamp" do
should "still return a widget when appropriate" do
# need to add 1 second onto the timestamps before casting to a string,
# since casting a Time to a string drops the microseconds
assert_equal "Widget", @widget.version_at((@created + 1.second).to_s).name
assert_equal "Fidget", @widget.version_at((@first_update + 1.second).to_s).name
assert_equal "Digit", @widget.version_at((@second_update + 1.second).to_s).name
end
end
end
context ".versions_between" do
setup do
@created = 30.days.ago
@first_update = 15.days.ago
@second_update = 1.day.ago
@widget.versions[0].update_attributes created_at: @created
@widget.versions[1].update_attributes created_at: @first_update
@widget.versions[2].update_attributes created_at: @second_update
@widget.update_attribute :updated_at, @second_update
end
should "return versions in the time period" do
assert_equal ["Fidget"],
@widget.versions_between(20.days.ago, 10.days.ago).map(&:name)
assert_equal %w(Widget Fidget),
@widget.versions_between(45.days.ago, 10.days.ago).map(&:name)
assert_equal %w(Fidget Digit Digit),
@widget.versions_between(16.days.ago, 1.minute.ago).map(&:name)
assert_equal [],
@widget.versions_between(60.days.ago, 45.days.ago).map(&:name)
end
end
context "on the first version" do
setup { @version = @widget.versions.first }
should "have a nil previous version" do
assert_nil @version.previous
end
should "return the next version" do
assert_equal @widget.versions[1], @version.next
end
should "return the correct index" do
assert_equal 0, @version.index
end
end
context "on the last version" do
setup { @version = @widget.versions.last }
should "return the previous version" do
assert_equal @widget.versions[@widget.versions.length - 2], @version.previous
end
should "have a nil next version" do
assert_nil @version.next
end
should "return the correct index" do
assert_equal @widget.versions.length - 1, @version.index
end
end
end
context "An item" do
setup do
@initial_title = "Foobar"
@article = Article.new title: @initial_title
end
context "which is created" do
setup { @article.save }
should "store fixed meta data" do
assert_equal 42, @article.versions.last.answer
end
should "store dynamic meta data which is independent of the item" do
assert_equal "31 + 11 = 42", @article.versions.last.question
end
should "store dynamic meta data which depends on the item" do
assert_equal @article.id, @article.versions.last.article_id
end
should "store dynamic meta data based on a method of the item" do
assert_equal @article.action_data_provider_method, @article.versions.last.action
end
should "store dynamic meta data based on an attribute of the item at creation" do
assert_equal @initial_title, @article.versions.last.title
end
context "and updated" do
setup do
@article.update_attributes! content: "Better text.", title: "Rhubarb"
end
should "store fixed meta data" do
assert_equal 42, @article.versions.last.answer
end
should "store dynamic meta data which is independent of the item" do
assert_equal "31 + 11 = 42", @article.versions.last.question
end
should "store dynamic meta data which depends on the item" do
assert_equal @article.id, @article.versions.last.article_id
end
should "store dynamic meta data based on an attribute of the item prior to the update" do
assert_equal @initial_title, @article.versions.last.title
end
end
context "and destroyed" do
setup { @article.destroy }
should "store fixed metadata" do
assert_equal 42, @article.versions.last.answer
end
should "store dynamic metadata which is independent of the item" do
assert_equal "31 + 11 = 42", @article.versions.last.question
end
should "store dynamic metadata which depends on the item" do
assert_equal @article.id, @article.versions.last.article_id
end
should "store dynamic metadata based on attribute of item prior to destruction" do
assert_equal @initial_title, @article.versions.last.title
end
end
end
end
context "A reified item" do
setup do
widget = Widget.create name: "Bob"
%w( Tom Dick Jane ).each { |name| widget.update_attributes name: name }
@version = widget.versions.last
@widget = @version.reify
end
should "know which version it came from" do
assert_equal @version, @widget.version
end
should "return its previous self" do
assert_equal @widget.versions[-2].reify, @widget.previous_version
end
end
context "A non-reified item" do
setup { @widget = Widget.new }
should "not have a previous version" do
assert_nil @widget.previous_version
end
should "not have a next version" do
assert_nil @widget.next_version
end
context "with versions" do
setup do
@widget.save
%w( Tom Dick Jane ).each { |name| @widget.update_attributes name: name }
end
should "have a previous version" do
assert_equal @widget.versions.last.reify.name, @widget.previous_version.name
end
should "not have a next version" do
assert_nil @widget.next_version
end
end
end
context "A reified item" do
setup do
@widget = Widget.create name: "Bob"
%w(Tom Dick Jane).each { |name| @widget.update_attributes name: name }
@second_widget = @widget.versions[1].reify # first widget is `nil`
@last_widget = @widget.versions.last.reify
end
should "have a previous version" do
assert_nil @second_widget.previous_version # `create` events return `nil` for `reify`
assert_equal @widget.versions[-2].reify.name, @last_widget.previous_version.name
end
should "have a next version" do
assert_equal @widget.versions[2].reify.name, @second_widget.next_version.name
assert_equal @last_widget.next_version.name, @widget.name
end
end
context ":has_many :through" do
setup do
@book = Book.create title: "War and Peace"
@dostoyevsky = Person.create name: "Dostoyevsky"
@solzhenitsyn = Person.create name: "Solzhenitsyn"
end
should "store version on source <<" do
count = PaperTrail::Version.count
@book.authors << @dostoyevsky
assert_equal 1, PaperTrail::Version.count - count
assert_equal PaperTrail::Version.last, @book.authorships.first.versions.first
end
should "store version on source create" do
count = PaperTrail::Version.count
@book.authors.create name: "Tolstoy"
assert_equal 2, PaperTrail::Version.count - count
actual = [
PaperTrail::Version.order(:id).to_a[-2].item,
PaperTrail::Version.last.item
]
assert_same_elements [Person.last, Authorship.last], actual
end
should "store version on join destroy" do
@book.authors << @dostoyevsky
count = PaperTrail::Version.count
@book.authorships.reload.last.destroy
assert_equal 1, PaperTrail::Version.count - count
assert_equal @book, PaperTrail::Version.last.reify.book
assert_equal @dostoyevsky, PaperTrail::Version.last.reify.person
end
should "store version on join clear" do
@book.authors << @dostoyevsky
count = PaperTrail::Version.count
@book.authorships.reload.destroy_all
assert_equal 1, PaperTrail::Version.count - count
assert_equal @book, PaperTrail::Version.last.reify.book
assert_equal @dostoyevsky, PaperTrail::Version.last.reify.person
end
end
context "When an attribute has a custom serializer" do
setup do
@person = Person.new(time_zone: "Samoa")
end
should "be an instance of ActiveSupport::TimeZone" do
assert_equal ActiveSupport::TimeZone, @person.time_zone.class
end
context "when the model is saved" do
setup do
@changes_before_save = @person.changes.dup
@person.save!
end
# Test for serialization:
should "version.object_changes should store long serialization of TimeZone object" do
len = @person.versions.last.object_changes.length
assert len < 105, "object_changes length was #{len}"
end
# It should store the serialized value.
should "version.object_changes attribute should have stored the value from serializer" do
as_stored_in_version = HashWithIndifferentAccess[
YAML.load(@person.versions.last.object_changes)
]
assert_equal [nil, "Samoa"], as_stored_in_version[:time_zone]
serialized_value = Person::TimeZoneSerializer.dump(@person.time_zone)
assert_equal serialized_value, as_stored_in_version[:time_zone].last
end
# Tests for unserialization:
should "version.changeset should convert attribute to original, unserialized value" do
unserialized_value = Person::TimeZoneSerializer.load(@person.time_zone)
assert_equal unserialized_value,
@person.versions.last.changeset[:time_zone].last
end
should "record.changes (before save) returns the original, unserialized values" do
assert_equal [NilClass, ActiveSupport::TimeZone],
@changes_before_save[:time_zone].map(&:class)
end
should "version.changeset should be the same as record.changes was before the save" do
actual = @person.versions.last.changeset.delete_if { |k, _v| k.to_sym == :id }
assert_equal @changes_before_save, actual
actual = @person.versions.last.changeset[:time_zone].map(&:class)
assert_equal [NilClass, ActiveSupport::TimeZone], actual
end
context "when that attribute is updated" do
setup do
@attribute_value_before_change = @person.time_zone
@person.assign_attributes(time_zone: "Pacific Time (US & Canada)")
@changes_before_save = @person.changes.dup
@person.save!
end
# Tests for serialization
# -----------------------
#
# Before the serialized attributes fix, the object/object_changes value
# that was stored was ridiculously long (58723).
#
# version.object should not have stored the default, ridiculously long
# (to_yaml) serialization of the TimeZone object.
should "object should not store long serialization of TimeZone object" do
len = @person.versions.last.object.length
assert len < 105, "object length was #{len}"
end
# Need an additional clause to detect what version of ActiveRecord is
# being used for this test because AR4 injects the `updated_at` column
# into the changeset for updates to models.
#
# version.object_changes should not have stored the default,
# ridiculously long (to_yaml) serialization of the TimeZone object
should "object_changes should not store long serialization of TimeZone object" do
max_len = ActiveRecord::VERSION::MAJOR < 4 ? 105 : 118
len = @person.versions.last.object_changes.length
assert len < max_len, "object_changes length was #{len}"
end
# But now it stores the short, serialized value.
should "version.object attribute should have stored value from serializer" do
as_stored_in_version = HashWithIndifferentAccess[
YAML.load(@person.versions.last.object)
]
assert_equal "Samoa", as_stored_in_version[:time_zone]
serialized_value = Person::TimeZoneSerializer.dump(@attribute_value_before_change)
assert_equal serialized_value, as_stored_in_version[:time_zone]
end
should "version.object_changes attribute should have stored value from serializer" do
as_stored_in_version = HashWithIndifferentAccess[
YAML.load(@person.versions.last.object_changes)
]
assert_equal ["Samoa", "Pacific Time (US & Canada)"], as_stored_in_version[:time_zone]
serialized_value = Person::TimeZoneSerializer.dump(@person.time_zone)
assert_equal serialized_value, as_stored_in_version[:time_zone].last
end
# Tests for unserialization:
should "version.reify should convert attribute to original, unserialized value" do
unserialized_value = Person::TimeZoneSerializer.load(@attribute_value_before_change)
assert_equal unserialized_value,
@person.versions.last.reify.time_zone
end
should "version.changeset should convert attribute to original, unserialized value" do
unserialized_value = Person::TimeZoneSerializer.load(@person.time_zone)
assert_equal unserialized_value,
@person.versions.last.changeset[:time_zone].last
end
should "record.changes (before save) returns the original, unserialized values" do
assert_equal [ActiveSupport::TimeZone, ActiveSupport::TimeZone],
@changes_before_save[:time_zone].map(&:class)
end
should "version.changeset should be the same as record.changes was before the save" do
assert_equal @changes_before_save, @person.versions.last.changeset
assert_equal [ActiveSupport::TimeZone, ActiveSupport::TimeZone],
@person.versions.last.changeset[:time_zone].map(&:class)
end
end
end
end
context "A new model instance which uses a custom PaperTrail::Version class" do
setup { @post = Post.new }
context "which is then saved" do
setup { @post.save }
should "change the number of post versions" do assert_equal 1, PostVersion.count end
should "not change the number of versions" do assert_equal(0, PaperTrail::Version.count) end
end
end
context "An existing model instance which uses a custom PaperTrail::Version class" do
setup { @post = Post.create }
should "have one post version" do assert_equal(1, PostVersion.count) end
context "on the first version" do
setup { @version = @post.versions.first }
should "have the correct index" do
assert_equal 0, @version.index
end
end
should "have versions of the custom class" do
assert_equal "PostVersion", @post.versions.first.class.name
end
context "which is modified" do
setup do
@post.update_attributes(content: "Some new content")
end
should "change the number of post versions" do
assert_equal(2, PostVersion.count)
end
should "not change the number of versions" do
assert_equal(0, PaperTrail::Version.count)
end
should "not have stored changes when object_changes column doesn't exist" do
assert_nil @post.versions.last.changeset
end
end
end
context "An overwritten default accessor" do
setup do
@song = Song.create length: 4
@song.update_attributes length: 5
end
should 'return "overwritten" value on live instance' do
assert_equal 5, @song.length
end
should 'return "overwritten" value on reified instance' do
assert_equal 4, @song.versions.last.reify.length
end
context "Has a virtual attribute injected into the ActiveModel::Dirty changes" do
setup do
@song.name = "Good Vibrations"
@song.save
@song.name = "Yellow Submarine"
end
should "return persist the changes on the live instance properly" do
assert_equal "Yellow Submarine", @song.name
end
should 'return "overwritten" virtual attribute on the reified instance' do
assert_equal "Good Vibrations", @song.versions.last.reify.name
end
end
end
context "An unsaved record" do
setup do
@widget = Widget.new
@widget.destroy
end
should "not have a version created on destroy" do
assert @widget.versions.empty?
end
end
context "A model with a custom association" do
setup do
@doc = Document.create
@doc.update_attributes name: "Doc 1"
end
should "not respond to versions method" do
assert !@doc.respond_to?(:versions)
end
should "create a new version record" do
assert_equal 2, @doc.paper_trail_versions.length
end
should "respond to `next_version` as normal" do
assert_equal @doc.paper_trail_versions.last.reify.next_version.name, @doc.name
end
should "respond to `previous_version` as normal" do
@doc.update_attributes name: "Doc 2"
assert_equal 3, @doc.paper_trail_versions.length
assert_equal "Doc 1", @doc.previous_version.name
end
end
context "The `on` option" do
context "on create" do
setup do
Fluxor.instance_eval <<-END
has_paper_trail :on => [:create]
END
@fluxor = Fluxor.create
@fluxor.update_attributes name: "blah"
@fluxor.destroy
end
should "only have a version for the create event" do
assert_equal 1, @fluxor.versions.length
assert_equal "create", @fluxor.versions.last.event
end
end
context "on update" do
setup do
Fluxor.reset_callbacks :create
Fluxor.reset_callbacks :update
Fluxor.reset_callbacks :destroy
Fluxor.instance_eval <<-END
has_paper_trail :on => [:update]
END
@fluxor = Fluxor.create
@fluxor.update_attributes name: "blah"
@fluxor.destroy
end
should "only have a version for the update event" do
assert_equal 1, @fluxor.versions.length
assert_equal "update", @fluxor.versions.last.event
end
end
context "on destroy" do
setup do
Fluxor.reset_callbacks :create
Fluxor.reset_callbacks :update
Fluxor.reset_callbacks :destroy
Fluxor.instance_eval <<-END
has_paper_trail :on => [:destroy]
END
@fluxor = Fluxor.create
@fluxor.update_attributes name: "blah"
@fluxor.destroy
end
should "only have a version for the destroy event" do
assert_equal 1, @fluxor.versions.length
assert_equal "destroy", @fluxor.versions.last.event
end
end
context "on []" do
setup do
Fluxor.reset_callbacks :create
Fluxor.reset_callbacks :update
Fluxor.reset_callbacks :destroy
Fluxor.instance_eval <<-END
has_paper_trail :on => []
END
@fluxor = Fluxor.create
@fluxor.update_attributes name: "blah"
end
teardown do
@fluxor.destroy
end
should "not have any versions" do
assert_equal 0, @fluxor.versions.length
end
should "still respond to touch_with_version" do
@fluxor.touch_with_version
assert_equal 1, @fluxor.versions.length
end
end
context "allows a symbol to be passed" do
setup do
Fluxor.reset_callbacks :create
Fluxor.reset_callbacks :update
Fluxor.reset_callbacks :destroy
Fluxor.instance_eval <<-END
has_paper_trail :on => :create
END
@fluxor = Fluxor.create
@fluxor.update_attributes name: "blah"
@fluxor.destroy
end
should "only have a version for hte create event" do
assert_equal 1, @fluxor.versions.length
assert_equal "create", @fluxor.versions.last.event
end
end
end
context "A model with column version and custom version_method" do
setup do
@legacy_widget = LegacyWidget.create(name: "foo", version: 2)
end
should "set version on create" do
assert_equal 2, @legacy_widget.version
end
should "allow version updates" do
@legacy_widget.update_attributes version: 3
assert_equal 3, @legacy_widget.version
end
should "create a new version record" do
assert_equal 1, @legacy_widget.versions.size
end
end
context "A reified item with a column -version- and custom version_method" do
setup do
widget = LegacyWidget.create(name: "foo", version: 2)
%w( bar baz ).each { |name| widget.update_attributes name: name }
@version = widget.versions.last
@widget = @version.reify
end
should "know which version it came from" do
assert_equal @version, @widget.custom_version
end
should "return its previous self" do
assert_equal @widget.versions[-2].reify, @widget.previous_version
end
end
context "custom events" do
context "on create" do
setup do
Fluxor.reset_callbacks :create
Fluxor.reset_callbacks :update
Fluxor.reset_callbacks :destroy
Fluxor.instance_eval <<-END
has_paper_trail :on => [:create]
END
@fluxor = Fluxor.new.tap { |model| model.paper_trail_event = "created" }
@fluxor.update_attributes name: "blah"
@fluxor.destroy
end
should "only have a version for the created event" do
assert_equal 1, @fluxor.versions.length
assert_equal "created", @fluxor.versions.last.event
end
end
context "on update" do
setup do
Fluxor.reset_callbacks :create
Fluxor.reset_callbacks :update
Fluxor.reset_callbacks :destroy
Fluxor.instance_eval <<-END
has_paper_trail :on => [:update]
END
@fluxor = Fluxor.create.tap { |model| model.paper_trail_event = "name_updated" }
@fluxor.update_attributes name: "blah"
@fluxor.destroy
end
should "only have a version for the name_updated event" do
assert_equal 1, @fluxor.versions.length
assert_equal "name_updated", @fluxor.versions.last.event
end
end
context "on destroy" do
setup do
Fluxor.reset_callbacks :create
Fluxor.reset_callbacks :update
Fluxor.reset_callbacks :destroy
Fluxor.instance_eval <<-END
has_paper_trail :on => [:destroy]
END
@fluxor = Fluxor.create.tap { |model| model.paper_trail_event = "destroyed" }
@fluxor.update_attributes name: "blah"
@fluxor.destroy
end
should "only have a version for the destroy event" do
assert_equal 1, @fluxor.versions.length
assert_equal "destroyed", @fluxor.versions.last.event
end
end
end
context "`PaperTrail::Config.version_limit` set" do
setup do
PaperTrail.config.version_limit = 2
@widget = Widget.create! name: "Henry"
6.times { @widget.update_attribute(:name, FFaker::Lorem.word) }
end
teardown { PaperTrail.config.version_limit = nil }
should "limit the number of versions to 3 (2 plus the created at event)" do
assert_equal "create", @widget.versions.first.event
assert_equal 3, @widget.versions.size
end
end
end