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.
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
@@ -1,8 +1,8 @@
1
1
  module PaperTrail
2
2
  module VERSION
3
- MAJOR = 3
4
- MINOR = 0
5
- TINY = 6
3
+ MAJOR = 4
4
+ MINOR = 2
5
+ TINY = 0
6
6
  PRE = nil
7
7
 
8
8
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
data/paper_trail.gemspec CHANGED
@@ -19,28 +19,41 @@ Gem::Specification.new do |s|
19
19
 
20
20
  s.required_rubygems_version = '>= 1.3.6'
21
21
 
22
- s.add_dependency 'activerecord', ['>= 3.0', '< 5.0']
23
- s.add_dependency 'activesupport', ['>= 3.0', '< 5.0']
22
+ s.add_dependency 'activerecord', ['>= 3.0', '< 6.0']
23
+ s.add_dependency 'activesupport', ['>= 3.0', '< 6.0']
24
+ s.add_dependency 'request_store', '~> 1.1'
24
25
 
25
26
  s.add_development_dependency 'rake', '~> 10.1.1'
26
27
  s.add_development_dependency 'shoulda', '~> 3.5'
27
28
  # s.add_development_dependency 'shoulda-matchers', '~> 1.5' # needed for ActiveRecord < 4
28
- s.add_development_dependency 'ffaker', '>= 1.15'
29
+ s.add_development_dependency 'ffaker', '<= 1.31.0'
29
30
  s.add_development_dependency 'railties', ['>= 3.0', '< 5.0']
30
31
  s.add_development_dependency 'sinatra', '~> 1.0'
31
32
  s.add_development_dependency 'rack-test', '>= 0.6'
32
- s.add_development_dependency 'rspec-rails', '~> 2.14'
33
+ s.add_development_dependency 'rspec-rails', '~> 3.1.0'
33
34
  s.add_development_dependency 'generator_spec'
34
35
  s.add_development_dependency 'database_cleaner', '~> 1.2'
36
+ s.add_development_dependency 'pry-nav'
35
37
 
36
- # JRuby support for the test ENV
37
- unless defined?(JRUBY_VERSION)
38
- s.add_development_dependency 'sqlite3', '~> 1.2'
39
- s.add_development_dependency 'mysql2', '~> 0.3'
40
- s.add_development_dependency 'pg', '~> 0.17'
38
+ # Allow time travel in testing. timecop is only supported after 1.9.2 but does a better cleanup at 'return'
39
+ if RUBY_VERSION < "1.9.2"
40
+ s.add_development_dependency 'delorean'
41
41
  else
42
+ s.add_development_dependency 'timecop'
43
+ end
44
+
45
+ if defined?(JRUBY_VERSION)
42
46
  s.add_development_dependency 'activerecord-jdbcsqlite3-adapter', '~> 1.3'
43
47
  s.add_development_dependency 'activerecord-jdbcpostgresql-adapter', '~> 1.3'
44
48
  s.add_development_dependency 'activerecord-jdbcmysql-adapter', '~> 1.3'
49
+ else
50
+ s.add_development_dependency 'sqlite3', '~> 1.2'
51
+
52
+ # We would prefer to only constrain mysql2 to '~> 0.3',
53
+ # but a rails bug (https://github.com/rails/rails/issues/21544)
54
+ # requires us to constrain to '~> 0.3.20' for now.
55
+ s.add_development_dependency 'mysql2', '~> 0.3.20'
56
+
57
+ s.add_development_dependency 'pg', '~> 0.17'
45
58
  end
46
59
  end
@@ -1,4 +1,4 @@
1
- require 'spec_helper'
1
+ require 'rails_helper'
2
2
  require 'generator_spec/test_case'
3
3
  require File.expand_path('../../../lib/generators/paper_trail/install_generator', __FILE__)
4
4
 
@@ -15,7 +15,7 @@ describe PaperTrail::InstallGenerator, :type => :generator do
15
15
  end
16
16
 
17
17
  it "generates a migration for creating the 'versions' table" do
