paper_trail 4.0.2 → 4.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +2 -0
  3. data/CHANGELOG.md +27 -0
  4. data/CONTRIBUTING.md +78 -5
  5. data/README.md +328 -268
  6. data/doc/bug_report_template.rb +65 -0
  7. data/gemfiles/3.0.gemfile +7 -4
  8. data/lib/generators/paper_trail/templates/add_object_changes_to_versions.rb +1 -1
  9. data/lib/generators/paper_trail/templates/create_versions.rb +14 -0
  10. data/lib/paper_trail.rb +11 -9
  11. data/lib/paper_trail/attributes_serialization.rb +89 -0
  12. data/lib/paper_trail/cleaner.rb +8 -1
  13. data/lib/paper_trail/config.rb +15 -18
  14. data/lib/paper_trail/frameworks/rails/controller.rb +16 -2
  15. data/lib/paper_trail/has_paper_trail.rb +102 -99
  16. data/lib/paper_trail/record_history.rb +59 -0
  17. data/lib/paper_trail/reifier.rb +270 -0
  18. data/lib/paper_trail/version_association_concern.rb +3 -1
  19. data/lib/paper_trail/version_concern.rb +60 -226
  20. data/lib/paper_trail/version_number.rb +2 -2
  21. data/paper_trail.gemspec +7 -10
  22. data/spec/models/animal_spec.rb +17 -0
  23. data/spec/models/callback_modifier_spec.rb +96 -0
  24. data/spec/models/json_version_spec.rb +20 -17
  25. data/spec/paper_trail/config_spec.rb +52 -0
  26. data/spec/spec_helper.rb +6 -0
  27. data/test/dummy/app/models/callback_modifier.rb +45 -0
  28. data/test/dummy/app/models/chapter.rb +9 -0
  29. data/test/dummy/app/models/citation.rb +5 -0
  30. data/test/dummy/app/models/paragraph.rb +5 -0
  31. data/test/dummy/app/models/quotation.rb +5 -0
  32. data/test/dummy/app/models/section.rb +6 -0
  33. data/test/dummy/config/database.postgres.yml +1 -1
  34. data/test/dummy/config/initializers/paper_trail.rb +3 -1
  35. data/test/dummy/db/migrate/20110208155312_set_up_test_tables.rb +33 -0
  36. data/test/dummy/db/schema.rb +27 -0
  37. data/test/test_helper.rb +36 -0
  38. data/test/unit/associations_test.rb +726 -0
  39. data/test/unit/inheritance_column_test.rb +6 -6
  40. data/test/unit/model_test.rb +62 -594
  41. data/test/unit/protected_attrs_test.rb +3 -2
  42. data/test/unit/version_test.rb +87 -69
  43. metadata +38 -2
@@ -1,8 +1,8 @@
1
1
  module PaperTrail
2
2
  module VERSION
3
3
  MAJOR = 4
4
- MINOR = 0
5
- TINY = 2
4
+ MINOR = 1
5
+ TINY = 0
6
6
  PRE = nil
7
7
 
8
8
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
@@ -33,19 +33,20 @@ Gem::Specification.new do |s|
33
33
  s.add_development_dependency 'rspec-rails', '~> 3.1.0'
34
34
  s.add_development_dependency 'generator_spec'
35
35
  s.add_development_dependency 'database_cleaner', '~> 1.2'
36
+ s.add_development_dependency 'pry-nav'
36
37
 
38
+ # Allow time travel in testing. timecop is only supported after 1.9.2 but does a better cleanup at 'return'
37
39
  if RUBY_VERSION < "1.9.2"
38
40
  s.add_development_dependency 'delorean'
39
-
40
- # rack-cache 1.3 drops ruby 1.8.7 support
41
- s.add_development_dependency 'rack-cache', '1.2'
42
41
  else
43
- # timecop is only supported after 1.9.2 but does a better cleanup at 'return'
44
42
  s.add_development_dependency 'timecop'
45
43
  end
46
44
 
47
- # JRuby support for the test ENV
48
- unless defined?(JRUBY_VERSION)
45
+ if defined?(JRUBY_VERSION)
46
+ s.add_development_dependency 'activerecord-jdbcsqlite3-adapter', '~> 1.3'
47
+ s.add_development_dependency 'activerecord-jdbcpostgresql-adapter', '~> 1.3'
48
+ s.add_development_dependency 'activerecord-jdbcmysql-adapter', '~> 1.3'
49
+ else
49
50
  s.add_development_dependency 'sqlite3', '~> 1.2'
50
51
 
51
52
  # We would prefer to only constrain mysql2 to '~> 0.3',
@@ -54,9 +55,5 @@ Gem::Specification.new do |s|
54
55
  s.add_development_dependency 'mysql2', '~> 0.3.20'
55
56
 
56
57
  s.add_development_dependency 'pg', '~> 0.17'
