vestal_versions 1.0.2 → 2.0.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.
- data/.gitignore +19 -20
- data/.travis.yml +22 -0
- data/CHANGELOG.md +7 -0
- data/Gemfile +10 -0
- data/README.rdoc +63 -36
- data/Rakefile +4 -43
- data/gemfiles/activerecord_3_0.gemfile +10 -0
- data/gemfiles/activerecord_3_1.gemfile +10 -0
- data/gemfiles/activerecord_3_2.gemfile +10 -0
- data/gemfiles/activerecord_4_0.gemfile +10 -0
- data/lib/generators/vestal_versions.rb +11 -0
- data/lib/generators/vestal_versions/migration/migration_generator.rb +17 -0
- data/{generators/vestal_versions → lib/generators/vestal_versions/migration}/templates/initializer.rb +0 -0
- data/{generators/vestal_versions → lib/generators/vestal_versions/migration}/templates/migration.rb +4 -3
- data/lib/vestal_versions.rb +39 -12
- data/lib/vestal_versions/changes.rb +43 -47
- data/lib/vestal_versions/conditions.rb +31 -43
- data/lib/vestal_versions/control.rb +162 -138
- data/lib/vestal_versions/creation.rb +67 -59
- data/lib/vestal_versions/deletion.rb +37 -0
- data/lib/vestal_versions/options.rb +6 -10
- data/lib/vestal_versions/reload.rb +7 -14
- data/lib/vestal_versions/reset.rb +15 -19
- data/lib/vestal_versions/reversion.rb +64 -52
- data/lib/vestal_versions/users.rb +36 -39
- data/lib/vestal_versions/version.rb +57 -2
- data/lib/vestal_versions/version_tagging.rb +51 -0
- data/lib/vestal_versions/versioned.rb +14 -17
- data/lib/vestal_versions/versions.rb +22 -7
- data/spec/spec_helper.rb +28 -0
- data/spec/support/models.rb +19 -0
- data/spec/support/schema.rb +25 -0
- data/spec/vestal_versions/changes_spec.rb +134 -0
- data/spec/vestal_versions/conditions_spec.rb +103 -0
- data/spec/vestal_versions/control_spec.rb +120 -0
- data/spec/vestal_versions/creation_spec.rb +90 -0
- data/spec/vestal_versions/deletion_spec.rb +86 -0
- data/spec/vestal_versions/options_spec.rb +45 -0
- data/spec/vestal_versions/reload_spec.rb +18 -0
- data/spec/vestal_versions/reset_spec.rb +111 -0
- data/spec/vestal_versions/reversion_spec.rb +103 -0
- data/spec/vestal_versions/users_spec.rb +21 -0
- data/spec/vestal_versions/version_spec.rb +61 -0
- data/spec/vestal_versions/version_tagging_spec.rb +39 -0
- data/spec/vestal_versions/versioned_spec.rb +16 -0
- data/spec/vestal_versions/versions_spec.rb +176 -0
- data/vestal_versions.gemspec +18 -100
- metadata +153 -102
- data/VERSION +0 -1
- data/generators/vestal_versions/vestal_versions_generator.rb +0 -10
- data/init.rb +0 -1
- data/lib/vestal_versions/configuration.rb +0 -40
- data/lib/vestal_versions/tagging.rb +0 -50
- data/test/changes_test.rb +0 -169
- data/test/conditions_test.rb +0 -137
- data/test/configuration_test.rb +0 -39
- data/test/control_test.rb +0 -152
- data/test/creation_test.rb +0 -110
- data/test/options_test.rb +0 -52
- data/test/reload_test.rb +0 -19
- data/test/reset_test.rb +0 -112
- data/test/reversion_test.rb +0 -68
- data/test/schema.rb +0 -43
- data/test/tagging_test.rb +0 -39
- data/test/test_helper.rb +0 -11
- data/test/users_test.rb +0 -25
- data/test/version_test.rb +0 -43
- data/test/versioned_test.rb +0 -18
- data/test/versions_test.rb +0 -172
@@ -0,0 +1,120 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe VestalVersions::Control do
|
4
|
+
let(:user){ User.create(:name => 'Steve Richert') }
|
5
|
+
let(:other_user){ User.create(:name => 'Michael Rossin') }
|
6
|
+
before do
|
7
|
+
@count = user.versions.count
|
8
|
+
end
|
9
|
+
|
10
|
+
shared_examples_for 'a version preserver' do |method|
|
11
|
+
it 'creates one version with a model update' do
|
12
|
+
user.send(method){ user.update_attribute(:last_name, 'Jobs') }
|
13
|
+
|
14
|
+
user.versions.count.should == @count
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'creates one version with multiple model updates' do
|
18
|
+
user.send(method) do
|
19
|
+
user.update_attribute(:first_name, 'Stephen')
|
20
|
+
user.update_attribute(:last_name, 'Jobs')
|
21
|
+
user.update_attribute(:first_name, 'Steve')
|
22
|
+
end
|
23
|
+
|
24
|
+
user.versions.count.should == @count
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
shared_examples_for 'a version incrementer' do |method|
|
30
|
+
it 'creates one version with a model update' do
|
31
|
+
user.send(method){ user.update_attribute(:last_name, 'Jobs') }
|
32
|
+
|
33
|
+
user.versions.count.should == @count + 1
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'creates one version with multiple model updates' do
|
37
|
+
user.send(method) do
|
38
|
+
user.update_attribute(:first_name, 'Stephen')
|
39
|
+
user.update_attribute(:last_name, 'Jobs')
|
40
|
+
user.update_attribute(:first_name, 'Steve')
|
41
|
+
end
|
42
|
+
|
43
|
+
user.versions.count.should == @count + 1
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
it_should_behave_like 'a version preserver', :skip_version
|
49
|
+
it_should_behave_like 'a version incrementer', :merge_version
|
50
|
+
|
51
|
+
context "when operating on the class level" do
|
52
|
+
before do
|
53
|
+
@count = user.versions.count
|
54
|
+
@other_user_count = other_user.versions.count
|
55
|
+
end
|
56
|
+
it 'skip_version doesn\' create versions on multiple models' do
|
57
|
+
other_user_count = other_user.versions.count
|
58
|
+
|
59
|
+
User.skip_version do
|
60
|
+
user.update_attribute(:first_name, 'Stephen')
|
61
|
+
user.update_attribute(:last_name, 'Jobs')
|
62
|
+
user.update_attribute(:first_name, 'Steve')
|
63
|
+
|
64
|
+
other_user.update_attribute(:first_name, 'Stephen')
|
65
|
+
other_user.update_attribute(:last_name, 'Jobs')
|
66
|
+
other_user.update_attribute(:first_name, 'Steve')
|
67
|
+
end
|
68
|
+
user.versions.count.should == @count
|
69
|
+
other_user.versions.count.should == @other_user_count
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
context 'within a append_version block' do
|
75
|
+
|
76
|
+
context 'when no versions exist' do
|
77
|
+
it_should_behave_like 'a version incrementer', :append_version
|
78
|
+
end
|
79
|
+
|
80
|
+
context 'when versions exist' do
|
81
|
+
let(:last_version){ user.versions.last }
|
82
|
+
|
83
|
+
before do
|
84
|
+
user.update_attribute(:last_name, 'Jobs')
|
85
|
+
user.update_attribute(:last_name, 'Richert')
|
86
|
+
|
87
|
+
@count = user.versions.count
|
88
|
+
end
|
89
|
+
|
90
|
+
it_should_behave_like 'a version preserver', :append_version
|
91
|
+
|
92
|
+
it "updates the last version with one update" do
|
93
|
+
original_id = last_version.id
|
94
|
+
original_attrs = last_version.attributes
|
95
|
+
|
96
|
+
user.append_version{ user.update_attribute(:last_name, 'Jobs') }
|
97
|
+
|
98
|
+
other_last_version = user.versions(true).last
|
99
|
+
other_last_version.id.should == original_id
|
100
|
+
other_last_version.attributes.should_not == original_attrs
|
101
|
+
end
|
102
|
+
|
103
|
+
it "updates the last version with multiple updates" do
|
104
|
+
original_id = last_version.id
|
105
|
+
original_attrs = last_version.attributes
|
106
|
+
|
107
|
+
user.append_version do
|
108
|
+
user.update_attribute(:first_name, 'Stephen')
|
109
|
+
user.update_attribute(:last_name, 'Jobs')
|
110
|
+
user.update_attribute(:first_name, 'Steve')
|
111
|
+
end
|
112
|
+
|
113
|
+
other_last_version = user.versions(true).last
|
114
|
+
other_last_version.id.should == original_id
|
115
|
+
other_last_version.attributes.should_not == original_attrs
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe VestalVersions::Creation do
|
4
|
+
let(:name){ 'Steve Richert' }
|
5
|
+
subject{ User.create(:name => name) }
|
6
|
+
|
7
|
+
context 'the number of versions' do
|
8
|
+
|
9
|
+
its('versions.count'){ should == 0 }
|
10
|
+
|
11
|
+
context 'with :initial_version option' do
|
12
|
+
before do
|
13
|
+
User.prepare_versioned_options(:initial_version => true)
|
14
|
+
end
|
15
|
+
|
16
|
+
its('versions.count'){ should == 1 }
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'does not increase when no changes are made in an update' do
|
20
|
+
expect {
|
21
|
+
subject.update_attribute(:name, name)
|
22
|
+
}.to change{ subject.versions.count }.by(0)
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'does not increase when no changes are made before a save' do
|
26
|
+
expect{ subject.save }.to change{ subject.versions.count }.by(0)
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'increases by one after an update' do
|
30
|
+
expect{
|
31
|
+
subject.update_attribute(:last_name, 'Jobs')
|
32
|
+
}.to change{ subject.versions.count }.by(1)
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'increases multiple times after multiple updates' do
|
36
|
+
expect{
|
37
|
+
subject.update_attribute(:last_name, 'Jobs')
|
38
|
+
subject.update_attribute(:first_name, 'Brian')
|
39
|
+
}.to change{ subject.versions.count }.by(2)
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
context "a created version's changes" do
|
45
|
+
before do
|
46
|
+
subject.update_attribute(:last_name, 'Jobs')
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'does not contain Rails timestamps' do
|
50
|
+
%w(created_at created_on updated_at updated_on).each do |timestamp|
|
51
|
+
subject.versions.last.changes.keys.should_not include(timestamp)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'allows the columns tracked to be restricted via :only' do
|
56
|
+
User.prepare_versioned_options(:only => [:first_name])
|
57
|
+
subject.update_attribute(:name, 'Steven Tyler')
|
58
|
+
|
59
|
+
subject.versions.last.changes.keys.should == ['first_name']
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'allows specific columns to be excluded via :except' do
|
63
|
+
User.prepare_versioned_options(:except => [:first_name])
|
64
|
+
subject.update_attribute(:name, 'Steven Tyler')
|
65
|
+
|
66
|
+
subject.versions.last.changes.keys.should_not include('first_name')
|
67
|
+
end
|
68
|
+
|
69
|
+
it "prefers :only to :except" do
|
70
|
+
User.prepare_versioned_options(:only => [:first_name],
|
71
|
+
:except => [:first_name])
|
72
|
+
subject.update_attribute(:name, 'Steven Tyler')
|
73
|
+
|
74
|
+
subject.versions.last.changes.keys.should == ['first_name']
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
context 'first version' do
|
79
|
+
it 'is number 2 after an update' do
|
80
|
+
subject.update_attribute(:last_name, 'Jobs')
|
81
|
+
subject.versions.first.number.should == 2
|
82
|
+
end
|
83
|
+
|
84
|
+
it "is number 1 if :initial_version is true" do
|
85
|
+
User.prepare_versioned_options(:initial_version => true)
|
86
|
+
subject.versions.first.number.should == 1
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe VestalVersions::Deletion do
|
4
|
+
let(:name){ 'Steve Richert' }
|
5
|
+
subject{ DeletedUser.create(:first_name => 'Steve', :last_name => 'Richert') }
|
6
|
+
|
7
|
+
context "a deleted version's changes" do
|
8
|
+
|
9
|
+
before do
|
10
|
+
subject.update_attribute(:last_name, 'Jobs')
|
11
|
+
end
|
12
|
+
|
13
|
+
it "removes the original record" do
|
14
|
+
subject.destroy
|
15
|
+
|
16
|
+
DeletedUser.find_by_id(subject.id).should be_nil
|
17
|
+
end
|
18
|
+
|
19
|
+
it "creates another version record" do
|
20
|
+
expect{ subject.destroy }.to change{ VestalVersions::Version.count }.by(1)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "creates a version with a tag 'deleted'" do
|
24
|
+
subject.destroy
|
25
|
+
VestalVersions::Version.last.tag.should == 'deleted'
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
context "deleted versions" do
|
31
|
+
let(:last_version){ VestalVersions::Version.last }
|
32
|
+
before do
|
33
|
+
subject.update_attribute(:last_name, 'Jobs')
|
34
|
+
subject.destroy
|
35
|
+
end
|
36
|
+
|
37
|
+
context "restoring a record with a bang" do
|
38
|
+
it "is able to restore the user record" do
|
39
|
+
last_version.restore!
|
40
|
+
|
41
|
+
last_version.versioned.should == subject
|
42
|
+
end
|
43
|
+
|
44
|
+
it "removes the last versioned entry" do
|
45
|
+
expect{
|
46
|
+
last_version.restore!
|
47
|
+
}.to change{ VestalVersions::Version.count }.by(-1)
|
48
|
+
end
|
49
|
+
|
50
|
+
it "works properly even if it's not on the proper version" do
|
51
|
+
another_version = VestalVersions::Version.where(
|
52
|
+
:versioned_id => last_version.versioned_id,
|
53
|
+
:versioned_type => last_version.versioned_type
|
54
|
+
).first
|
55
|
+
|
56
|
+
another_version.should_not == last_version
|
57
|
+
|
58
|
+
another_version.restore!.should == subject
|
59
|
+
end
|
60
|
+
|
61
|
+
it "restores even if the schema has changed" do
|
62
|
+
new_mods = last_version.modifications.merge(:old_column => 'old')
|
63
|
+
last_version.update_attributes(:modifications => new_mods)
|
64
|
+
|
65
|
+
last_version.restore.should == subject
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
context "restoring a record without a save" do
|
70
|
+
it "does not save the DeletedUser when restoring" do
|
71
|
+
last_version.restore.should be_new_record
|
72
|
+
end
|
73
|
+
|
74
|
+
it "restores the user object properly" do
|
75
|
+
last_version.restore.should == subject
|
76
|
+
end
|
77
|
+
|
78
|
+
it "does not decrement the versions table" do
|
79
|
+
expect{
|
80
|
+
last_version.restore
|
81
|
+
}.to change{ VestalVersions::Version.count }.by(0)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe VestalVersions::Options do
|
4
|
+
context 'with explicit configuration' do
|
5
|
+
let(:options){ {:dependent => :destroy} }
|
6
|
+
let(:prepared_options){ User.prepare_versioned_options(options.dup) }
|
7
|
+
|
8
|
+
before do
|
9
|
+
VestalVersions::Version.config.clear
|
10
|
+
VestalVersions::Version.config.class_name = 'MyCustomVersion'
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'has symbolized keys' do
|
14
|
+
User.vestal_versions_options.keys.all?{|k| k.is_a?(Symbol) }
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'combines class-level and global configuration options' do
|
18
|
+
prepared_options.slice(:dependent, :class_name).should == {
|
19
|
+
:dependent => :destroy,
|
20
|
+
:class_name => 'MyCustomVersion'
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
context 'default configuration options' do
|
27
|
+
subject { User.prepare_versioned_options({}) }
|
28
|
+
|
29
|
+
it 'defaults to "VestalVersions::Version" for :class_name' do
|
30
|
+
subject[:class_name].should == 'VestalVersions::Version'
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'defaults to :delete_all for :dependent' do
|
34
|
+
subject[:dependent].should == :delete_all
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'forces the :as option value to :versioned' do
|
38
|
+
subject[:as].should == :versioned
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'defaults to [VestalVersions::Versions] for :extend' do
|
42
|
+
subject[:extend].should == [VestalVersions::Versions]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe VestalVersions::Reload do
|
4
|
+
let(:user){ User.create(:name => 'Steve Richert') }
|
5
|
+
|
6
|
+
before do
|
7
|
+
first_version = user.version
|
8
|
+
user.update_attribute(:last_name, 'Jobs')
|
9
|
+
@last_version = user.version
|
10
|
+
user.revert_to(first_version)
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'resets the version number to the most recent version' do
|
14
|
+
user.version.should_not == @last_version
|
15
|
+
user.reload
|
16
|
+
user.version.should == @last_version
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe VestalVersions::Reset do
|
4
|
+
let(:names){
|
5
|
+
['Steve Richert', 'Stephen Richert', 'Stephen Jobs', 'Steve Jobs']
|
6
|
+
}
|
7
|
+
|
8
|
+
subject{ User.new }
|
9
|
+
let(:versions){ names.map{ |name|
|
10
|
+
subject.update_attribute :name, name
|
11
|
+
subject.version
|
12
|
+
} }
|
13
|
+
|
14
|
+
before do
|
15
|
+
@dependent = User.reflect_on_association(:versions).options[:dependent]
|
16
|
+
end
|
17
|
+
|
18
|
+
after do
|
19
|
+
User.reflect_on_association(:versions).options[:dependent] = @dependent
|
20
|
+
end
|
21
|
+
|
22
|
+
it "properly reverts the model's attributes" do
|
23
|
+
versions.reverse.each_with_index do |version, i|
|
24
|
+
subject.reset_to!(version)
|
25
|
+
subject.name.should == names.reverse[i]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'dissociates all versions after the target' do
|
30
|
+
versions.reverse.each do |version|
|
31
|
+
subject.reset_to!(version)
|
32
|
+
subject.versions(true).after(version).count.should == 0
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context 'with the :dependent option as :delete_all' do
|
37
|
+
before do
|
38
|
+
User.reflect_on_association(:versions).options[:dependent] = :delete_all
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'deletes all versions after the target version' do
|
42
|
+
versions.reverse.each do |version|
|
43
|
+
later_versions = subject.versions.after(version)
|
44
|
+
subject.reset_to!(version)
|
45
|
+
|
46
|
+
later_versions.each do |later_version|
|
47
|
+
expect{
|
48
|
+
later_version.reload
|
49
|
+
}.to raise_error(ActiveRecord::RecordNotFound)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'does not destroy all versions after the target version' do
|
55
|
+
expect {
|
56
|
+
versions.reverse.each do |version|
|
57
|
+
subject.reset_to! version
|
58
|
+
end
|
59
|
+
}.to_not change{ VestalVersions::Version.count }
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
context 'with the :dependent option as :destroy' do
|
64
|
+
before do
|
65
|
+
User.reflect_on_association(:versions).options[:dependent] = :destroy
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'deletes all versions after the target version' do
|
69
|
+
versions.reverse.each do |version|
|
70
|
+
later_versions = subject.versions.after(version)
|
71
|
+
subject.reset_to!(version)
|
72
|
+
|
73
|
+
later_versions.each do |later_version|
|
74
|
+
expect{
|
75
|
+
later_version.reload
|
76
|
+
}.to raise_error(ActiveRecord::RecordNotFound)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'destroys all versions after the target version' do
|
82
|
+
expect {
|
83
|
+
versions.reverse.each do |version|
|
84
|
+
later_versions = subject.versions.after(version)
|
85
|
+
|
86
|
+
subject.reset_to!(version)
|
87
|
+
end
|
88
|
+
}.to change{ VestalVersions::Version.count }.by(-versions.size + 1)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
context 'with the :dependent option as :nullify' do
|
93
|
+
before do
|
94
|
+
User.reflect_on_association(:versions).options[:dependent] = :nullify
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'leaves all versions after the target version' do
|
98
|
+
versions.reverse.each do |version|
|
99
|
+
later_versions = subject.versions.after(version)
|
100
|
+
subject.reset_to!(version)
|
101
|
+
|
102
|
+
later_versions.each do |later_version|
|
103
|
+
expect{
|
104
|
+
later_version.reload
|
105
|
+
}.to_not raise_error
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|