18
- destination_root.should have_structure {
18
+ expect(destination_root).to have_structure {
19
19
  directory 'db' do
20
20
  directory 'migrate' do
21
21
  migration 'create_versions' do
@@ -36,7 +36,7 @@ describe PaperTrail::InstallGenerator, :type => :generator do
36
36
  end
37
37
 
38
38
  it "generates a migration for creating the 'versions' table" do
39
- destination_root.should have_structure {
39
+ expect(destination_root).to have_structure {
40
40
  directory 'db' do
41
41
  directory 'migrate' do
42
42
  migration 'create_versions' do
@@ -50,7 +50,7 @@ describe PaperTrail::InstallGenerator, :type => :generator do
50
50
  end
51
51
 
52
52
  it "generates a migration for adding the 'object_changes' column to the 'versions' table" do
53
- destination_root.should have_structure {
53
+ expect(destination_root).to have_structure {
54
54
  directory 'db' do
55
55
  directory 'migrate' do
56
56
  migration 'add_object_changes_to_versions' do
@@ -0,0 +1,36 @@
1
+ require 'rails_helper'
2
+
3
+ describe Animal, :type => :model do
4
+ it { is_expected.to be_versioned }
5
+
6
+ describe "STI", :versioning => true do
7
+ it { expect(Animal.inheritance_column).to eq('species') }
8
+
9
+ describe "updates to the `inheritance_column`" do
10
+ subject { Cat.create!(:name => 'Leo') }
11
+
12
+ it "should be allowed" do
13
+ subject.update_attributes(:name => 'Spike', :species => 'Dog')
14
+ dog = Animal.find(subject.id)
15
+ expect(dog).to be_instance_of(Dog)
16
+ end
17
+ end
18
+
19
+ context 'with callback-methods' do
20
+ context 'when only has_paper_trail set in super class' do
21
+ let(:callback_cat) { Cat.create(:name => 'Markus') }
22
+
23
+ it 'trails all events' do
24
+ callback_cat.update_attributes(:name => 'Billie')
25
+ callback_cat.destroy
26
+ expect(callback_cat.versions.collect(&:event)).to eq %w(create update destroy)
27
+ end
28
+
29
+ it 'does not break reify' do
30
+ callback_cat.destroy
31
+ expect { callback_cat.versions.last.reify }.not_to raise_error
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,48 @@
1
+ require 'rails_helper'
2
+ require Rails.root.join('..', 'custom_json_serializer')
3
+
4
+ describe Boolit, :type => :model do
5
+ it { is_expected.to be_versioned }
6
+
7
+ it "has a default scope" do
8
+ expect(subject.default_scopes).to_not be_empty
9
+ end
10
+
11
+ describe "Versioning", :versioning => true do
12
+ subject { Boolit.create! }
13
+ before { subject.update_attributes!(:name => Faker::Name.name) }
14
+
15
+ it "should have versions" do
16
+ expect(subject.versions.size).to eq(2)
17
+ end
18
+
19
+ it "should be able to be reified and persisted" do
20
+ expect { subject.versions.last.reify.save! }.to_not raise_error
21
+ end
22
+
23
+ context "Instance falls out of default scope" do
24
+ before { subject.update_attributes!(:scoped => false) }
25
+
26
+ it "is NOT scoped" do
27
+ expect(Boolit.first).to be_nil
28
+ end
29
+
30
+ it "should still be able to be reified and persisted" do
31
+ expect { subject.previous_version.save! }.to_not raise_error
32
+ end
33
+
34
+ context "with `nil` attributes on the live instance" do
35
+ before do
36
+ PaperTrail.serializer = CustomJsonSerializer
37
+ subject.update_attributes!(:name => nil)
38
+ subject.update_attributes!(:name => Faker::Name.name)
39
+ end
40
+ after { PaperTrail.serializer = PaperTrail::Serializers::YAML }
41
+
42
+ it "should not overwrite that attribute during the reification process" do
43
+ expect(subject.previous_version.name).to be_nil
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,96 @@
1
+ require 'rails_helper'
2
+
3
+ describe CallbackModifier, :type => :model do
4
+ with_versioning do
5
+ describe 'callback-methods', :versioning => true do
6
+ describe 'paper_trail_on_destroy' do
7
+ it 'should add :destroy to paper_trail_options[:on]' do
8
+ modifier = NoArgDestroyModifier.create!(:some_content => Faker::Lorem.sentence)
9
+ expect(modifier.paper_trail_options[:on]).to eq [:destroy]
10
+ end
11
+
12
+ context 'when :before' do
13
+ it 'should create the version before destroy' do
14
+ modifier = BeforeDestroyModifier.create!(:some_content => Faker::Lorem.sentence)
15
+ modifier.test_destroy
16
+ expect(modifier.versions.last.reify).not_to be_flagged_deleted
17
+ end
18
+ end
19
+
20
+ context 'when :after' do
21
+ it 'should create the version after destroy' do
22
+ modifier = AfterDestroyModifier.create!(:some_content => Faker::Lorem.sentence)
23
+ modifier.test_destroy
24
+ expect(modifier.versions.last.reify).to be_flagged_deleted
25
+ end
26
+ end
27
+
28
+ context 'when no argument' do
29
+ it 'should default to after destroy' do
30
+ modifier = NoArgDestroyModifier.create!(:some_content => Faker::Lorem.sentence)
31
+ modifier.test_destroy
32
+ expect(modifier.versions.last.reify).to be_flagged_deleted
33
+ end
34
+ end
35
+ end
36
+
37
+ describe 'paper_trail_on_update' do
38
+ it 'should add :update to paper_trail_options[:on]' do
39
+ modifier = UpdateModifier.create!(:some_content => Faker::Lorem.sentence)
40
+ expect(modifier.paper_trail_options[:on]).to eq [:update]
41
+ end
42
+
43
+ it 'should create a version' do
44
+ modifier = UpdateModifier.create!(:some_content => Faker::Lorem.sentence)
45
+ modifier.update_attributes! :some_content => 'modified'
46
+ expect(modifier.versions.last.event).to eq 'update'
47
+ end
48
+ end
49
+
50
+ describe 'paper_trail_on_create' do
51
+ it 'should add :create to paper_trail_options[:on]' do
52
+ modifier = CreateModifier.create!(:some_content => Faker::Lorem.sentence)
53
+ expect(modifier.paper_trail_options[:on]).to eq [:create]
54
+ end
55
+
56
+ it 'should create a version' do
57
+ modifier = CreateModifier.create!(:some_content => Faker::Lorem.sentence)
58
+ expect(modifier.versions.last.event).to eq 'create'
59
+ end
60
+ end
61
+
62
+ context 'when no callback-method used' do
63
+ it 'should set paper_trail_options[:on] to [:create, :update, :destroy]' do
64
+ modifier = DefaultModifier.create!(:some_content => Faker::Lorem.sentence)
65
+ expect(modifier.paper_trail_options[:on]).to eq [:create, :update, :destroy]
66
+ end
67
+
68
+ it 'should default to track destroy' do
69
+ modifier = DefaultModifier.create!(:some_content => Faker::Lorem.sentence)
70
+ modifier.destroy
71
+ expect(modifier.versions.last.event).to eq 'destroy'
72
+ end
73
+
74
+ it 'should default to track update' do
75
+ modifier = DefaultModifier.create!(:some_content => Faker::Lorem.sentence)
76
+ modifier.update_attributes! :some_content => 'modified'
77
+ expect(modifier.versions.last.event).to eq 'update'
78
+ end
79
+
80
+ it 'should default to track create' do
81
+ modifier = DefaultModifier.create!(:some_content => Faker::Lorem.sentence)
82
+ expect(modifier.versions.last.event).to eq 'create'
83
+ end
84
+ end
85
+
86
+ context 'when only one callback-method' do
87
+ it 'does only track the corresponding event' do
88
+ modifier = CreateModifier.create!(:some_content => Faker::Lorem.sentence)
89
+ modifier.update_attributes!(:some_content => 'modified')
90
+ modifier.test_destroy
91
+ expect(modifier.versions.collect(&:event)).to eq ['create']
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,19 @@
1
+ require 'rails_helper'
2
+
3
+ describe Fluxor, :type => :model do
4
+ describe '`be_versioned` matcher' do
5
+ it { is_expected.to_not be_versioned }
6
+ end
7
+
8
+ describe "Methods" do
9
+ describe "Class" do
10
+ subject { Fluxor }
11
+
12
+ describe "#paper_trail_enabled_for_model?" do
13
+ it { is_expected.to respond_to(:paper_trail_enabled_for_model?) }
14
+
15
+ it { expect(subject.paper_trail_enabled_for_model?).to be false }
16
+ end
17
+ end
18
+ end
19
+ end
@@ -1,7 +1,7 @@
1
- require 'spec_helper'
1
+ require 'rails_helper'
2
2
 
3
- describe Gadget do
4
- it { should be_versioned }
3
+ describe Gadget, :type => :model do
4
+ it { is_expected.to be_versioned }
5
5
 
6
6
  let(:gadget) { Gadget.create!(:name => 'Wrench', :brand => 'Acme') }
7
7
 
@@ -22,16 +22,18 @@ describe Gadget do
22
22
  describe "Methods" do
23
23
  describe "Instance", :versioning => true do
24
24
  describe "private" do
25
- describe :changed_notably? do
25
+ describe '#changed_notably?' do
26
26
  subject { Gadget.new(:created_at => Time.now) }
27
27
 
28
28
  # apparently the private methods list in Ruby18 is different than in Ruby19+
29
- if RUBY_VERSION.to_f >= 1.9
30
- its(:private_methods) { should include(:changed_notably?) }
29
+ if RUBY_VERSION >= '1.9'
30
+ it { expect(subject.private_methods).to include(:changed_notably?) }
31
+ else
32
+ it { expect(subject.private_methods).to include('changed_notably?') }
31
33
  end
32
34
 
33
35
  context "create events" do
34
- it { subject.send(:changed_notably?).should == true }
36
+ it { expect(subject.send(:changed_notably?)).to be true }
35
37
  end
36
38
 
37
39
  context "update events" do
@@ -40,24 +42,24 @@ describe Gadget do
40
42
  context "without update timestamps" do
41
43
  it "should only acknowledge non-ignored attrs" do
42
44
  subject.name = 'Wrench'
43
- subject.send(:changed_notably?).should == true
45
+ expect(subject.send(:changed_notably?)).to be true
44
46
  end
45
47
 
46
- it "should not acknowledge ignored attrs and timestamps only" do
48
+ it "should not acknowledge ignored attr (brand)" do
47
49
  subject.brand = 'Acme'
48
- subject.send(:changed_notably?).should == false
50
+ expect(subject.send(:changed_notably?)).to be false
49
51
  end
50
52
  end
51
53
 
52
54
  context "with update timestamps" do
53
55
  it "should only acknowledge non-ignored attrs" do
54
56
  subject.name, subject.updated_at = 'Wrench', Time.now
55
- subject.send(:changed_notably?).should == true
57
+ expect(subject.send(:changed_notably?)).to be true
56
58
  end
57
59
 
58
60
  it "should not acknowledge ignored attrs and timestamps only" do
59
61
  subject.brand, subject.updated_at = 'Acme', Time.now
60
- subject.send(:changed_notably?).should == false
62
+ expect(subject.send(:changed_notably?)).to be false
61
63
  end
62
64
  end
63
65
  end
@@ -1,32 +1,32 @@
1
- require 'spec_helper'
1
+ require 'rails_helper'
2
2
 
3
- describe JoinedVersion, :versioning => true do
4
- it { JoinedVersion.superclass.should == PaperTrail::Version }
3
+ describe JoinedVersion, :type => :model, :versioning => true do
4
+ it { expect(JoinedVersion.superclass).to be PaperTrail::Version }
5
5
 
6
6
  let(:widget) { Widget.create!(:name => Faker::Name.name) }
7
7
  let(:version) { JoinedVersion.first }
8
8
 
9
9
  describe "Scopes" do
10
10
  describe "default_scope" do
11
- it { JoinedVersion.default_scopes.should_not be_empty }
11
+ it { expect(JoinedVersion.default_scopes).not_to be_empty }
12
12
  end
13
13
 
14
14
  describe "VersionConcern::ClassMethods" do
15
15
  before { widget } # persist a widget
16
16
 
17
- describe :subsequent do
17
+ describe '#subsequent' do
18
18
  it "shouldn't error out when there is a default_scope that joins" do
19
19
  JoinedVersion.subsequent(version).first
20
20
  end
21
21
  end
22
22
 
23
- describe :preceding do
23
+ describe '#preceding' do
24
24
  it "shouldn't error out when there is a default scope that joins" do
25
25
  JoinedVersion.preceding(version).first
26
26
  end
27
27
  end
28
28
 
29
- describe :between do
29
+ describe '#between' do
30
30
  it "shouldn't error out when there is a default scope that joins" do
31
31
  JoinedVersion.between(Time.now, 1.minute.from_now).first
32
32
  end
@@ -35,8 +35,8 @@ describe JoinedVersion, :versioning => true do
35
35
  end
36
36
 
37
37
  describe "Methods" do
38
- describe :index do
39
- it { should respond_to(:index) }
38
+ describe '#index' do
39
+ it { is_expected.to respond_to(:index) }
40
40
 
41
41
  it "shouldn't error out when there is a default scope that joins" do
42
42
  widget # persist a widget
@@ -0,0 +1,103 @@
1
+ require 'rails_helper'
2
+
3
+ # The `json_versions` table tests postgres' `json` data type. So, that
4
+ # table is only created when testing against postgres and ActiveRecord >= 4.
5
+ if JsonVersion.table_exists?
6
+
7
+ describe JsonVersion, :type => :model do
8
+ it "should include the `VersionConcern` module to get base functionality" do
9
+ expect(JsonVersion).to include(PaperTrail::VersionConcern)
10
+ end
11
+
12
+ describe "Methods" do
13
+ describe "Class" do
14
+
15
+ describe '#where_object' do
16
+ it { expect(JsonVersion).to respond_to(:where_object) }
17
+
18
+ it "escapes values" do
19
+ f = Fruit.create(:name => 'Bobby')
20
+ expect(
21
+ f.
22
+ versions.
23
+ where_object(:name => "Robert'; DROP TABLE Students;--").
24
+ count
25
+ ).to eq(0)
26
+ end
27
+
28
+ context "invalid arguments" do
29
+ it "should raise an error" do
30
+ expect { JsonVersion.where_object(:foo) }.to raise_error(ArgumentError)
31
+ expect { JsonVersion.where_object([]) }.to raise_error(ArgumentError)
32
+ end
33
+ end
34
+
35
+ context "valid arguments", :versioning => true do
36
+ let(:fruit_names) { %w(apple orange lemon banana lime coconut strawberry blueberry) }
37
+ let(:fruit) { Fruit.new }
38
+ let(:name) { 'pomegranate' }
39
+ let(:color) { Faker::Color.name }
40
+
41
+ before do
42
+ fruit.update_attributes!(:name => name)
43
+ fruit.update_attributes!(:name => fruit_names.sample, :color => color)
44
+ fruit.update_attributes!(:name => fruit_names.sample, :color => Faker::Color.name)
45
+ end
46
+
47
+ it "should be able to locate versions according to their `object` contents" do
48
+ expect(JsonVersion.where_object(:name => name)).to eq([fruit.versions[1]])
49
+ expect(JsonVersion.where_object(:color => color)).to eq([fruit.versions[2]])
50
+ end
51
+ end
52
+ end
53
+
54
+ describe '#where_object_changes' do
55
+ it { expect(JsonVersion).to respond_to(:where_object_changes) }
56
+
57
+ it "escapes values" do
58
+ f = Fruit.create(:name => 'Bobby')
59
+ expect(
60
+ f.
61
+ versions.
62
+ where_object_changes(:name => "Robert'; DROP TABLE Students;--").
63
+ count
64
+ ).to eq(0)
65
+ end
66
+
67
+ context "invalid arguments" do
68
+ it "should raise an error" do
69
+ expect { JsonVersion.where_object_changes(:foo) }.to raise_error(ArgumentError)
70
+ expect { JsonVersion.where_object_changes([]) }.to raise_error(ArgumentError)
71
+ end
72
+ end
73
+
74
+ context "valid arguments", :versioning => true do
75
+ let(:color) { %w[red green] }
76
+ let(:fruit) { Fruit.create!(:name => name[0]) }
77
+ let(:name) { %w[banana kiwi mango] }
78
+
79
+ before do
80
+ fruit.update_attributes!(:name => name[1], :color => color[0])
81
+ fruit.update_attributes!(:name => name[2], :color => color[1])
82
+ end
83
+
84
+ it "finds versions according to their `object_changes` contents" do
85
+ expect(
86
+ fruit.versions.where_object_changes(:name => name[0])
87
+ ).to match_array(fruit.versions[0..1])
88
+ expect(
89
+ fruit.versions.where_object_changes(:color => color[0])
90
+ ).to match_array(fruit.versions[1..2])
91
+ end
92
+
93
+ it "finds versions with multiple attributes changed" do
94
+ expect(
95
+ fruit.versions.where_object_changes(:color => color[0], :name => name[0])
96
+ ).to match_array([fruit.versions[1]])
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end