paper_trail 3.0.6 → 4.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (119) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +5 -0
  3. data/.rspec +1 -2
  4. data/.travis.yml +14 -5
  5. data/CHANGELOG.md +215 -8
  6. data/CONTRIBUTING.md +84 -0
  7. data/README.md +922 -502
  8. data/Rakefile +2 -2
  9. data/doc/bug_report_template.rb +65 -0
  10. data/gemfiles/ar3.gemfile +61 -0
  11. data/lib/generators/paper_trail/install_generator.rb +22 -3
  12. data/lib/generators/paper_trail/templates/add_object_changes_to_versions.rb +6 -1
  13. data/lib/generators/paper_trail/templates/add_transaction_id_column_to_versions.rb +11 -0
  14. data/lib/generators/paper_trail/templates/create_version_associations.rb +17 -0
  15. data/lib/generators/paper_trail/templates/create_versions.rb +22 -1
  16. data/lib/paper_trail.rb +52 -22
  17. data/lib/paper_trail/attributes_serialization.rb +89 -0
  18. data/lib/paper_trail/cleaner.rb +32 -15
  19. data/lib/paper_trail/config.rb +35 -2
  20. data/lib/paper_trail/frameworks/active_record.rb +4 -5
  21. data/lib/paper_trail/frameworks/active_record/models/paper_trail/version_association.rb +7 -0
  22. data/lib/paper_trail/frameworks/rails.rb +1 -0
  23. data/lib/paper_trail/frameworks/rails/controller.rb +27 -11
  24. data/lib/paper_trail/frameworks/rspec.rb +5 -0
  25. data/lib/paper_trail/frameworks/sinatra.rb +3 -1
  26. data/lib/paper_trail/has_paper_trail.rb +304 -148
  27. data/lib/paper_trail/record_history.rb +59 -0
  28. data/lib/paper_trail/reifier.rb +270 -0
  29. data/lib/paper_trail/serializers/json.rb +13 -2
  30. data/lib/paper_trail/serializers/yaml.rb +16 -2
  31. data/lib/paper_trail/version_association_concern.rb +15 -0
  32. data/lib/paper_trail/version_concern.rb +160 -122
  33. data/lib/paper_trail/version_number.rb +3 -3
  34. data/paper_trail.gemspec +22 -9
  35. data/spec/generators/install_generator_spec.rb +4 -4
  36. data/spec/models/animal_spec.rb +36 -0
  37. data/spec/models/boolit_spec.rb +48 -0
  38. data/spec/models/callback_modifier_spec.rb +96 -0
  39. data/spec/models/fluxor_spec.rb +19 -0
  40. data/spec/models/gadget_spec.rb +14 -12
  41. data/spec/models/joined_version_spec.rb +9 -9
  42. data/spec/models/json_version_spec.rb +103 -0
  43. data/spec/models/kitchen/banana_spec.rb +14 -0
  44. data/spec/models/not_on_update_spec.rb +19 -0
  45. data/spec/models/post_with_status_spec.rb +3 -3
  46. data/spec/models/skipper_spec.rb +46 -0
  47. data/spec/models/thing_spec.rb +11 -0
  48. data/spec/models/version_spec.rb +195 -44
  49. data/spec/models/widget_spec.rb +136 -76
  50. data/spec/modules/paper_trail_spec.rb +27 -0
  51. data/spec/modules/version_concern_spec.rb +8 -8
  52. data/spec/modules/version_number_spec.rb +16 -16
  53. data/spec/paper_trail/config_spec.rb +52 -0
  54. data/spec/paper_trail_spec.rb +17 -17
  55. data/spec/rails_helper.rb +34 -0
  56. data/spec/requests/articles_spec.rb +10 -14
  57. data/spec/spec_helper.rb +81 -34
  58. data/spec/support/alt_db_init.rb +1 -1
  59. data/test/dummy/app/controllers/application_controller.rb +1 -1
  60. data/test/dummy/app/controllers/articles_controller.rb +4 -1
  61. data/test/dummy/app/models/animal.rb +2 -0
  62. data/test/dummy/app/models/book.rb +4 -0
  63. data/test/dummy/app/models/boolit.rb +4 -0
  64. data/test/dummy/app/models/callback_modifier.rb +45 -0
  65. data/test/dummy/app/models/chapter.rb +9 -0
  66. data/test/dummy/app/models/citation.rb +5 -0
  67. data/test/dummy/app/models/customer.rb +4 -0
  68. data/test/dummy/app/models/editor.rb +4 -0
  69. data/test/dummy/app/models/editorship.rb +5 -0
  70. data/test/dummy/app/models/fruit.rb +5 -0
  71. data/test/dummy/app/models/kitchen/banana.rb +5 -0
  72. data/test/dummy/app/models/line_item.rb +4 -0
  73. data/test/dummy/app/models/not_on_update.rb +4 -0
  74. data/test/dummy/app/models/order.rb +5 -0
  75. data/test/dummy/app/models/paragraph.rb +5 -0
  76. data/test/dummy/app/models/person.rb +13 -3
  77. data/test/dummy/app/models/post.rb +0 -1
  78. data/test/dummy/app/models/quotation.rb +5 -0
  79. data/test/dummy/app/models/section.rb +6 -0
  80. data/test/dummy/app/models/skipper.rb +6 -0
  81. data/test/dummy/app/models/song.rb +20 -0
  82. data/test/dummy/app/models/thing.rb +3 -0
  83. data/test/dummy/app/models/whatchamajigger.rb +4 -0
  84. data/test/dummy/app/models/widget.rb +5 -0
  85. data/test/dummy/app/versions/json_version.rb +3 -0
  86. data/test/dummy/app/versions/kitchen/banana_version.rb +5 -0
  87. data/test/dummy/config/application.rb +6 -0
  88. data/test/dummy/config/database.postgres.yml +1 -1
  89. data/test/dummy/config/environments/test.rb +5 -1
  90. data/test/dummy/config/initializers/paper_trail.rb +6 -1
  91. data/test/dummy/db/migrate/20110208155312_set_up_test_tables.rb +143 -3
  92. data/test/dummy/db/schema.rb +169 -25
  93. data/test/functional/controller_test.rb +4 -2
  94. data/test/functional/modular_sinatra_test.rb +1 -1
  95. data/test/functional/sinatra_test.rb +1 -1
  96. data/test/paper_trail_test.rb +7 -0
  97. data/test/test_helper.rb +38 -2
  98. data/test/time_travel_helper.rb +15 -0
  99. data/test/unit/associations_test.rb +726 -0
  100. data/test/unit/inheritance_column_test.rb +6 -6
  101. data/test/unit/model_test.rb +109 -125
  102. data/test/unit/protected_attrs_test.rb +4 -3
  103. data/test/unit/serializer_test.rb +6 -6
  104. data/test/unit/serializers/json_test.rb +17 -4
  105. data/test/unit/serializers/yaml_test.rb +5 -1
  106. data/test/unit/version_test.rb +87 -69
  107. metadata +172 -75
  108. data/gemfiles/3.0.gemfile +0 -42
  109. data/test/dummy/public/404.html +0 -26
  110. data/test/dummy/public/422.html +0 -26
  111. data/test/dummy/public/500.html +0 -26
  112. data/test/dummy/public/favicon.ico +0 -0
  113. data/test/dummy/public/javascripts/application.js +0 -2
  114. data/test/dummy/public/javascripts/controls.js +0 -965
  115. data/test/dummy/public/javascripts/dragdrop.js +0 -974
  116. data/test/dummy/public/javascripts/effects.js +0 -1123
  117. data/test/dummy/public/javascripts/prototype.js +0 -6001
  118. data/test/dummy/public/javascripts/rails.js +0 -175
  119. 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 'spec_helper'
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?.should == true
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
@@ -1,84 +1,235 @@
1
- require 'spec_helper'
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.should include(PaperTrail::VersionConcern)
5
+ expect(PaperTrail::Version).to include(PaperTrail::VersionConcern)
6
6
  end
