brianjlandau-vestal_versions 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +27 -0
- data/Gemfile +3 -0
- data/LICENSE +20 -0
- data/README.rdoc +196 -0
- data/Rakefile +50 -0
- data/VERSION +1 -0
- data/generators/vestal_versions/templates/initializer.rb +9 -0
- data/generators/vestal_versions/templates/migration.rb +28 -0
- data/generators/vestal_versions/vestal_versions_generator.rb +10 -0
- data/init.rb +1 -0
- data/lib/vestal_versions.rb +104 -0
- data/lib/vestal_versions/associations.rb +67 -0
- data/lib/vestal_versions/changes.rb +125 -0
- data/lib/vestal_versions/conditions.rb +69 -0
- data/lib/vestal_versions/configuration.rb +40 -0
- data/lib/vestal_versions/control.rb +175 -0
- data/lib/vestal_versions/creation.rb +85 -0
- data/lib/vestal_versions/deletion.rb +46 -0
- data/lib/vestal_versions/options.rb +45 -0
- data/lib/vestal_versions/reload.rb +22 -0
- data/lib/vestal_versions/reset.rb +28 -0
- data/lib/vestal_versions/reversion.rb +92 -0
- data/lib/vestal_versions/tagging.rb +54 -0
- data/lib/vestal_versions/users.rb +56 -0
- data/lib/vestal_versions/version.rb +76 -0
- data/lib/vestal_versions/versioned.rb +30 -0
- data/lib/vestal_versions/versions.rb +74 -0
- data/test/associations_test.rb +49 -0
- data/test/changes_test.rb +169 -0
- data/test/conditions_test.rb +137 -0
- data/test/configuration_test.rb +39 -0
- data/test/control_test.rb +152 -0
- data/test/creation_test.rb +110 -0
- data/test/deletion_test.rb +121 -0
- data/test/options_test.rb +52 -0
- data/test/reload_test.rb +19 -0
- data/test/reset_test.rb +112 -0
- data/test/reversion_test.rb +99 -0
- data/test/schema.rb +62 -0
- data/test/tagging_test.rb +39 -0
- data/test/test_helper.rb +12 -0
- data/test/users_test.rb +25 -0
- data/test/version_test.rb +61 -0
- data/test/versioned_test.rb +18 -0
- data/test/versions_test.rb +172 -0
- data/vestal_versions.gemspec +124 -0
- metadata +245 -0
data/test/reload_test.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'test_helper'))
|
2
|
+
|
3
|
+
class ReloadTest < Test::Unit::TestCase
|
4
|
+
context 'Reloading a reverted model' do
|
5
|
+
setup do
|
6
|
+
@user = User.create(:name => 'Steve Richert')
|
7
|
+
first_version = @user.version
|
8
|
+
@user.update_attributes(:last_name => 'Jobs')
|
9
|
+
@last_version = @user.version
|
10
|
+
@user.revert_to(first_version)
|
11
|
+
end
|
12
|
+
|
13
|
+
should 'reset the version number to the most recent version' do
|
14
|
+
assert_not_equal @last_version, @user.version
|
15
|
+
@user.reload
|
16
|
+
assert_equal @last_version, @user.version
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/test/reset_test.rb
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'test_helper'))
|
2
|
+
|
3
|
+
class ResetTest < Test::Unit::TestCase
|
4
|
+
context 'Resetting a model' do
|
5
|
+
setup do
|
6
|
+
@original_dependent = User.reflect_on_association(:versions).options[:dependent]
|
7
|
+
@user, @versions = User.new, []
|
8
|
+
@names = ['Steve Richert', 'Stephen Richert', 'Stephen Jobs', 'Steve Jobs']
|
9
|
+
@names.each do |name|
|
10
|
+
@user.update_attributes(:name => name)
|
11
|
+
@versions << @user.version
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
should "properly revert the model's attributes" do
|
16
|
+
@versions.reverse.each_with_index do |version, i|
|
17
|
+
@user.reset_to!(version)
|
18
|
+
assert_equal @names.reverse[i], @user.name
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
should 'dissociate all versions after the target' do
|
23
|
+
@versions.reverse.each do |version|
|
24
|
+
@user.reset_to!(version)
|
25
|
+
assert_equal 0, @user.versions(true).after(version).count
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context 'with the :dependent option as :delete_all' do
|
30
|
+
setup do
|
31
|
+
User.reflect_on_association(:versions).options[:dependent] = :delete_all
|
32
|
+
end
|
33
|
+
|
34
|
+
should 'delete all versions after the target version' do
|
35
|
+
@versions.reverse.each do |version|
|
36
|
+
later_versions = @user.versions.after(version)
|
37
|
+
@user.reset_to!(version)
|
38
|
+
later_versions.each do |later_version|
|
39
|
+
assert_raise ActiveRecord::RecordNotFound do
|
40
|
+
later_version.reload
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
should 'not destroy all versions after the target version' do
|
47
|
+
VestalVersions::Version.any_instance.stubs(:destroy).raises(RuntimeError)
|
48
|
+
@versions.reverse.each do |version|
|
49
|
+
assert_nothing_raised do
|
50
|
+
@user.reset_to!(version)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
context 'with the :dependent option as :destroy' do
|
57
|
+
setup do
|
58
|
+
User.reflect_on_association(:versions).options[:dependent] = :destroy
|
59
|
+
end
|
60
|
+
|
61
|
+
should 'delete all versions after the target version' do
|
62
|
+
@versions.reverse.each do |version|
|
63
|
+
later_versions = @user.versions.after(version)
|
64
|
+
@user.reset_to!(version)
|
65
|
+
later_versions.each do |later_version|
|
66
|
+
assert_raise ActiveRecord::RecordNotFound do
|
67
|
+
later_version.reload
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
should 'destroy all versions after the target version' do
|
74
|
+
VestalVersions::Version.any_instance.stubs(:destroy).raises(RuntimeError)
|
75
|
+
@versions.reverse.each do |version|
|
76
|
+
later_versions = @user.versions.after(version)
|
77
|
+
if later_versions.empty?
|
78
|
+
assert_nothing_raised do
|
79
|
+
@user.reset_to!(version)
|
80
|
+
end
|
81
|
+
else
|
82
|
+
assert_raise RuntimeError do
|
83
|
+
@user.reset_to!(version)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
context 'with the :dependent option as :nullify' do
|
91
|
+
setup do
|
92
|
+
User.reflect_on_association(:versions).options[:dependent] = :nullify
|
93
|
+
end
|
94
|
+
|
95
|
+
should 'leave all versions after the target version' do
|
96
|
+
@versions.reverse.each do |version|
|
97
|
+
later_versions = @user.versions.after(version)
|
98
|
+
@user.reset_to!(version)
|
99
|
+
later_versions.each do |later_version|
|
100
|
+
assert_nothing_raised do
|
101
|
+
later_version.reload
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
teardown do
|
109
|
+
User.reflect_on_association(:versions).options[:dependent] = @original_dependent
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'test_helper'))
|
2
|
+
|
3
|
+
class ReversionTest < Test::Unit::TestCase
|
4
|
+
context 'A model reversion' do
|
5
|
+
setup do
|
6
|
+
@user, @attributes, @times = User.new, {}, {}
|
7
|
+
names = ['Steve Richert', 'Stephen Richert', 'Stephen Jobs', 'Steve Jobs']
|
8
|
+
time = names.size.hours.ago
|
9
|
+
names.each do |name|
|
10
|
+
@user.update_attributes(:name => name)
|
11
|
+
@attributes[@user.version] = @user.attributes
|
12
|
+
time += 1.hour
|
13
|
+
if last_version = @user.versions.last
|
14
|
+
last_version.update_attributes(:created_at => time)
|
15
|
+
end
|
16
|
+
@times[@user.version] = time
|
17
|
+
end
|
18
|
+
@user.reload.versions.reload
|
19
|
+
@first_version, @last_version = @attributes.keys.min, @attributes.keys.max
|
20
|
+
end
|
21
|
+
|
22
|
+
should 'return the new version number' do
|
23
|
+
new_version = @user.revert_to(@first_version)
|
24
|
+
assert_equal @first_version, new_version
|
25
|
+
end
|
26
|
+
|
27
|
+
should 'change the version number when saved' do
|
28
|
+
current_version = @user.version
|
29
|
+
@user.revert_to!(@first_version)
|
30
|
+
assert_not_equal current_version, @user.version
|
31
|
+
end
|
32
|
+
|
33
|
+
should 'do nothing for a invalid argument' do
|
34
|
+
current_version = @user.version
|
35
|
+
[nil, :bogus, 'bogus', (1..2)].each do |invalid|
|
36
|
+
@user.revert_to(invalid)
|
37
|
+
assert_equal current_version, @user.version
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
should 'be able to target a version number' do
|
42
|
+
@user.revert_to(1)
|
43
|
+
assert_equal 1, @user.version
|
44
|
+
end
|
45
|
+
|
46
|
+
should 'be able to target a date and time' do
|
47
|
+
@times.each do |version, time|
|
48
|
+
@user.revert_to(time + 1.second)
|
49
|
+
assert_equal version, @user.version
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
should 'be able to target a version object' do
|
54
|
+
@user.versions.each do |version|
|
55
|
+
@user.revert_to(version)
|
56
|
+
assert_equal version.number, @user.version
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
should "correctly roll back the model's attributes" do
|
61
|
+
timestamps = %w(created_at created_on updated_at updated_on)
|
62
|
+
@attributes.each do |version, attributes|
|
63
|
+
@user.revert_to!(version)
|
64
|
+
assert_equal attributes.except(*timestamps), @user.attributes.except(*timestamps)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
should "store the reverted_from pointing to the previous version" do
|
69
|
+
@user.revert_to!(1)
|
70
|
+
assert_equal 1, @user.versions.last.reverted_from
|
71
|
+
end
|
72
|
+
|
73
|
+
should "not store the revereted_from for subsequent saves" do
|
74
|
+
@user.revert_to!(1)
|
75
|
+
@user.update_attributes(:name => 'Bill Gates')
|
76
|
+
assert_equal nil, @user.versions.last.reverted_from
|
77
|
+
end
|
78
|
+
should "store the reverted_from pointing to the version it was reverted from when save is called later" do
|
79
|
+
@user.revert_to(1)
|
80
|
+
@user.name = "Reverted"
|
81
|
+
@user.save
|
82
|
+
assert_equal 1, @user.versions.last.reverted_from
|
83
|
+
end
|
84
|
+
should "not store the reverted_from for subsequent saves when the revert_to-save is called later" do
|
85
|
+
@user.revert_to(1)
|
86
|
+
@user.name = "Reverted"
|
87
|
+
@user.save
|
88
|
+
@user.update_attributes(:name => 'Bill Gates')
|
89
|
+
assert_equal nil, @user.versions.last.reverted_from
|
90
|
+
end
|
91
|
+
should "clear the reverted_from if the model is reloaded after a revert_to without a save" do
|
92
|
+
@user.revert_to(1)
|
93
|
+
@user.reload
|
94
|
+
@user.update_attributes(:name => 'Bill Gates')
|
95
|
+
assert_equal nil, @user.versions.last.reverted_from
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
end
|
data/test/schema.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
ActiveRecord::Base.establish_connection(
|
2
|
+
:adapter => defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby' ? 'jdbcsqlite3' : 'sqlite3',
|
3
|
+
:database => File.join(File.dirname(__FILE__), 'test.db')
|
4
|
+
)
|
5
|
+
|
6
|
+
class CreateSchema < ActiveRecord::Migration
|
7
|
+
def self.up
|
8
|
+
create_table :groups, :force => true do |t|
|
9
|
+
t.string :name
|
10
|
+
t.timestamps
|
11
|
+
end
|
12
|
+
|
13
|
+
create_table :users, :force => true do |t|
|
14
|
+
t.string :first_name
|
15
|
+
t.string :last_name
|
16
|
+
t.integer :group_id
|
17
|
+
t.timestamps
|
18
|
+
end
|
19
|
+
|
20
|
+
create_table :versions, :force => true do |t|
|
21
|
+
t.belongs_to :versioned, :polymorphic => true
|
22
|
+
t.belongs_to :user, :polymorphic => true
|
23
|
+
t.string :user_name
|
24
|
+
t.text :modifications
|
25
|
+
t.integer :number
|
26
|
+
t.integer :reverted_from
|
27
|
+
t.string :tag
|
28
|
+
t.timestamps
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
CreateSchema.suppress_messages do
|
34
|
+
CreateSchema.migrate(:up)
|
35
|
+
end
|
36
|
+
|
37
|
+
class Group < ActiveRecord::Base
|
38
|
+
versioned
|
39
|
+
has_many :users, :versioned => true
|
40
|
+
end
|
41
|
+
|
42
|
+
class User < ActiveRecord::Base
|
43
|
+
versioned
|
44
|
+
belongs_to :group, :versioned => true
|
45
|
+
|
46
|
+
def name
|
47
|
+
[first_name, last_name].compact.join(' ')
|
48
|
+
end
|
49
|
+
|
50
|
+
def name=(names)
|
51
|
+
self[:first_name], self[:last_name] = names.split(' ', 2)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class DeletedUser < ActiveRecord::Base
|
56
|
+
set_table_name 'users'
|
57
|
+
versioned :dependent => :tracking
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
class MyCustomVersion < VestalVersions::Version
|
62
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'test_helper'))
|
2
|
+
|
3
|
+
class TaggingTest < Test::Unit::TestCase
|
4
|
+
context 'Tagging a version' do
|
5
|
+
setup do
|
6
|
+
@user = User.create(:name => 'Steve Richert')
|
7
|
+
@user.update_attributes(:last_name => 'Jobs')
|
8
|
+
end
|
9
|
+
|
10
|
+
should "update the version record's tag column" do
|
11
|
+
tag_name = 'TAG'
|
12
|
+
last_version = @user.versions.last
|
13
|
+
assert_not_equal tag_name, last_version.tag
|
14
|
+
@user.tag_version(tag_name)
|
15
|
+
assert_equal tag_name, last_version.reload.tag
|
16
|
+
end
|
17
|
+
|
18
|
+
should 'create a version record for an initial version' do
|
19
|
+
@user.revert_to(1)
|
20
|
+
assert_nil @user.versions.at(1)
|
21
|
+
@user.tag_version('TAG')
|
22
|
+
assert_not_nil @user.versions.at(1)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context 'A tagged version' do
|
27
|
+
setup do
|
28
|
+
user = User.create(:name => 'Steve Richert')
|
29
|
+
user.update_attributes(:last_name => 'Jobs')
|
30
|
+
user.tag_version('TAG')
|
31
|
+
@version = user.versions.last
|
32
|
+
end
|
33
|
+
|
34
|
+
should 'return true for the "tagged?" method' do
|
35
|
+
assert @version.respond_to?(:tagged?)
|
36
|
+
assert_equal true, @version.tagged?
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
$: << File.join(File.dirname(__FILE__), '..', 'lib')
|
2
|
+
$: << File.dirname(__FILE__)
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'test/unit'
|
6
|
+
require 'active_record'
|
7
|
+
require 'shoulda'
|
8
|
+
require 'mocha'
|
9
|
+
# require 'vestal_versions'
|
10
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '../lib/vestal_versions'))
|
11
|
+
require 'schema'
|
12
|
+
begin; require 'redgreen'; rescue LoadError; end
|
data/test/users_test.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'test_helper'))
|
2
|
+
|
3
|
+
class UsersTest < Test::Unit::TestCase
|
4
|
+
context 'The user responsible for an update' do
|
5
|
+
setup do
|
6
|
+
@updated_by = User.create(:name => 'Steve Jobs')
|
7
|
+
@user = User.create(:name => 'Steve Richert')
|
8
|
+
end
|
9
|
+
|
10
|
+
should 'default to nil' do
|
11
|
+
@user.update_attributes(:first_name => 'Stephen')
|
12
|
+
assert_nil @user.versions.last.user
|
13
|
+
end
|
14
|
+
|
15
|
+
should 'accept and return an ActiveRecord user' do
|
16
|
+
@user.update_attributes(:first_name => 'Stephen', :updated_by => @updated_by)
|
17
|
+
assert_equal @updated_by, @user.versions.last.user
|
18
|
+
end
|
19
|
+
|
20
|
+
should 'accept and return a string user name' do
|
21
|
+
@user.update_attributes(:first_name => 'Stephen', :updated_by => @updated_by.name)
|
22
|
+
assert_equal @updated_by.name, @user.versions.last.user
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'test_helper'))
|
2
|
+
|
3
|
+
class VersionTest < Test::Unit::TestCase
|
4
|
+
context 'Versions' do
|
5
|
+
setup do
|
6
|
+
@user = User.create(:name => 'Stephen Richert')
|
7
|
+
@user.update_attributes(:name => 'Steve Jobs')
|
8
|
+
@user.update_attributes(:last_name => 'Richert')
|
9
|
+
@first_version, @last_version = @user.versions.first, @user.versions.last
|
10
|
+
end
|
11
|
+
|
12
|
+
should 'be comparable to another version based on version number' do
|
13
|
+
assert @first_version == @first_version
|
14
|
+
assert @last_version == @last_version
|
15
|
+
assert @first_version != @last_version
|
16
|
+
assert @last_version != @first_version
|
17
|
+
assert @first_version < @last_version
|
18
|
+
assert @last_version > @first_version
|
19
|
+
assert @first_version <= @last_version
|
20
|
+
assert @last_version >= @first_version
|
21
|
+
end
|
22
|
+
|
23
|
+
should "not equal a separate model's version with the same number" do
|
24
|
+
user = User.create(:name => 'Stephen Richert')
|
25
|
+
user.update_attributes(:name => 'Steve Jobs')
|
26
|
+
user.update_attributes(:last_name => 'Richert')
|
27
|
+
first_version, last_version = user.versions.first, user.versions.last
|
28
|
+
assert_not_equal @first_version, first_version
|
29
|
+
assert_not_equal @last_version, last_version
|
30
|
+
end
|
31
|
+
|
32
|
+
should 'default to ordering by number when finding through association' do
|
33
|
+
numbers = @user.versions.map(&:number)
|
34
|
+
assert_equal numbers.sort, numbers
|
35
|
+
end
|
36
|
+
|
37
|
+
should 'return true for the "initial?" method when the version number is 1' do
|
38
|
+
version = @user.versions.build(:number => 1)
|
39
|
+
assert_equal 1, version.number
|
40
|
+
assert_equal true, version.initial?
|
41
|
+
end
|
42
|
+
|
43
|
+
should "return the version number if it is not a revert" do
|
44
|
+
assert_equal @user.version, @user.versions.last.original_number
|
45
|
+
end
|
46
|
+
|
47
|
+
should "return the reverted_version if it is a revert" do
|
48
|
+
@user.revert_to!(1)
|
49
|
+
assert_equal 1, @user.versions.last.original_number
|
50
|
+
end
|
51
|
+
|
52
|
+
should "return the original version if it is a double revert" do
|
53
|
+
@user.revert_to!(2)
|
54
|
+
version = @user.version
|
55
|
+
@user.update_attributes(:last_name => 'Gates')
|
56
|
+
@user.revert_to!(version)
|
57
|
+
assert_equal 2, @user.versions.last.original_number
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'test_helper'))
|
2
|
+
|
3
|
+
class VersionedTest < Test::Unit::TestCase
|
4
|
+
context 'ActiveRecord models' do
|
5
|
+
should 'respond to the "versioned?" method' do
|
6
|
+
assert ActiveRecord::Base.respond_to?(:versioned?)
|
7
|
+
assert User.respond_to?(:versioned?)
|
8
|
+
end
|
9
|
+
|
10
|
+
should 'return true for the "versioned?" method if the model is versioned' do
|
11
|
+
assert_equal true, User.versioned?
|
12
|
+
end
|
13
|
+
|
14
|
+
should 'return false for the "versioned?" method if the model is not versioned' do
|
15
|
+
assert_equal false, ActiveRecord::Base.versioned?
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|