set_vestal_versions 1.2.2
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/LICENSE +20 -0
- data/README.rdoc +196 -0
- data/lib/generators/vestal_versions.rb +11 -0
- data/lib/generators/vestal_versions/migration/migration_generator.rb +17 -0
- data/lib/generators/vestal_versions/migration/templates/initializer.rb +9 -0
- data/lib/generators/vestal_versions/migration/templates/migration.rb +28 -0
- data/lib/vestal_versions.rb +126 -0
- data/lib/vestal_versions/changes.rb +122 -0
- data/lib/vestal_versions/conditions.rb +57 -0
- data/lib/vestal_versions/control.rb +200 -0
- data/lib/vestal_versions/creation.rb +93 -0
- data/lib/vestal_versions/deletion.rb +39 -0
- data/lib/vestal_versions/options.rb +41 -0
- data/lib/vestal_versions/reload.rb +17 -0
- data/lib/vestal_versions/reset.rb +24 -0
- data/lib/vestal_versions/reversion.rb +82 -0
- data/lib/vestal_versions/users.rb +55 -0
- data/lib/vestal_versions/version.rb +80 -0
- data/lib/vestal_versions/version_num.rb +3 -0
- data/lib/vestal_versions/version_tagging.rb +50 -0
- data/lib/vestal_versions/versioned.rb +27 -0
- data/lib/vestal_versions/versions.rb +74 -0
- data/spec/spec_helper.rb +20 -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
- metadata +165 -0
@@ -0,0 +1,25 @@
|
|
1
|
+
ActiveRecord::Base.establish_connection(
|
2
|
+
:adapter => 'sqlite3',
|
3
|
+
:database => File.expand_path('../../test.db', __FILE__)
|
4
|
+
)
|
5
|
+
|
6
|
+
class CreateSchema < ActiveRecord::Migration
|
7
|
+
def self.up
|
8
|
+
create_table :users, :force => true do |t|
|
9
|
+
t.string :first_name
|
10
|
+
t.string :last_name
|
11
|
+
t.timestamps
|
12
|
+
end
|
13
|
+
|
14
|
+
create_table :versions, :force => true do |t|
|
15
|
+
t.belongs_to :versioned, :polymorphic => true
|
16
|
+
t.belongs_to :user, :polymorphic => true
|
17
|
+
t.string :user_name
|
18
|
+
t.text :modifications
|
19
|
+
t.integer :number
|
20
|
+
t.integer :reverted_from
|
21
|
+
t.string :tag
|
22
|
+
t.timestamps
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe VestalVersions::Changes do
|
4
|
+
context "a version's changes" do
|
5
|
+
let(:user){ User.create(:name => 'Steve Richert') }
|
6
|
+
subject{ user.versions.last.changes }
|
7
|
+
|
8
|
+
before do
|
9
|
+
user.update_attribute(:last_name, 'Jobs')
|
10
|
+
end
|
11
|
+
|
12
|
+
it { should be_a(Hash) }
|
13
|
+
it { should_not be_empty }
|
14
|
+
|
15
|
+
it 'has string keys' do
|
16
|
+
subject.keys.each{ |key| key.should be_a(String) }
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'has two-element array values' do
|
20
|
+
subject.values.each do |key|
|
21
|
+
key.should be_a(Array)
|
22
|
+
key.size.should == 2
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'has unique-element values' do
|
27
|
+
subject.values.each{ |v| v.uniq.should == v }
|
28
|
+
end
|
29
|
+
|
30
|
+
it "equals the model's changes" do
|
31
|
+
user.first_name = 'Stephen'
|
32
|
+
model_changes = user.changes
|
33
|
+
user.save
|
34
|
+
changes = user.versions.last.changes
|
35
|
+
|
36
|
+
model_changes.should == changes
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
context 'a hash of changes' do
|
41
|
+
let(:changes){ {'first_name' => ['Steve', 'Stephen']} }
|
42
|
+
let(:other){ {'first_name' => ['Catie', 'Catherine']} }
|
43
|
+
|
44
|
+
it 'properly appends other changes' do
|
45
|
+
expected = {'first_name' => ['Steve', 'Catherine']}
|
46
|
+
|
47
|
+
changes.append_changes(other).should == expected
|
48
|
+
|
49
|
+
changes.append_changes!(other)
|
50
|
+
changes.should == expected
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'properly prepends other changes' do
|
54
|
+
expected = {'first_name' => ['Catie', 'Stephen']}
|
55
|
+
|
56
|
+
changes.prepend_changes(other).should == expected
|
57
|
+
|
58
|
+
changes.prepend_changes!(other)
|
59
|
+
changes.should == expected
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'is reversible' do
|
63
|
+
expected = {'first_name' => ['Stephen', 'Steve']}
|
64
|
+
|
65
|
+
changes.reverse_changes.should == expected
|
66
|
+
|
67
|
+
changes.reverse_changes!
|
68
|
+
changes.should == expected
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
context 'the changes between two versions' do
|
73
|
+
let(:name){ 'Steve Richert' }
|
74
|
+
let(:user){ User.create(:name => name) } # 1
|
75
|
+
let(:version){ user.version }
|
76
|
+
|
77
|
+
before do
|
78
|
+
user.update_attribute(:last_name, 'Jobs') # 2
|
79
|
+
user.update_attribute(:first_name, 'Stephen') # 3
|
80
|
+
user.update_attribute(:last_name, 'Richert') # 4
|
81
|
+
user.update_attribute(:name, name) # 5
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'is a hash' do
|
85
|
+
1.upto(version) do |i|
|
86
|
+
1.upto(version) do |j|
|
87
|
+
user.changes_between(i, j).should be_a(Hash)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'has string keys' do
|
93
|
+
1.upto(version) do |i|
|
94
|
+
1.upto(version) do |j|
|
95
|
+
user.changes_between(i, j).keys.each{ |key| key.should be_a(String) }
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'has two-element arrays with unique values' do
|
101
|
+
1.upto(version) do |i|
|
102
|
+
1.upto(version) do |j|
|
103
|
+
user.changes_between(i, j).values.each do |value|
|
104
|
+
value.should be_a(Array)
|
105
|
+
value.size.should == 2
|
106
|
+
value.uniq.should == value
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'is empty between identical versions' do
|
113
|
+
user.changes_between(1, version).should be_empty
|
114
|
+
user.changes_between(version, 1).should be_empty
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'is should reverse with direction' do
|
118
|
+
1.upto(version) do |i|
|
119
|
+
i.upto(version) do |j|
|
120
|
+
up = user.changes_between(i, j)
|
121
|
+
down = user.changes_between(j, i)
|
122
|
+
up.should == down.reverse_changes
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
it 'is empty with invalid arguments' do
|
128
|
+
1.upto(version) do |i|
|
129
|
+
user.changes_between(i, nil).should be_blank
|
130
|
+
user.changes_between(nil, i).should be_blank
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe VestalVersions::Conditions do
|
4
|
+
shared_examples_for 'a conditional option' do |option|
|
5
|
+
before do
|
6
|
+
User.class_eval do
|
7
|
+
def true; true; end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'is an array' do
|
12
|
+
User.vestal_versions_options[option].should be_a(Array)
|
13
|
+
User.prepare_versioned_options(option => :true)
|
14
|
+
User.vestal_versions_options[option].should be_a(Array)
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'has proc values' do
|
18
|
+
User.prepare_versioned_options(option => :true)
|
19
|
+
User.vestal_versions_options[option].each{|i| i.should be_a(Proc) }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
it_should_behave_like 'a conditional option', :if
|
24
|
+
it_should_behave_like 'a conditional option', :unless
|
25
|
+
|
26
|
+
context 'a new version' do
|
27
|
+
subject{ User.create(:name => 'Steve Richert') }
|
28
|
+
let(:count){ subject.versions.count }
|
29
|
+
|
30
|
+
before do
|
31
|
+
User.class_eval do
|
32
|
+
def true; true; end
|
33
|
+
def false; false; end
|
34
|
+
end
|
35
|
+
count # memoize this value
|
36
|
+
end
|
37
|
+
|
38
|
+
after do
|
39
|
+
User.prepare_versioned_options(:if => [], :unless => [])
|
40
|
+
end
|
41
|
+
|
42
|
+
context 'with :if conditions' do
|
43
|
+
context 'that pass' do
|
44
|
+
before do
|
45
|
+
User.prepare_versioned_options(:if => [:true])
|
46
|
+
subject.update_attribute(:last_name, 'Jobs')
|
47
|
+
end
|
48
|
+
|
49
|
+
its('versions.count'){ should == count + 1 }
|
50
|
+
end
|
51
|
+
|
52
|
+
context 'that fail' do
|
53
|
+
before do
|
54
|
+
User.prepare_versioned_options(:if => [:false])
|
55
|
+
subject.update_attribute(:last_name, 'Jobs')
|
56
|
+
end
|
57
|
+
|
58
|
+
its('versions.count'){ should == count }
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
context 'with :unless conditions' do
|
63
|
+
context 'that pass' do
|
64
|
+
before do
|
65
|
+
User.prepare_versioned_options(:unless => [:true])
|
66
|
+
subject.update_attribute(:last_name, 'Jobs')
|
67
|
+
end
|
68
|
+
|
69
|
+
its('versions.count'){ should == count }
|
70
|
+
end
|
71
|
+
|
72
|
+
context 'that fail' do
|
73
|
+
before do
|
74
|
+
User.prepare_versioned_options(:unless => [:false])
|
75
|
+
subject.update_attribute(:last_name, 'Jobs')
|
76
|
+
end
|
77
|
+
|
78
|
+
its('versions.count'){ should == count + 1 }
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
context 'with :if and :unless conditions' do
|
83
|
+
context 'that pass' do
|
84
|
+
before do
|
85
|
+
User.prepare_versioned_options(:if => [:true], :unless => [:true])
|
86
|
+
subject.update_attribute(:last_name, 'Jobs')
|
87
|
+
end
|
88
|
+
|
89
|
+
its('versions.count'){ should == count }
|
90
|
+
end
|
91
|
+
|
92
|
+
context 'that fail' do
|
93
|
+
before do
|
94
|
+
User.prepare_versioned_options(:if => [:false], :unless => [:false])
|
95
|
+
subject.update_attribute(:last_name, 'Jobs')
|
96
|
+
end
|
97
|
+
|
98
|
+
its('versions.count'){ should == count }
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
end
|
@@ -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
|