7
7
 
8
8
  describe "Attributes" do
9
- it { should have_db_column(:item_type).of_type(:string) }
10
- it { should have_db_column(:item_id).of_type(:integer) }
11
- it { should have_db_column(:event).of_type(:string) }
12
- it { should have_db_column(:whodunnit).of_type(:string) }
13
- it { should have_db_column(:object).of_type(:text) }
14
- it { should have_db_column(:created_at).of_type(:datetime) }
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 { should have_db_index([:item_type, :item_id]) }
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 :terminator do
26
- it { should respond_to(:terminator) }
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.whodunnit.should == attributes[:whodunnit]
98
+ expect(subject.terminator).to eq(attributes[:whodunnit])
32
99
  end
33
100
  end
34
101
 
35
- describe :version_author do
36
- it { should respond_to(:version_author) }
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).should == subject.method(:terminator)
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
- describe :where_object do
46
- it { PaperTrail::Version.should respond_to(:where_object) }
47
-
48
- context "invalid arguments" do
49
- it "should raise an error" do
50
- expect { PaperTrail::Version.where_object(:foo) }.to raise_error(ArgumentError)
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
- widget.update_attributes!(:name => name, :an_integer => int)
62
- widget.update_attributes!(:name => 'foobar', :an_integer => 100)
63
- widget.update_attributes!(:name => Faker::Name.last_name, :an_integer => 15)
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
- context "`serializer == YAML`" do
67
- specify { PaperTrail.serializer == PaperTrail::Serializers::YAML }
138
+ describe '#where_object' do
139
+ it { expect(PaperTrail::Version).to respond_to(:where_object) }
68
140
 