57
- else
58
- s.add_development_dependency 'activerecord-jdbcsqlite3-adapter', '~> 1.3'
59
- s.add_development_dependency 'activerecord-jdbcpostgresql-adapter', '~> 1.3'
60
- s.add_development_dependency 'activerecord-jdbcmysql-adapter', '~> 1.3'
61
58
  end
62
59
  end
@@ -15,5 +15,22 @@ describe Animal, :type => :model do
15
15
  expect(dog).to be_instance_of(Dog)
16
16
  end
17
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
18
35
  end
19
36
  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
@@ -1,6 +1,8 @@
1
- if JsonVersion.table_exists?
1
+ require 'rails_helper'
2
2
 
3
- require 'rails_helper'
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?
4
6
 
5
7
  describe JsonVersion, :type => :model do
6
8
  it "should include the `VersionConcern` module to get base functionality" do
@@ -70,31 +72,32 @@ if JsonVersion.table_exists?
70
72
  end
71
73
 
72
74
  context "valid arguments", :versioning => true do
73
- let(:fruit_names) { %w(apple orange lemon banana lime strawberry blueberry) }
74
- let(:tropical_fruit_names) { %w(coconut pineapple kiwi mango melon) }
75
- let(:fruit) { Fruit.new }
76
- let(:name) { 'pomegranate' }
77
- let(:color) { Faker::Color.name }
75
+ let(:color) { %w[red green] }
76
+ let(:fruit) { Fruit.create!(:name => name[0]) }
77
+ let(:name) { %w[banana kiwi mango] }
78
78
 
79
79
  before do
80
- fruit.update_attributes!(:name => name)
81
- fruit.update_attributes!(:name => tropical_fruit_names.sample, :color => color)
82
- fruit.update_attributes!(:name => fruit_names.sample, :color => Faker::Color.name)
80
+ fruit.update_attributes!(:name => name[1], :color => color[0])
81
+ fruit.update_attributes!(:name => name[2], :color => color[1])
83
82
  end
84
83
 
85
- it "should be able to locate versions according to their `object_changes` contents" do
86
- expect(fruit.versions.where_object_changes(:name => name)).to eq(fruit.versions[0..1])
87
- expect(fruit.versions.where_object_changes(:color => color)).to eq(fruit.versions[1..2])
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])
88
91
  end
89
92
 
90
- it "should be able to handle queries for multiple attributes" do
91
- expect(fruit.versions.where_object_changes(:color => color, :name => name)).to eq([fruit.versions[1]])
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]])
92
97
  end
93
98
  end
94
99
  end
95
100
  end
96
-
97
101
  end
98
102
  end
99
-
100
103
  end
@@ -0,0 +1,52 @@
1
+ require "rails_helper"
2
+
3
+ module PaperTrail
4
+ RSpec.describe Config do
5
+ describe ".instance" do
6
+ it "returns the singleton instance" do
7
+ expect { described_class.instance }.to_not raise_error
8
+ end
9
+ end
10
+
11
+ describe ".new" do
12
+ it "raises NoMethodError" do
13
+ expect { described_class.new }.to raise_error(NoMethodError)
14
+ end
15
+ end
16
+
17
+ describe "#enabled" do
18
+ context "when paper_trail_enabled is true" do
19
+ it "returns true" do
20
+ store = double
21
+ allow(store).to receive(:fetch).
22
+ with(:paper_trail_enabled, true).
23
+ and_return(true)
24
+ allow(PaperTrail).to receive(:paper_trail_store).and_return(store)
25
+ expect(described_class.instance.enabled).to eq(true)
26
+ end
27
+ end
28
+
29
+ context "when paper_trail_enabled is false" do
30
+ it "returns false" do
31
+ store = double
32
+ allow(store).to receive(:fetch).
33
+ with(:paper_trail_enabled, true).
34
+ and_return(false)
35
+ allow(PaperTrail).to receive(:paper_trail_store).and_return(store)
36
+ expect(described_class.instance.enabled).to eq(false)
37
+ end
38
+ end
39
+
40
+ context "when paper_trail_enabled is nil" do
41
+ it "returns true" do
42
+ store = double
43
+ allow(store).to receive(:fetch).
44
+ with(:paper_trail_enabled, true).
45
+ and_return(nil)
46
+ allow(PaperTrail).to receive(:paper_trail_store).and_return(store)
47
+ expect(described_class.instance.enabled).to eq(true)
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -1,3 +1,9 @@
1
+ begin
2
+ require 'pry-nav'
3
+ rescue LoadError
4
+ # It's OK, we don't include pry in e.g. gemfiles/3.0.gemfile
5
+ end
6
+
1
7
  # This file was generated by the `rspec --init` command. Conventionally, all
2
8
  # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
9
  # The generated `.rspec` file contains `--require spec_helper` which will cause this
