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.
- 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
|