69
- it "should be able to locate versions according to their `object` contents" do
70
- PaperTrail::Version.where_object(:name => name).should == [widget.versions[1]]
71
- PaperTrail::Version.where_object(:an_integer => 100).should == [widget.versions[2]]
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
- context "`serializer == JSON`" do
76
- before { PaperTrail.serializer = PaperTrail::Serializers::JSON }
77
- specify { PaperTrail.serializer == PaperTrail::Serializers::JSON }
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
- it "should be able to locate versions according to their `object` contents" do
80
- PaperTrail::Version.where_object(:name => name).should == [widget.versions[1]]
81
- PaperTrail::Version.where_object(:an_integer => 100).should == [widget.versions[2]]
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
@@ -1,22 +1,36 @@
1
- require 'spec_helper'
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 { should be_versioned }
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.should == 1
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.should == 0
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.should_not be_live }
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.should be_live
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.should be_nil
82
+ expect(widget.version).to be_nil
57
83
  widget.destroy
58
- widget.version.should_not be_nil
59
- widget.version.should == widget.versions.last
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.should ==
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 :originator do
76
- it { should respond_to(:originator) }
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.should be_live }
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.originator.should == orig_name
136
+ expect(widget.paper_trail_originator).to eq(orig_name)
88
137
  widget.whodunnit(new_name) { |w| w.update_attributes(:name => 'Elizabeth') }
89
- widget.originator.should == new_name
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
- it "should return the appropriate originator" do
102
- reified_widget.originator.should == orig_name
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 :version_at do
109
- it { should respond_to(:version_at) }
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).should be_nil
204
+ expect(widget.version_at(Time.now)).to be_nil
119
205
  end
120
206
  end
121
207
  end
122
208
 
123
- describe :whodunnit do
124
- it { should respond_to(:whodunnit) }
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.should == orig_name # persist `widget`
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.should == new_name
229
+ expect(PaperTrail.whodunnit).to eq(new_name)
144
230
  widget.update_attributes(:name => 'Elizabeth')
145
231
  end
146
- widget.versions.last.whodunnit.should == new_name
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.should == orig_name
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.should == orig_name
243
+ expect(PaperTrail.whodunnit).to eq(orig_name)
158
244
  end
159
245
  end
160
246
  end
161
247
  end
162
248
 
163
- describe :touch_with_version do
164
- it { should respond_to(:touch_with_version) }
249
+ describe '#touch_with_version' do
250
+ it { is_expected.to respond_to(:touch_with_version) }
165
251
 
166
- it "should generate a version" do
252
+ it "creates a version" do
167
253
  count = widget.versions.size
168
254
  widget.touch_with_version
169
- widget.versions.size.should == count + 1
255
+ expect(widget.versions.size).to eq(count + 1)
170
256
  end
171
257
 
172
- it "should increment the `:updated_at` timestamp" do
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.should > time_was
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 :paper_trail_off! do
184
- it { should respond_to(:paper_trail_off!) }
269
+ describe "#paper_trail_enabled_for_model?" do
270
+ it { is_expected.to respond_to(:paper_trail_enabled_for_model?) }
185
271
 
186
- it 'should set the `paper_trail_enabled_for_model?` to `false`' do
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 :paper_trail_off do
194
- it { should respond_to(:paper_trail_off) }
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 display a deprecation warning' do
203
- 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")
204
- subject.paper_trail_on
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 :paper_trail_on! do
285
+ describe '#paper_trail_on!' do
209
286
  before { subject.paper_trail_off! }
210
287
 
211
- it { should respond_to(:paper_trail_on!) }
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?.should == false
291
+ expect(subject.paper_trail_enabled_for_model?).to be false
215
292
  subject.paper_trail_on!
216
- subject.paper_trail_enabled_for_model?.should == true
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