paper_trail 4.0.2 → 4.1.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/.travis.yml +2 -0
- data/CHANGELOG.md +27 -0
- data/CONTRIBUTING.md +78 -5
- data/README.md +328 -268
- data/doc/bug_report_template.rb +65 -0
- data/gemfiles/3.0.gemfile +7 -4
- data/lib/generators/paper_trail/templates/add_object_changes_to_versions.rb +1 -1
- data/lib/generators/paper_trail/templates/create_versions.rb +14 -0
- data/lib/paper_trail.rb +11 -9
- data/lib/paper_trail/attributes_serialization.rb +89 -0
- data/lib/paper_trail/cleaner.rb +8 -1
- data/lib/paper_trail/config.rb +15 -18
- data/lib/paper_trail/frameworks/rails/controller.rb +16 -2
- data/lib/paper_trail/has_paper_trail.rb +102 -99
- data/lib/paper_trail/record_history.rb +59 -0
- data/lib/paper_trail/reifier.rb +270 -0
- data/lib/paper_trail/version_association_concern.rb +3 -1
- data/lib/paper_trail/version_concern.rb +60 -226
- data/lib/paper_trail/version_number.rb +2 -2
- data/paper_trail.gemspec +7 -10
- data/spec/models/animal_spec.rb +17 -0
- data/spec/models/callback_modifier_spec.rb +96 -0
- data/spec/models/json_version_spec.rb +20 -17
- data/spec/paper_trail/config_spec.rb +52 -0
- data/spec/spec_helper.rb +6 -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/paragraph.rb +5 -0
- data/test/dummy/app/models/quotation.rb +5 -0
- data/test/dummy/app/models/section.rb +6 -0
- data/test/dummy/config/database.postgres.yml +1 -1
- data/test/dummy/config/initializers/paper_trail.rb +3 -1
- data/test/dummy/db/migrate/20110208155312_set_up_test_tables.rb +33 -0
- data/test/dummy/db/schema.rb +27 -0
- data/test/test_helper.rb +36 -0
- data/test/unit/associations_test.rb +726 -0
- data/test/unit/inheritance_column_test.rb +6 -6
- data/test/unit/model_test.rb +62 -594
- data/test/unit/protected_attrs_test.rb +3 -2
- data/test/unit/version_test.rb +87 -69
- metadata +38 -2
data/paper_trail.gemspec
CHANGED
@@ -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
|
-
|
48
|
-
|
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
|
data/spec/models/animal_spec.rb
CHANGED
@@ -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
|
-
|
1
|
+
require 'rails_helper'
|
2
2
|
|
3
|
-
|
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(:
|
74
|
-
let(:
|
75
|
-
let(:
|
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 =>
|
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 "
|
86
|
-
expect(
|
87
|
-
|
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 "
|
91
|
-
expect(
|
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
|
data/spec/spec_helper.rb
CHANGED
@@ -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
|
@@ -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
|
-
|
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
|