paper_trail 3.0.6 → 4.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +5 -0
- data/.rspec +1 -2
- data/.travis.yml +14 -5
- data/CHANGELOG.md +215 -8
- data/CONTRIBUTING.md +84 -0
- data/README.md +922 -502
- data/Rakefile +2 -2
- data/doc/bug_report_template.rb +65 -0
- data/gemfiles/ar3.gemfile +61 -0
- data/lib/generators/paper_trail/install_generator.rb +22 -3
- data/lib/generators/paper_trail/templates/add_object_changes_to_versions.rb +6 -1
- data/lib/generators/paper_trail/templates/add_transaction_id_column_to_versions.rb +11 -0
- data/lib/generators/paper_trail/templates/create_version_associations.rb +17 -0
- data/lib/generators/paper_trail/templates/create_versions.rb +22 -1
- data/lib/paper_trail.rb +52 -22
- data/lib/paper_trail/attributes_serialization.rb +89 -0
- data/lib/paper_trail/cleaner.rb +32 -15
- data/lib/paper_trail/config.rb +35 -2
- data/lib/paper_trail/frameworks/active_record.rb +4 -5
- data/lib/paper_trail/frameworks/active_record/models/paper_trail/version_association.rb +7 -0
- data/lib/paper_trail/frameworks/rails.rb +1 -0
- data/lib/paper_trail/frameworks/rails/controller.rb +27 -11
- data/lib/paper_trail/frameworks/rspec.rb +5 -0
- data/lib/paper_trail/frameworks/sinatra.rb +3 -1
- data/lib/paper_trail/has_paper_trail.rb +304 -148
- data/lib/paper_trail/record_history.rb +59 -0
- data/lib/paper_trail/reifier.rb +270 -0
- data/lib/paper_trail/serializers/json.rb +13 -2
- data/lib/paper_trail/serializers/yaml.rb +16 -2
- data/lib/paper_trail/version_association_concern.rb +15 -0
- data/lib/paper_trail/version_concern.rb +160 -122
- data/lib/paper_trail/version_number.rb +3 -3
- data/paper_trail.gemspec +22 -9
- data/spec/generators/install_generator_spec.rb +4 -4
- data/spec/models/animal_spec.rb +36 -0
- data/spec/models/boolit_spec.rb +48 -0
- data/spec/models/callback_modifier_spec.rb +96 -0
- data/spec/models/fluxor_spec.rb +19 -0
- data/spec/models/gadget_spec.rb +14 -12
- data/spec/models/joined_version_spec.rb +9 -9
- data/spec/models/json_version_spec.rb +103 -0
- data/spec/models/kitchen/banana_spec.rb +14 -0
- data/spec/models/not_on_update_spec.rb +19 -0
- data/spec/models/post_with_status_spec.rb +3 -3
- data/spec/models/skipper_spec.rb +46 -0
- data/spec/models/thing_spec.rb +11 -0
- data/spec/models/version_spec.rb +195 -44
- data/spec/models/widget_spec.rb +136 -76
- data/spec/modules/paper_trail_spec.rb +27 -0
- data/spec/modules/version_concern_spec.rb +8 -8
- data/spec/modules/version_number_spec.rb +16 -16
- data/spec/paper_trail/config_spec.rb +52 -0
- data/spec/paper_trail_spec.rb +17 -17
- data/spec/rails_helper.rb +34 -0
- data/spec/requests/articles_spec.rb +10 -14
- data/spec/spec_helper.rb +81 -34
- data/spec/support/alt_db_init.rb +1 -1
- data/test/dummy/app/controllers/application_controller.rb +1 -1
- data/test/dummy/app/controllers/articles_controller.rb +4 -1
- data/test/dummy/app/models/animal.rb +2 -0
- data/test/dummy/app/models/book.rb +4 -0
- data/test/dummy/app/models/boolit.rb +4 -0
- data/test/dummy/app/models/callback_modifier.rb +45 -0
- data/test/dummy/app/models/chapter.rb +9 -0
- data/test/dummy/app/models/citation.rb +5 -0
- data/test/dummy/app/models/customer.rb +4 -0
- data/test/dummy/app/models/editor.rb +4 -0
- data/test/dummy/app/models/editorship.rb +5 -0
- data/test/dummy/app/models/fruit.rb +5 -0
- data/test/dummy/app/models/kitchen/banana.rb +5 -0
- data/test/dummy/app/models/line_item.rb +4 -0
- data/test/dummy/app/models/not_on_update.rb +4 -0
- data/test/dummy/app/models/order.rb +5 -0
- data/test/dummy/app/models/paragraph.rb +5 -0
- data/test/dummy/app/models/person.rb +13 -3
- data/test/dummy/app/models/post.rb +0 -1
- data/test/dummy/app/models/quotation.rb +5 -0
- data/test/dummy/app/models/section.rb +6 -0
- data/test/dummy/app/models/skipper.rb +6 -0
- data/test/dummy/app/models/song.rb +20 -0
- data/test/dummy/app/models/thing.rb +3 -0
- data/test/dummy/app/models/whatchamajigger.rb +4 -0
- data/test/dummy/app/models/widget.rb +5 -0
- data/test/dummy/app/versions/json_version.rb +3 -0
- data/test/dummy/app/versions/kitchen/banana_version.rb +5 -0
- data/test/dummy/config/application.rb +6 -0
- data/test/dummy/config/database.postgres.yml +1 -1
- data/test/dummy/config/environments/test.rb +5 -1
- data/test/dummy/config/initializers/paper_trail.rb +6 -1
- data/test/dummy/db/migrate/20110208155312_set_up_test_tables.rb +143 -3
- data/test/dummy/db/schema.rb +169 -25
- data/test/functional/controller_test.rb +4 -2
- data/test/functional/modular_sinatra_test.rb +1 -1
- data/test/functional/sinatra_test.rb +1 -1
- data/test/paper_trail_test.rb +7 -0
- data/test/test_helper.rb +38 -2
- data/test/time_travel_helper.rb +15 -0
- data/test/unit/associations_test.rb +726 -0
- data/test/unit/inheritance_column_test.rb +6 -6
- data/test/unit/model_test.rb +109 -125
- data/test/unit/protected_attrs_test.rb +4 -3
- data/test/unit/serializer_test.rb +6 -6
- data/test/unit/serializers/json_test.rb +17 -4
- data/test/unit/serializers/yaml_test.rb +5 -1
- data/test/unit/version_test.rb +87 -69
- metadata +172 -75
- data/gemfiles/3.0.gemfile +0 -42
- data/test/dummy/public/404.html +0 -26
- data/test/dummy/public/422.html +0 -26
- data/test/dummy/public/500.html +0 -26
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/public/javascripts/application.js +0 -2
- data/test/dummy/public/javascripts/controls.js +0 -965
- data/test/dummy/public/javascripts/dragdrop.js +0 -974
- data/test/dummy/public/javascripts/effects.js +0 -1123
- data/test/dummy/public/javascripts/prototype.js +0 -6001
- data/test/dummy/public/javascripts/rails.js +0 -175
- data/test/dummy/public/stylesheets/.gitkeep +0 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
require 'rails_helper'
|
|
2
|
+
|
|
3
|
+
module Kitchen
|
|
4
|
+
describe Banana, :type => :model do
|
|
5
|
+
it { is_expected.to be_versioned }
|
|
6
|
+
|
|
7
|
+
describe '#versions' do
|
|
8
|
+
it "returns instances of Kitchen::BananaVersion", :versioning => true do
|
|
9
|
+
banana = described_class.create!
|
|
10
|
+
expect(banana.versions.first).to be_a(Kitchen::BananaVersion)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
require 'rails_helper'
|
|
2
|
+
|
|
3
|
+
describe NotOnUpdate, :type => :model do
|
|
4
|
+
describe "#touch_with_version", :versioning => true do
|
|
5
|
+
let!(:record) { described_class.create! }
|
|
6
|
+
|
|
7
|
+
it "should create a version, regardless" do
|
|
8
|
+
expect { record.touch_with_version }.to change {
|
|
9
|
+
PaperTrail::Version.count
|
|
10
|
+
}.by(+1)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
it "increments the `:updated_at` timestamp" do
|
|
14
|
+
before = record.updated_at
|
|
15
|
+
record.touch_with_version
|
|
16
|
+
expect(record.updated_at).to be > before
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
require '
|
|
1
|
+
require 'rails_helper'
|
|
2
2
|
|
|
3
3
|
# This model is in the test suite soley for the purpose of testing ActiveRecord::Enum,
|
|
4
4
|
# which is available in ActiveRecord4+ only
|
|
5
|
-
describe PostWithStatus do
|
|
5
|
+
describe PostWithStatus, :type => :model do
|
|
6
6
|
if defined?(ActiveRecord::Enum)
|
|
7
7
|
with_versioning do
|
|
8
8
|
let(:post) { PostWithStatus.create!(:status => 'draft') }
|
|
@@ -10,7 +10,7 @@ describe PostWithStatus do
|
|
|
10
10
|
it "should stash the enum value properly in versions" do
|
|
11
11
|
post.published!
|
|
12
12
|
post.archived!
|
|
13
|
-
post.previous_version.published
|
|
13
|
+
expect(post.previous_version.published?).to be true
|
|
14
14
|
end
|
|
15
15
|
end
|
|
16
16
|
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
require 'rails_helper'
|
|
2
|
+
|
|
3
|
+
describe Skipper, :type => :model do
|
|
4
|
+
with_versioning do
|
|
5
|
+
it { is_expected.to be_versioned }
|
|
6
|
+
|
|
7
|
+
describe "#update_attributes!", :versioning => true do
|
|
8
|
+
context "updating a skipped attribute" do
|
|
9
|
+
let(:t1) { Time.zone.local(2015, 7, 15, 20, 34, 0) }
|
|
10
|
+
let(:t2) { Time.zone.local(2015, 7, 15, 20, 34, 30) }
|
|
11
|
+
|
|
12
|
+
it "should not create a version" do
|
|
13
|
+
skipper = Skipper.create!(:another_timestamp => t1)
|
|
14
|
+
expect {
|
|
15
|
+
skipper.update_attributes!(:another_timestamp => t2)
|
|
16
|
+
}.to_not change { skipper.versions.length }
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
describe "reify" do
|
|
22
|
+
context "reifying a with a skipped attribute" do
|
|
23
|
+
let(:t1) { Time.zone.local(2015, 7, 15, 20, 34, 0) }
|
|
24
|
+
let(:t2) { Time.zone.local(2015, 7, 15, 20, 34, 30) }
|
|
25
|
+
|
|
26
|
+
context "without preserve (default)" do
|
|
27
|
+
it "should have no timestamp" do
|
|
28
|
+
skipper = Skipper.create!(:another_timestamp => t1)
|
|
29
|
+
skipper.update_attributes!(:another_timestamp => t2, :name => "Foobar")
|
|
30
|
+
skipper = skipper.versions.last.reify
|
|
31
|
+
expect(skipper.another_timestamp).to be(nil)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
context "with preserve" do
|
|
36
|
+
it "should preserve its timestamp" do
|
|
37
|
+
skipper = Skipper.create!(:another_timestamp => t1)
|
|
38
|
+
skipper.update_attributes!(:another_timestamp => t2, :name => "Foobar")
|
|
39
|
+
skipper = skipper.versions.last.reify(:unversioned_attributes => :preserve)
|
|
40
|
+
expect(skipper.another_timestamp).to eq(t2)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
require 'rails_helper'
|
|
2
|
+
|
|
3
|
+
describe Thing, :type => :model do
|
|
4
|
+
it { is_expected.to be_versioned }
|
|
5
|
+
|
|
6
|
+
describe "should not store object_changes", :versioning => true do
|
|
7
|
+
let(:thing) { Thing.create(:name =>"pencil") }
|
|
8
|
+
|
|
9
|
+
it { expect(thing.versions.last.object_changes).to be_nil }
|
|
10
|
+
end
|
|
11
|
+
end
|
data/spec/models/version_spec.rb
CHANGED
|
@@ -1,84 +1,235 @@
|
|
|
1
|
-
require '
|
|
1
|
+
require 'rails_helper'
|
|
2
2
|
|
|
3
|
-
describe PaperTrail::Version do
|
|
3
|
+
describe PaperTrail::Version, :type => :model do
|
|
4
4
|
it "should include the `VersionConcern` module to get base functionality" do
|
|
5
|
-
PaperTrail::Version.
|
|
5
|
+
expect(PaperTrail::Version).to include(PaperTrail::VersionConcern)
|
|
6
6
|
end
|
|
7
7
|
|
|
8
8
|
describe "Attributes" do
|
|
9
|
-
it {
|
|
10
|
-
it {
|
|
11
|
-
it {
|
|
12
|
-
it {
|
|
13
|
-
it {
|
|
14
|
-
it {
|
|
9
|
+
it { is_expected.to have_db_column(:item_type).of_type(:string) }
|
|
10
|
+
it { is_expected.to have_db_column(:item_id).of_type(:integer) }
|
|
11
|
+
it { is_expected.to have_db_column(:event).of_type(:string) }
|
|
12
|
+
it { is_expected.to have_db_column(:whodunnit).of_type(:string) }
|
|
13
|
+
it { is_expected.to have_db_column(:object).of_type(:text) }
|
|
14
|
+
it { is_expected.to have_db_column(:created_at).of_type(:datetime) }
|
|
15
|
+
|
|
16
|
+
describe "object_changes column", :versioning => true do
|
|
17
|
+
let(:widget) { Widget.create!(:name => 'Dashboard') }
|
|
18
|
+
let(:value) { widget.versions.last.object_changes }
|
|
19
|
+
|
|
20
|
+
context "serializer is YAML" do
|
|
21
|
+
specify { expect(PaperTrail.serializer).to be PaperTrail::Serializers::YAML }
|
|
22
|
+
|
|
23
|
+
it "should store out as a plain hash" do
|
|
24
|
+
expect(value =~ /HashWithIndifferentAccess/).to be_nil
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
context "serializer is JSON" do
|
|
29
|
+
before(:all) { PaperTrail.serializer = PaperTrail::Serializers::JSON }
|
|
30
|
+
|
|
31
|
+
it "should store out as a plain hash" do
|
|
32
|
+
expect(value =~ /HashWithIndifferentAccess/).to be_nil
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
after(:all) { PaperTrail.serializer = PaperTrail::Serializers::YAML }
|
|
36
|
+
end
|
|
37
|
+
end
|
|
15
38
|
end
|
|
16
39
|
|
|
17
40
|
describe "Indexes" do
|
|
18
|
-
it {
|
|
41
|
+
it { is_expected.to have_db_index([:item_type, :item_id]) }
|
|
19
42
|
end
|
|
20
43
|
|
|
21
44
|
describe "Methods" do
|
|
22
45
|
describe "Instance" do
|
|
23
46
|
subject { PaperTrail::Version.new(attributes) rescue PaperTrail::Version.new }
|
|
24
47
|
|
|
25
|
-
describe
|
|
26
|
-
it {
|
|
48
|
+
describe '#paper_trail_originator' do
|
|
49
|
+
it { is_expected.to respond_to(:paper_trail_originator) }
|
|
50
|
+
|
|
51
|
+
context "No previous versions" do
|
|
52
|
+
specify { expect(subject.previous).to be_nil }
|
|
53
|
+
|
|
54
|
+
it "should return nil" do
|
|
55
|
+
expect(subject.paper_trail_originator).to be_nil
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
context "Has previous version", :versioning => true do
|
|
60
|
+
let(:name) { Faker::Name.name }
|
|
61
|
+
let(:widget) { Widget.create!(:name => Faker::Name.name) }
|
|
62
|
+
before do
|
|
63
|
+
widget.versions.first.update_attributes!(:whodunnit => name)
|
|
64
|
+
widget.update_attributes!(:name => Faker::Name.first_name)
|
|
65
|
+
end
|
|
66
|
+
subject { widget.versions.last }
|
|
67
|
+
|
|
68
|
+
specify { expect(subject.previous).to be_instance_of(PaperTrail::Version) }
|
|
69
|
+
|
|
70
|
+
it "should return nil" do
|
|
71
|
+
expect(subject.paper_trail_originator).to eq(name)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
describe "#originator" do
|
|
77
|
+
it { is_expected.to respond_to(:originator) }
|
|
78
|
+
|
|
79
|
+
it 'should set the invoke `paper_trail_originator`' do
|
|
80
|
+
allow(ActiveSupport::Deprecation).to receive(:warn)
|
|
81
|
+
is_expected.to receive(:paper_trail_originator)
|
|
82
|
+
subject.originator
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
it 'should display a deprecation warning' do
|
|
86
|
+
expect(ActiveSupport::Deprecation).to receive(:warn).
|
|
87
|
+
with(/Use paper_trail_originator instead of originator/)
|
|
88
|
+
subject.originator
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
describe '#terminator' do
|
|
93
|
+
it { is_expected.to respond_to(:terminator) }
|
|
27
94
|
|
|
28
95
|
let(:attributes) { {:whodunnit => Faker::Name.first_name} }
|
|
29
96
|
|
|
30
97
|
it "is an alias for the `whodunnit` attribute" do
|
|
31
|
-
subject.
|
|
98
|
+
expect(subject.terminator).to eq(attributes[:whodunnit])
|
|
32
99
|
end
|
|
33
100
|
end
|
|
34
101
|
|
|
35
|
-
describe
|
|
36
|
-
it {
|
|
102
|
+
describe '#version_author' do
|
|
103
|
+
it { is_expected.to respond_to(:version_author) }
|
|
37
104
|
|
|
38
105
|
it "should be an alias for the `terminator` method" do
|
|
39
|
-
subject.method(:version_author).
|
|
106
|
+
expect(subject.method(:version_author)).to eq(subject.method(:terminator))
|
|
40
107
|
end
|
|
41
108
|
end
|
|
42
109
|
end
|
|
43
110
|
|
|
44
111
|
describe "Class" do
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
expect { PaperTrail::Version.where_object([]) }.to raise_error(ArgumentError)
|
|
52
|
-
end
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
context "valid arguments", :versioning => true do
|
|
56
|
-
let(:widget) { Widget.new }
|
|
57
|
-
let(:name) { Faker::Name.first_name }
|
|
58
|
-
let(:int) { rand(10) + 1 }
|
|
112
|
+
column_overrides = [false]
|
|
113
|
+
if ENV['DB'] == 'postgres' && ::ActiveRecord::VERSION::MAJOR >= 4
|
|
114
|
+
column_overrides << 'json'
|
|
115
|
+
# 'jsonb' column types are only supported for ActiveRecord 4.2+
|
|
116
|
+
column_overrides << 'jsonb' if ::ActiveRecord::VERSION::STRING >= '4.2'
|
|
117
|
+
end
|
|
59
118
|
|
|
119
|
+
column_overrides.shuffle.each do |override|
|
|
120
|
+
context "with a #{override || 'text'} column" do
|
|
60
121
|
before do
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
122
|
+
if override
|
|
123
|
+
ActiveRecord::Base.connection.execute("SAVEPOINT pgtest;")
|
|
124
|
+
%w[object object_changes].each do |column|
|
|
125
|
+
ActiveRecord::Base.connection.execute("ALTER TABLE versions DROP COLUMN #{column};")
|
|
126
|
+
ActiveRecord::Base.connection.execute("ALTER TABLE versions ADD COLUMN #{column} #{override};")
|
|
127
|
+
end
|
|
128
|
+
PaperTrail::Version.reset_column_information
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
after do
|
|
132
|
+
if override
|
|
133
|
+
ActiveRecord::Base.connection.execute("ROLLBACK TO SAVEPOINT pgtest;")
|
|
134
|
+
PaperTrail::Version.reset_column_information
|
|
135
|
+
end
|
|
64
136
|
end
|
|
65
137
|
|
|
66
|
-
|
|
67
|
-
|
|
138
|
+
describe '#where_object' do
|
|
139
|
+
it { expect(PaperTrail::Version).to respond_to(:where_object) }
|
|
68
140
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
141
|
+
context "invalid arguments" do
|
|
142
|
+
it "should raise an error" do
|
|
143
|
+
expect { PaperTrail::Version.where_object(:foo) }.to raise_error(ArgumentError)
|
|
144
|
+
expect { PaperTrail::Version.where_object([]) }.to raise_error(ArgumentError)
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
context "valid arguments", :versioning => true do
|
|
149
|
+
let(:widget) { Widget.new }
|
|
150
|
+
let(:name) { Faker::Name.first_name }
|
|
151
|
+
let(:int) { rand(10) + 1 }
|
|
152
|
+
|
|
153
|
+
before do
|
|
154
|
+
widget.update_attributes!(:name => name, :an_integer => int)
|
|
155
|
+
widget.update_attributes!(:name => 'foobar', :an_integer => 100)
|
|
156
|
+
widget.update_attributes!(:name => Faker::Name.last_name, :an_integer => 15)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
context "`serializer == YAML`" do
|
|
160
|
+
specify { expect(PaperTrail.serializer).to be PaperTrail::Serializers::YAML }
|
|
161
|
+
|
|
162
|
+
it "should be able to locate versions according to their `object` contents" do
|
|
163
|
+
expect(PaperTrail::Version.where_object(:name => name)).to eq([widget.versions[1]])
|
|
164
|
+
expect(PaperTrail::Version.where_object(:an_integer => 100)).to eq([widget.versions[2]])
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
context "`serializer == JSON`" do
|
|
169
|
+
before(:all) { PaperTrail.serializer = PaperTrail::Serializers::JSON }
|
|
170
|
+
specify { expect(PaperTrail.serializer).to be PaperTrail::Serializers::JSON }
|
|
171
|
+
|
|
172
|
+
it "should be able to locate versions according to their `object` contents" do
|
|
173
|
+
expect(PaperTrail::Version.where_object(:name => name)).to eq([widget.versions[1]])
|
|
174
|
+
expect(PaperTrail::Version.where_object(:an_integer => 100)).to eq([widget.versions[2]])
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
after(:all) { PaperTrail.serializer = PaperTrail::Serializers::YAML }
|
|
178
|
+
end
|
|
72
179
|
end
|
|
73
180
|
end
|
|
74
181
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
182
|
+
describe '#where_object_changes' do
|
|
183
|
+
it { expect(PaperTrail::Version).to respond_to(:where_object_changes) }
|
|
184
|
+
|
|
185
|
+
context "invalid arguments" do
|
|
186
|
+
it "should raise an error" do
|
|
187
|
+
expect { PaperTrail::Version.where_object_changes(:foo) }.to raise_error(ArgumentError)
|
|
188
|
+
expect { PaperTrail::Version.where_object_changes([]) }.to raise_error(ArgumentError)
|
|
189
|
+
end
|
|
190
|
+
end
|
|
78
191
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
192
|
+
context "valid arguments", :versioning => true do
|
|
193
|
+
let(:widget) { Widget.new }
|
|
194
|
+
let(:name) { Faker::Name.first_name }
|
|
195
|
+
let(:int) { rand(5) + 2 }
|
|
196
|
+
|
|
197
|
+
before do
|
|
198
|
+
widget.update_attributes!(:name => name, :an_integer => 0)
|
|
199
|
+
widget.update_attributes!(:name => 'foobar', :an_integer => 77)
|
|
200
|
+
widget.update_attributes!(:name => Faker::Name.last_name, :an_integer => int)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
context "`serializer == YAML`" do
|
|
204
|
+
specify { expect(PaperTrail.serializer).to be PaperTrail::Serializers::YAML }
|
|
205
|
+
|
|
206
|
+
it "should be able to locate versions according to their `object_changes` contents" do
|
|
207
|
+
expect(widget.versions.where_object_changes(:name => name)).to eq(widget.versions[0..1])
|
|
208
|
+
expect(widget.versions.where_object_changes(:an_integer => 77)).to eq(widget.versions[1..2])
|
|
209
|
+
expect(widget.versions.where_object_changes(:an_integer => int)).to eq([widget.versions.last])
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
it "should be able to handle queries for multiple attributes" do
|
|
213
|
+
expect(widget.versions.where_object_changes(:an_integer => 77, :name => 'foobar')).to eq(widget.versions[1..2])
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
context "`serializer == JSON`" do
|
|
218
|
+
before(:all) { PaperTrail.serializer = PaperTrail::Serializers::JSON }
|
|
219
|
+
specify { expect(PaperTrail.serializer).to be PaperTrail::Serializers::JSON }
|
|
220
|
+
|
|
221
|
+
it "should be able to locate versions according to their `object_changes` contents" do
|
|
222
|
+
expect(widget.versions.where_object_changes(:name => name)).to eq(widget.versions[0..1])
|
|
223
|
+
expect(widget.versions.where_object_changes(:an_integer => 77)).to eq(widget.versions[1..2])
|
|
224
|
+
expect(widget.versions.where_object_changes(:an_integer => int)).to eq([widget.versions.last])
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
it "should be able to handle queries for multiple attributes" do
|
|
228
|
+
expect(widget.versions.where_object_changes(:an_integer => 77, :name => 'foobar')).to eq(widget.versions[1..2])
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
after(:all) { PaperTrail.serializer = PaperTrail::Serializers::YAML }
|
|
232
|
+
end
|
|
82
233
|
end
|
|
83
234
|
end
|
|
84
235
|
end
|
data/spec/models/widget_spec.rb
CHANGED
|
@@ -1,22 +1,36 @@
|
|
|
1
|
-
require '
|
|
1
|
+
require 'rails_helper'
|
|
2
2
|
|
|
3
|
-
describe Widget do
|
|
3
|
+
describe Widget, :type => :model do
|
|
4
4
|
describe '`be_versioned` matcher' do
|
|
5
|
-
it {
|
|
5
|
+
it { is_expected.to be_versioned }
|
|
6
6
|
end
|
|
7
7
|
|
|
8
8
|
let(:widget) { Widget.create! :name => 'Bob', :an_integer => 1 }
|
|
9
9
|
|
|
10
|
+
describe '`have_a_version_with` matcher', :versioning => true do
|
|
11
|
+
before do
|
|
12
|
+
widget.update_attributes!(:name => 'Leonard', :an_integer => 1 )
|
|
13
|
+
widget.update_attributes!(:name => 'Tom')
|
|
14
|
+
widget.update_attributes!(:name => 'Bob')
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it "is possible to do assertions on versions" do
|
|
18
|
+
expect(widget).to have_a_version_with :name => 'Leonard', :an_integer => 1
|
|
19
|
+
expect(widget).to have_a_version_with :an_integer => 1
|
|
20
|
+
expect(widget).to have_a_version_with :name => 'Tom'
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
10
24
|
describe "`versioning` option" do
|
|
11
25
|
context :enabled, :versioning => true do
|
|
12
26
|
it 'should enable versioning for models wrapped within a block' do
|
|
13
|
-
widget.versions.size.
|
|
27
|
+
expect(widget.versions.size).to eq(1)
|
|
14
28
|
end
|
|
15
29
|
end
|
|
16
30
|
|
|
17
31
|
context '`disabled` (default)' do
|
|
18
32
|
it 'should not enable versioning for models wrapped within a block not marked to used versioning' do
|
|
19
|
-
widget.versions.size.
|
|
33
|
+
expect(widget.versions.size).to eq(0)
|
|
20
34
|
end
|
|
21
35
|
end
|
|
22
36
|
end
|
|
@@ -34,16 +48,28 @@ describe Widget do
|
|
|
34
48
|
end
|
|
35
49
|
end
|
|
36
50
|
|
|
51
|
+
describe :after_create do
|
|
52
|
+
let(:widget) { Widget.create!(:name => 'Foobar', :created_at => Time.now - 1.week) }
|
|
53
|
+
|
|
54
|
+
it "corresponding version should use the widget's `updated_at`" do
|
|
55
|
+
expect(widget.versions.last.created_at.to_i).to eq(widget.updated_at.to_i)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
37
59
|
describe :after_update do
|
|
38
|
-
before { widget.update_attributes!(:name => 'Foobar') }
|
|
60
|
+
before { widget.update_attributes!(:name => 'Foobar', :updated_at => Time.now + 1.week) }
|
|
39
61
|
|
|
40
62
|
subject { widget.versions.last.reify }
|
|
41
63
|
|
|
42
|
-
it { subject.
|
|
64
|
+
it { expect(subject).not_to be_live }
|
|
43
65
|
|
|
44
66
|
it "should clear the `versions_association_name` virtual attribute" do
|
|
45
67
|
subject.save!
|
|
46
|
-
subject.
|
|
68
|
+
expect(subject).to be_live
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
it "corresponding version should use the widget updated_at" do
|
|
72
|
+
expect(widget.versions.last.created_at.to_i).to eq(widget.updated_at.to_i)
|
|
47
73
|
end
|
|
48
74
|
end
|
|
49
75
|
|
|
@@ -53,10 +79,33 @@ describe Widget do
|
|
|
53
79
|
end
|
|
54
80
|
|
|
55
81
|
it "should assign the version into the `versions_association_name`" do
|
|
56
|
-
widget.version.
|
|
82
|
+
expect(widget.version).to be_nil
|
|
57
83
|
widget.destroy
|
|
58
|
-
widget.version.
|
|
59
|
-
widget.version.
|
|
84
|
+
expect(widget.version).not_to be_nil
|
|
85
|
+
expect(widget.version).to eq(widget.versions.last)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
describe :after_rollback do
|
|
90
|
+
let(:rolled_back_name) { 'Big Moo' }
|
|
91
|
+
|
|
92
|
+
before do
|
|
93
|
+
begin
|
|
94
|
+
widget.transaction do
|
|
95
|
+
widget.update_attributes!(:name => rolled_back_name)
|
|
96
|
+
widget.update_attributes!(:name => Widget::EXCLUDED_NAME)
|
|
97
|
+
end
|
|
98
|
+
rescue ActiveRecord::RecordInvalid
|
|
99
|
+
widget.reload
|
|
100
|
+
widget.name = nil
|
|
101
|
+
widget.save
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
it 'does not create an event for changes that did not happen' do
|
|
106
|
+
widget.versions.map(&:changeset).each do |changeset|
|
|
107
|
+
expect(changeset.fetch('name', [])).to_not include(rolled_back_name)
|
|
108
|
+
end
|
|
60
109
|
end
|
|
61
110
|
end
|
|
62
111
|
end
|
|
@@ -64,16 +113,16 @@ describe Widget do
|
|
|
64
113
|
describe "Association", :versioning => true do
|
|
65
114
|
describe "sort order" do
|
|
66
115
|
it "should sort by the timestamp order from the `VersionConcern`" do
|
|
67
|
-
widget.versions.to_sql.
|
|
68
|
-
widget.versions.reorder(PaperTrail::Version.timestamp_sort_order).to_sql
|
|
116
|
+
expect(widget.versions.to_sql).to eq(
|
|
117
|
+
widget.versions.reorder(PaperTrail::Version.timestamp_sort_order).to_sql)
|
|
69
118
|
end
|
|
70
119
|
end
|
|
71
120
|
end
|
|
72
121
|
|
|
73
122
|
describe "Methods" do
|
|
74
123
|
describe "Instance", :versioning => true do
|
|
75
|
-
describe
|
|
76
|
-
it {
|
|
124
|
+
describe '#paper_trail_originator' do
|
|
125
|
+
it { is_expected.to respond_to(:paper_trail_originator) }
|
|
77
126
|
|
|
78
127
|
describe "return value" do
|
|
79
128
|
let(:orig_name) { Faker::Name.name }
|
|
@@ -81,12 +130,12 @@ describe Widget do
|
|
|
81
130
|
before { PaperTrail.whodunnit = orig_name }
|
|
82
131
|
|
|
83
132
|
context "accessed from live model instance" do
|
|
84
|
-
specify { widget.
|
|
133
|
+
specify { expect(widget).to be_live }
|
|
85
134
|
|
|
86
135
|
it "should return the originator for the model at a given state" do
|
|
87
|
-
widget.
|
|
136
|
+
expect(widget.paper_trail_originator).to eq(orig_name)
|
|
88
137
|
widget.whodunnit(new_name) { |w| w.update_attributes(:name => 'Elizabeth') }
|
|
89
|
-
widget.
|
|
138
|
+
expect(widget.paper_trail_originator).to eq(new_name)
|
|
90
139
|
end
|
|
91
140
|
end
|
|
92
141
|
|
|
@@ -96,17 +145,54 @@ describe Widget do
|
|
|
96
145
|
PaperTrail.whodunnit = new_name
|
|
97
146
|
widget.update_attributes(:name => 'Elizabeth')
|
|
98
147
|
end
|
|
99
|
-
let(:reified_widget) { widget.versions[1].reify }
|
|
100
148
|
|
|
101
|
-
|
|
102
|
-
reified_widget.
|
|
149
|
+
context "default behavior (no `options[:dup]` option passed in)" do
|
|
150
|
+
let(:reified_widget) { widget.versions[1].reify }
|
|
151
|
+
|
|
152
|
+
it "should return the appropriate originator" do
|
|
153
|
+
expect(reified_widget.paper_trail_originator).to eq(orig_name)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
it "should not create a new model instance" do
|
|
157
|
+
expect(reified_widget).not_to be_new_record
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
context "creating a new instance (`options[:dup] == true`)" do
|
|
162
|
+
let(:reified_widget) { widget.versions[1].reify(:dup => true) }
|
|
163
|
+
|
|
164
|
+
it "should return the appropriate originator" do
|
|
165
|
+
expect(reified_widget.paper_trail_originator).to eq(orig_name)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
it "should not create a new model instance" do
|
|
169
|
+
expect(reified_widget).to be_new_record
|
|
170
|
+
end
|
|
103
171
|
end
|
|
104
172
|
end
|
|
105
173
|
end
|
|
106
174
|
end
|
|
107
175
|
|
|
108
|
-
describe
|
|
109
|
-
|
|
176
|
+
describe "#originator" do
|
|
177
|
+
subject { widget }
|
|
178
|
+
|
|
179
|
+
it { is_expected.to respond_to(:originator) }
|
|
180
|
+
|
|
181
|
+
it 'should set the invoke `paper_trail_originator`' do
|
|
182
|
+
allow(::ActiveSupport::Deprecation).to receive(:warn)
|
|
183
|
+
is_expected.to receive(:paper_trail_originator)
|
|
184
|
+
subject.originator
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
it 'should display a deprecation warning' do
|
|
188
|
+
expect(::ActiveSupport::Deprecation).to receive(:warn).
|
|
189
|
+
with(/Use paper_trail_originator instead of originator/)
|
|
190
|
+
subject.originator
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
describe '#version_at' do
|
|
195
|
+
it { is_expected.to respond_to(:version_at) }
|
|
110
196
|
|
|
111
197
|
context "Timestamp argument is AFTER object has been destroyed" do
|
|
112
198
|
before do
|
|
@@ -115,13 +201,13 @@ describe Widget do
|
|
|
115
201
|
end
|
|
116
202
|
|
|
117
203
|
it "should return `nil`" do
|
|
118
|
-
widget.version_at(Time.now).
|
|
204
|
+
expect(widget.version_at(Time.now)).to be_nil
|
|
119
205
|
end
|
|
120
206
|
end
|
|
121
207
|
end
|
|
122
208
|
|
|
123
|
-
describe
|
|
124
|
-
it {
|
|
209
|
+
describe '#whodunnit' do
|
|
210
|
+
it { is_expected.to respond_to(:whodunnit) }
|
|
125
211
|
|
|
126
212
|
context "no block given" do
|
|
127
213
|
it "should raise an error" do
|
|
@@ -135,44 +221,44 @@ describe Widget do
|
|
|
135
221
|
|
|
136
222
|
before do
|
|
137
223
|
PaperTrail.whodunnit = orig_name
|
|
138
|
-
widget.versions.last.whodunnit.
|
|
224
|
+
expect(widget.versions.last.whodunnit).to eq(orig_name) # persist `widget`
|
|
139
225
|
end
|
|
140
226
|
|
|
141
227
|
it "should modify value of `PaperTrail.whodunnit` while executing the block" do
|
|
142
228
|
widget.whodunnit(new_name) do
|
|
143
|
-
PaperTrail.whodunnit.
|
|
229
|
+
expect(PaperTrail.whodunnit).to eq(new_name)
|
|
144
230
|
widget.update_attributes(:name => 'Elizabeth')
|
|
145
231
|
end
|
|
146
|
-
widget.versions.last.whodunnit.
|
|
232
|
+
expect(widget.versions.last.whodunnit).to eq(new_name)
|
|
147
233
|
end
|
|
148
234
|
|
|
149
235
|
it "should revert the value of `PaperTrail.whodunnit` to it's previous value after executing the block" do
|
|
150
236
|
widget.whodunnit(new_name) { |w| w.update_attributes(:name => 'Elizabeth') }
|
|
151
|
-
PaperTrail.whodunnit.
|
|
237
|
+
expect(PaperTrail.whodunnit).to eq(orig_name)
|
|
152
238
|
end
|
|
153
239
|
|
|
154
240
|
context "error within block" do
|
|
155
241
|
it "should ensure that the whodunnit value still reverts to it's previous value" do
|
|
156
242
|
expect { widget.whodunnit(new_name) { raise } }.to raise_error
|
|
157
|
-
PaperTrail.whodunnit.
|
|
243
|
+
expect(PaperTrail.whodunnit).to eq(orig_name)
|
|
158
244
|
end
|
|
159
245
|
end
|
|
160
246
|
end
|
|
161
247
|
end
|
|
162
248
|
|
|
163
|
-
describe
|
|
164
|
-
it {
|
|
249
|
+
describe '#touch_with_version' do
|
|
250
|
+
it { is_expected.to respond_to(:touch_with_version) }
|
|
165
251
|
|
|
166
|
-
it "
|
|
252
|
+
it "creates a version" do
|
|
167
253
|
count = widget.versions.size
|
|
168
254
|
widget.touch_with_version
|
|
169
|
-
widget.versions.size.
|
|
255
|
+
expect(widget.versions.size).to eq(count + 1)
|
|
170
256
|
end
|
|
171
257
|
|
|
172
|
-
it "
|
|
258
|
+
it "increments the `:updated_at` timestamp" do
|
|
173
259
|
time_was = widget.updated_at
|
|
174
260
|
widget.touch_with_version
|
|
175
|
-
widget.updated_at.
|
|
261
|
+
expect(widget.updated_at).to be > time_was
|
|
176
262
|
end
|
|
177
263
|
end
|
|
178
264
|
end
|
|
@@ -180,57 +266,31 @@ describe Widget do
|
|
|
180
266
|
describe "Class" do
|
|
181
267
|
subject { Widget }
|
|
182
268
|
|
|
183
|
-
describe
|
|
184
|
-
it {
|
|
269
|
+
describe "#paper_trail_enabled_for_model?" do
|
|
270
|
+
it { is_expected.to respond_to(:paper_trail_enabled_for_model?) }
|
|
185
271
|
|
|
186
|
-
it
|
|
187
|
-
subject.paper_trail_enabled_for_model?.should == true
|
|
188
|
-
subject.paper_trail_off!
|
|
189
|
-
subject.paper_trail_enabled_for_model?.should == false
|
|
190
|
-
end
|
|
272
|
+
it { expect(subject.paper_trail_enabled_for_model?).to be true }
|
|
191
273
|
end
|
|
192
274
|
|
|
193
|
-
describe
|
|
194
|
-
it {
|
|
195
|
-
|
|
196
|
-
it 'should set the invoke `paper_trail_off!`' do
|
|
197
|
-
subject.should_receive(:warn)
|
|
198
|
-
subject.should_receive(:paper_trail_off!)
|
|
199
|
-
subject.paper_trail_off
|
|
200
|
-
end
|
|
275
|
+
describe '#paper_trail_off!' do
|
|
276
|
+
it { is_expected.to respond_to(:paper_trail_off!) }
|
|
201
277
|
|
|
202
|
-
it 'should
|
|
203
|
-
subject.
|
|
204
|
-
subject.
|
|
278
|
+
it 'should set the `paper_trail_enabled_for_model?` to `false`' do
|
|
279
|
+
expect(subject.paper_trail_enabled_for_model?).to be true
|
|
280
|
+
subject.paper_trail_off!
|
|
281
|
+
expect(subject.paper_trail_enabled_for_model?).to be false
|
|
205
282
|
end
|
|
206
283
|
end
|
|
207
284
|
|
|
208
|
-
describe
|
|
285
|
+
describe '#paper_trail_on!' do
|
|
209
286
|
before { subject.paper_trail_off! }
|
|
210
287
|
|
|
211
|
-
it {
|
|
288
|
+
it { is_expected.to respond_to(:paper_trail_on!) }
|
|
212
289
|
|
|
213
290
|
it 'should set the `paper_trail_enabled_for_model?` to `true`' do
|
|
214
|
-
subject.paper_trail_enabled_for_model
|
|
291
|
+
expect(subject.paper_trail_enabled_for_model?).to be false
|
|
215
292
|
subject.paper_trail_on!
|
|
216
|
-
subject.paper_trail_enabled_for_model
|
|
217
|
-
end
|
|
218
|
-
end
|
|
219
|
-
|
|
220
|
-
describe :paper_trail_on do
|
|
221
|
-
before { subject.paper_trail_off! }
|
|
222
|
-
|
|
223
|
-
it { should respond_to(:paper_trail_on) }
|
|
224
|
-
|
|
225
|
-
it 'should set the invoke `paper_trail_on!`' do
|
|
226
|
-
subject.should_receive(:warn)
|
|
227
|
-
subject.should_receive(:paper_trail_on!)
|
|
228
|
-
subject.paper_trail_on
|
|
229
|
-
end
|
|
230
|
-
|
|
231
|
-
it 'should display a deprecation warning' do
|
|
232
|
-
subject.should_receive(:warn).with("DEPRECATED: use `paper_trail_on!` instead of `paper_trail_on`. Support for `paper_trail_on` will be removed in PaperTrail 3.1")
|
|
233
|
-
subject.paper_trail_on
|
|
293
|
+
expect(subject.paper_trail_enabled_for_model?).to be true
|
|
234
294
|
end
|
|
235
295
|
end
|
|
236
296
|
end
|