mil_vestal_versions 1.2.6
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 +7 -0
- data/.gitignore +19 -0
- data/.travis.yml +9 -0
- data/CHANGELOG +25 -0
- data/Gemfile +8 -0
- data/LICENSE +20 -0
- data/README.rdoc +200 -0
- data/Rakefile +6 -0
- data/gemfiles/activerecord_3_0.gemfile +9 -0
- data/gemfiles/activerecord_3_1.gemfile +9 -0
- data/gemfiles/activerecord_3_2.gemfile +9 -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 +121 -0
- data/lib/vestal_versions/conditions.rb +57 -0
- data/lib/vestal_versions/control.rb +199 -0
- data/lib/vestal_versions/creation.rb +93 -0
- data/lib/vestal_versions/deletion.rb +37 -0
- data/lib/vestal_versions/options.rb +41 -0
- data/lib/vestal_versions/reload.rb +16 -0
- data/lib/vestal_versions/reset.rb +24 -0
- data/lib/vestal_versions/reversion.rb +81 -0
- data/lib/vestal_versions/users.rb +54 -0
- data/lib/vestal_versions/version.rb +81 -0
- data/lib/vestal_versions/version_tagging.rb +49 -0
- data/lib/vestal_versions/versioned.rb +27 -0
- data/lib/vestal_versions/versions.rb +74 -0
- data/mil_vestal_versions.gemspec +19 -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
- data/vestal_versions.gemspec +19 -0
- metadata +139 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
Gem::Specification.new do |gem|
|
|
4
|
+
gem.name = 'mil_vestal_versions'
|
|
5
|
+
gem.version = '1.2.6'
|
|
6
|
+
|
|
7
|
+
gem.authors = ['Steve Richert', "James O'Kelly"]
|
|
8
|
+
gem.email = ['steve.richert@gmail.com', 'dreamr.okelly@gmail.com']
|
|
9
|
+
gem.description = "Keep a DRY history of your ActiveRecord models' changes"
|
|
10
|
+
gem.summary = gem.description
|
|
11
|
+
gem.homepage = 'http://github.com/laserlemon/vestal_versions'
|
|
12
|
+
|
|
13
|
+
gem.add_dependency 'activerecord', '~> 3.0'
|
|
14
|
+
gem.add_dependency 'activesupport', '~> 3.0'
|
|
15
|
+
|
|
16
|
+
gem.files = `git ls-files`.split($\)
|
|
17
|
+
gem.test_files = gem.files.grep(/^spec/)
|
|
18
|
+
gem.require_paths = ['lib']
|
|
19
|
+
end
|
data/spec/spec_helper.rb
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
require 'bundler'
|
|
2
|
+
Bundler.require
|
|
3
|
+
require 'rspec/core'
|
|
4
|
+
|
|
5
|
+
RSpec.configure do |c|
|
|
6
|
+
c.before(:suite) do
|
|
7
|
+
CreateSchema.suppress_messages{ CreateSchema.migrate(:up) }
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
c.after(:suite) do
|
|
11
|
+
FileUtils.rm_rf(File.expand_path('../test.db', __FILE__))
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
c.after(:each) do
|
|
15
|
+
VestalVersions::Version.config.clear
|
|
16
|
+
User.prepare_versioned_options({})
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
Dir[File.expand_path('../support/*.rb', __FILE__)].each{|f| require f }
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
class User < ActiveRecord::Base
|
|
2
|
+
versioned
|
|
3
|
+
|
|
4
|
+
def name
|
|
5
|
+
[first_name, last_name].compact.join(' ')
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def name= names
|
|
9
|
+
self[:first_name], self[:last_name] = names.split(' ', 2)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
class DeletedUser < ActiveRecord::Base
|
|
14
|
+
self.table_name = 'users'
|
|
15
|
+
versioned :dependent => :tracking
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class MyCustomVersion < VestalVersions::Version
|
|
19
|
+
end
|
|
@@ -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
|