@@ -0,0 +1,45 @@
1
+ class CallbackModifier < ActiveRecord::Base
2
+ has_paper_trail :on => []
3
+
4
+ def test_destroy
5
+ transaction do
6
+ run_callbacks(:destroy) do
7
+ self.deleted = true
8
+ save!
9
+ end
10
+ end
11
+ end
12
+
13
+ def flagged_deleted?
14
+ deleted?
15
+ end
16
+ end
17
+
18
+ class BeforeDestroyModifier < CallbackModifier
19
+ has_paper_trail :on => []
20
+ paper_trail_on_destroy :before
21
+ end
22
+
23
+ class AfterDestroyModifier < CallbackModifier
24
+ has_paper_trail :on => []
25
+ paper_trail_on_destroy :after
26
+ end
27
+
28
+ class NoArgDestroyModifier < CallbackModifier
29
+ has_paper_trail :on => []
30
+ paper_trail_on_destroy
31
+ end
32
+
33
+ class UpdateModifier < CallbackModifier
34
+ has_paper_trail :on => []
35
+ paper_trail_on_update
36
+ end
37
+
38
+ class CreateModifier < CallbackModifier
39
+ has_paper_trail :on => []
40
+ paper_trail_on_create
41
+ end
42
+
43
+ class DefaultModifier < CallbackModifier
44
+ has_paper_trail
45
+ end
@@ -0,0 +1,9 @@
1
+ class Chapter < ActiveRecord::Base
2
+ has_many :sections, :dependent => :destroy
3
+ has_many :paragraphs, :through => :sections
4
+
5
+ has_many :quotations, :dependent => :destroy
6
+ has_many :citations, :through => :quotations
7
+
8
+ has_paper_trail
9
+ end
@@ -0,0 +1,5 @@
1
+ class Citation < ActiveRecord::Base
2
+ belongs_to :quotation
3
+
4
+ has_paper_trail
5
+ end
@@ -0,0 +1,5 @@
1
+ class Paragraph < ActiveRecord::Base
2
+ belongs_to :section
3
+
4
+ has_paper_trail
5
+ end
@@ -0,0 +1,5 @@
1
+ class Quotation < ActiveRecord::Base
2
+ belongs_to :chapter
3
+ has_many :citations, :dependent => :destroy
4
+ has_paper_trail
5
+ end
@@ -0,0 +1,6 @@
1
+ class Section < ActiveRecord::Base
2
+ belongs_to :chapter
3
+ has_many :paragraphs, :dependent => :destroy
4
+
5
+ has_paper_trail
6
+ end
@@ -12,4 +12,4 @@ foo:
12
12
 
13
13
  bar:
14
14
  <<: *test
15
- database: paper_trail_bar
15
+ database: paper_trail_bar
@@ -3,6 +3,8 @@ PaperTrail.config.track_associations = true if ENV['TRAVIS']
3
3
 
4
4
  module PaperTrail
5
5
  class Version < ActiveRecord::Base
6
- attr_accessible :answer, :action, :question, :article_id, :ip, :user_agent, :title if ::PaperTrail.active_record_protected_attributes?
6
+ if ::PaperTrail.active_record_protected_attributes?
7
+ attr_accessible :answer, :action, :question, :article_id, :ip, :user_agent, :title
8
+ end
7
9
  end
8
10
  end
@@ -211,9 +211,38 @@ class SetUpTestTables < ActiveRecord::Migration
211
211
  t.string :name
212
212
  t.boolean :scoped, :default => true
213
213
  end
214
+
215
+ create_table :callback_modifiers, :force => true do |t|
216
+ t.string :some_content
217
+ t.boolean :deleted, :default => false
218
+ end
219
+
220
+ create_table :chapters, :force => true do |t|
221
+ t.string :name
222
+ end
223
+
224
+ create_table :sections, :force => true do |t|
225
+ t.integer :chapter_id
226
+ t.string :name
227
+ end
228
+
229
+ create_table :paragraphs, :force => true do |t|
230
+ t.integer :section_id
231
+ t.string :name
232
+ end
233
+
234
+ create_table :quotations, :force => true do |t|
235
+ t.integer :chapter_id
236
+ end
237
+
238
+ create_table :citations, :force => true do |t|
239
+ t.integer :quotation_id
240
+ end
214
241
  end
215
242
 
216
243
  def self.down
244
+ drop_table :citations
245
+ drop_table :quotations
217
246
  drop_table :animals
218
247
  drop_table :skippers
219
248
  drop_table :not_on_updates
@@ -247,8 +276,12 @@ class SetUpTestTables < ActiveRecord::Migration
247
276
  drop_table :line_items
248
277
  drop_table :fruits
249
278
  drop_table :boolits
279
+ drop_table :chapters
280
+ drop_table :sections
281
+ drop_table :paragraphs
250
282
  remove_index :version_associations, :column => [:version_id]
251
283
  remove_index :version_associations, :name => 'index_version_associations_on_foreign_key'
252
284
  drop_table :version_associations
285
+ drop_table :callback_modifiers
253
286
  end
254
287
  end