# frozen_string_literal: true require "spec_helper" RSpec.describe Widget, type: :model do let(:widget) { Widget.create! name: "Bob", an_integer: 1 } describe "`be_versioned` matcher" do it { is_expected.to be_versioned } end describe "`have_a_version_with` matcher", versioning: true do before do widget.update!(name: "Leonard", an_integer: 1) widget.update!(name: "Tom") widget.update!(name: "Bob") end it "is possible to do assertions on version attributes" do expect(widget).to have_a_version_with name: "Leonard", an_integer: 1 expect(widget).to have_a_version_with an_integer: 1 expect(widget).to have_a_version_with name: "Tom" end end describe "versioning option" do context "enabled", versioning: true do it "enables versioning" do expect(widget.versions.size).to eq(1) end end context "disabled (default)" do it "does not enable versioning" do expect(widget.versions.size).to eq(0) end end end describe "Callbacks", versioning: true do describe "before_save" do it "resets value for timestamp attrs for update so that value gets updated properly" do widget.update!(name: "Foobar") w = widget.versions.last.reify expect { w.save! }.to change(w, :updated_at) end end describe "after_create" do let(:widget) { Widget.create!(name: "Foobar", created_at: Time.now - 1.week) } it "corresponding version uses the widget's `updated_at`" do expect(widget.versions.last.created_at.to_i).to eq(widget.updated_at.to_i) end end describe "after_update" do before do widget.update!(name: "Foobar", updated_at: Time.now + 1.week) end it "clears the `versions_association_name` virtual attribute" do w = widget.versions.last.reify expect(w.paper_trail).not_to be_live w.save! expect(w.paper_trail).to be_live end it "corresponding version uses the widget updated_at" do expect(widget.versions.last.created_at.to_i).to eq(widget.updated_at.to_i) end end describe "after_destroy" do it "creates a version for that event" do expect { widget.destroy }.to change(widget.versions, :count).by(1) end it "assigns the version into the `versions_association_name`" do expect(widget.version).to be_nil widget.destroy expect(widget.version).not_to be_nil expect(widget.version).to eq(widget.versions.last) end end describe "after_rollback" do let(:rolled_back_name) { "Big Moo" } before do begin widget.transaction do widget.update!(name: rolled_back_name) widget.update!(name: Widget::EXCLUDED_NAME) end rescue ActiveRecord::RecordInvalid widget.reload widget.name = nil widget.save end end it "does not create an event for changes that did not happen" do widget.versions.map(&:changeset).each do |changeset| expect(changeset.fetch("name", [])).not_to include(rolled_back_name) end end it "has not yet loaded the assocation" do expect(widget.versions).not_to be_loaded end end end describe "Association", versioning: true do describe "sort order" do it "sorts by the timestamp order from the `VersionConcern`" do expect(widget.versions.to_sql).to eq( widget.versions.reorder(PaperTrail::Version.timestamp_sort_order).to_sql ) end end end if defined?(ActiveRecord::IdentityMap) && ActiveRecord::IdentityMap.respond_to?(:without) describe "IdentityMap", versioning: true do it "does not clobber the IdentityMap when reifying" do widget.update name: "Henry", created_at: Time.now - 1.day widget.update name: "Harry" allow(ActiveRecord::IdentityMap).to receive(:without) widget.versions.last.reify expect(ActiveRecord::IdentityMap).to have_receive(:without).once end end end describe "#create", versioning: true do it "creates a version record" do wordget = Widget.create assert_equal 1, wordget.versions.length end end describe "#destroy", versioning: true do it "creates a version record" do widget = Widget.create assert_equal 1, widget.versions.length widget.destroy versions_for_widget = PaperTrail::Version.with_item_keys("Widget", widget.id) assert_equal 2, versions_for_widget.length end it "can have multiple destruction records" do versions = lambda { |widget| # Workaround for AR 3. When we drop AR 3 support, we can simply use # the `widget.versions` association, instead of `with_item_keys`. PaperTrail::Version.with_item_keys("Widget", widget.id) } widget = Widget.create assert_equal 1, widget.versions.length widget.destroy assert_equal 2, versions.call(widget).length widget = widget.version.reify widget.save assert_equal 3, versions.call(widget).length widget.destroy assert_equal 4, versions.call(widget).length assert_equal 2, versions.call(widget).where(event: "destroy").length end end describe "#paper_trail.originator", versioning: true do describe "return value" do let(:orig_name) { FFaker::Name.name } let(:new_name) { FFaker::Name.name } before do PaperTrail.request.whodunnit = orig_name end it "returns the originator for the model at a given state" do expect(widget.paper_trail).to be_live expect(widget.paper_trail.originator).to eq(orig_name) ::PaperTrail.request(whodunnit: new_name) { widget.update(name: "Elizabeth") } expect(widget.paper_trail.originator).to eq(new_name) end it "returns the appropriate originator" do widget.update(name: "Andy") PaperTrail.request.whodunnit = new_name widget.update(name: "Elizabeth") reified_widget = widget.versions[1].reify expect(reified_widget.paper_trail.originator).to eq(orig_name) expect(reified_widget).not_to be_new_record end it "can create a new instance with options[:dup]" do widget.update(name: "Andy") PaperTrail.request.whodunnit = new_name widget.update(name: "Elizabeth") reified_widget = widget.versions[1].reify(dup: true) expect(reified_widget.paper_trail.originator).to eq(orig_name) expect(reified_widget).to be_new_record end end end describe "#version_at", versioning: true do context "Timestamp argument is AFTER object has been destroyed" do it "returns nil" do widget.update_attribute(:name, "foobar") widget.destroy expect(widget.paper_trail.version_at(Time.now)).to be_nil end end end describe "touch", versioning: true do it "creates a version" do expect { widget.touch }.to change { widget.versions.count }.by(+1) end context "request is disabled" do it "does not create a version" do count = widget.versions.count PaperTrail.request(enabled: false) do widget.touch end expect(count).to eq(count) end end end describe ".paper_trail.update_columns", versioning: true do it "creates a version record" do widget = Widget.create expect(widget.versions.count).to eq(1) widget.paper_trail.update_columns(name: "Bugle") expect(widget.versions.count).to eq(2) expect(widget.versions.last.event).to(eq("update")) expect(widget.versions.last.changeset[:name]).to eq([nil, "Bugle"]) end end describe "#update", versioning: true do it "creates a version record" do widget = Widget.create assert_equal 1, widget.versions.length widget.update(name: "Bugle") assert_equal 2, widget.versions.length end end end