versioned 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/Rakefile +2 -2
- data/VERSION +1 -1
- data/lib/version.rb +16 -0
- data/lib/versioned.rb +67 -5
- data/test/lock_test.rb +91 -0
- data/test/schema.rb +35 -1
- data/test/specified_version_key_test.rb +50 -0
- data/test/test_helper.rb +2 -0
- data/versioned.gemspec +9 -4
- metadata +8 -2
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
pkg
|
data/Rakefile
CHANGED
@@ -11,7 +11,7 @@ begin
|
|
11
11
|
g.description = %(Versioning for MongoMapper)
|
12
12
|
g.email = 'signalstatic@gmail.com'
|
13
13
|
g.homepage = 'http://github.com/twoism/versioned'
|
14
|
-
g.authors = %w(twoism toastyapps jacqui)
|
14
|
+
g.authors = %w(twoism toastyapps jacqui mrkurt)
|
15
15
|
end
|
16
16
|
Jeweler::GemcutterTasks.new
|
17
17
|
rescue LoadError
|
@@ -23,4 +23,4 @@ Rake::TestTask.new do |t|
|
|
23
23
|
t.pattern = 'test/**/*_test.rb'
|
24
24
|
end
|
25
25
|
|
26
|
-
task :default => :test
|
26
|
+
task :default => :test
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.2.0
|
data/lib/version.rb
CHANGED
@@ -17,4 +17,20 @@ class Version
|
|
17
17
|
def <=>(other)
|
18
18
|
number <=> other.number
|
19
19
|
end
|
20
|
+
|
21
|
+
def previous
|
22
|
+
find_related(:first, :number => {:$lt => number}, :order => 'number.desc')
|
23
|
+
end
|
24
|
+
|
25
|
+
def next
|
26
|
+
find_related(:first, :number => {:$gt => number}, :order => 'number.asc')
|
27
|
+
end
|
28
|
+
|
29
|
+
protected
|
30
|
+
|
31
|
+
def find_related(*args)
|
32
|
+
options = args.extract_options!
|
33
|
+
params = options.merge(:versioned_id => versioned_id, :versioned_type => versioned_type)
|
34
|
+
self.class.find(args.first, params)
|
35
|
+
end
|
20
36
|
end
|
data/lib/versioned.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'version'
|
2
2
|
|
3
3
|
module Versioned
|
4
|
+
class StaleDocumentError < MongoMapper::MongoMapperError; end
|
4
5
|
def self.included(base)
|
5
6
|
base.extend ClassMethods
|
6
7
|
base.class_eval do
|
@@ -8,13 +9,67 @@ module Versioned
|
|
8
9
|
end
|
9
10
|
end
|
10
11
|
|
12
|
+
module LockingInstanceMethods
|
13
|
+
private
|
14
|
+
#new? isn't working
|
15
|
+
def is_new_document?
|
16
|
+
(read_attribute(self.version_lock_key).blank? && changes[self.version_lock_key.to_s].blank?) ||
|
17
|
+
(changes[self.version_lock_key.to_s] && changes[self.version_lock_key.to_s].first.blank?)
|
18
|
+
end
|
19
|
+
def prep_lock_version
|
20
|
+
old = read_attribute(self.version_lock_key)
|
21
|
+
if !is_new_document? || old.blank?
|
22
|
+
v = (Time.now.to_f * 1000).ceil.to_s
|
23
|
+
write_attribute self.version_lock_key, v
|
24
|
+
end
|
25
|
+
|
26
|
+
old
|
27
|
+
end
|
28
|
+
|
29
|
+
def save_to_collection(options = {})
|
30
|
+
current_version = prep_lock_version
|
31
|
+
if is_new_document?
|
32
|
+
collection.insert(to_mongo, :safe => true)
|
33
|
+
else
|
34
|
+
selector = { :_id => read_attribute(:_id), self.version_lock_key => current_version }
|
35
|
+
#can't upsert, safe must be true for this to work
|
36
|
+
result = collection.update(selector, to_mongo, :upsert => false, :safe => true)
|
37
|
+
|
38
|
+
if result.is_a?(Array) && result[0][0]['updatedExisting'] == false
|
39
|
+
write_attribute self.version_lock_key, current_version
|
40
|
+
raise StaleDocumentError.new
|
41
|
+
elsif !result.is_a?(Array)
|
42
|
+
#wtf?
|
43
|
+
write_attribute self.version_lock_key, current_version
|
44
|
+
raise "Unexpected result from mongo"
|
45
|
+
end
|
46
|
+
|
47
|
+
selector[:_id]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
11
51
|
module ClassMethods
|
52
|
+
def locking!(options = {})
|
53
|
+
include(LockingInstanceMethods)
|
54
|
+
class_inheritable_accessor :version_lock_key
|
55
|
+
self.version_lock_key = options[:key] || :lock_version
|
56
|
+
key self.version_lock_key, Integer
|
57
|
+
|
58
|
+
if self.respond_to?(:version_use_key)
|
59
|
+
self.version_use_key = self.version_lock_key
|
60
|
+
(self.version_except_columns ||= []) << self.version_lock_key.to_s #don't version the lock key
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
12
64
|
def versioned(options = {})
|
13
65
|
class_inheritable_accessor :version_only_columns
|
14
66
|
self.version_only_columns = Array(options[:only]).map(&:to_s).uniq if options[:only]
|
15
67
|
class_inheritable_accessor :version_except_columns
|
16
68
|
self.version_except_columns = Array(options[:except]).map(&:to_s).uniq if options[:except]
|
17
69
|
|
70
|
+
class_inheritable_accessor :version_use_key
|
71
|
+
self.version_use_key = options[:use_key]
|
72
|
+
|
18
73
|
many :versions, :as => :versioned, :order => 'number ASC', :dependent => :delete_all do
|
19
74
|
def between(from, to)
|
20
75
|
from_number, to_number = number_at(from), number_at(to)
|
@@ -56,6 +111,7 @@ module Versioned
|
|
56
111
|
include InstanceMethods
|
57
112
|
alias_method_chain :reload, :versions
|
58
113
|
end
|
114
|
+
|
59
115
|
end
|
60
116
|
|
61
117
|
module InstanceMethods
|
@@ -81,12 +137,18 @@ module Versioned
|
|
81
137
|
@version = new_version
|
82
138
|
end
|
83
139
|
|
140
|
+
def next_version_number(initial = false)
|
141
|
+
v = read_attribute self.version_use_key unless self.version_use_key.nil?
|
142
|
+
v = 1 if v.nil? && initial
|
143
|
+
v = last_version + 1 if v.nil? && !initial
|
144
|
+
v
|
145
|
+
end
|
84
146
|
def create_initial_version
|
85
|
-
versions.create(:changes => nil, :number =>
|
147
|
+
versions.create(:changes => nil, :number => next_version_number(true))
|
86
148
|
end
|
87
149
|
|
88
150
|
def create_version
|
89
|
-
versions << Version.create(:changes => changes.slice(*versioned_columns), :number =>
|
151
|
+
versions << Version.create(:changes => changes.slice(*versioned_columns), :number => next_version_number)
|
90
152
|
reset_version
|
91
153
|
end
|
92
154
|
|
@@ -128,7 +190,7 @@ module Versioned
|
|
128
190
|
end
|
129
191
|
|
130
192
|
def revert
|
131
|
-
revert_to self.version
|
193
|
+
revert_to self.versions.at(self.version).previous
|
132
194
|
end
|
133
195
|
|
134
196
|
def retrieve_version n
|
@@ -156,8 +218,8 @@ module Versioned
|
|
156
218
|
end
|
157
219
|
|
158
220
|
def latest_changes
|
159
|
-
return {} if version.nil?
|
221
|
+
return {} if version.nil?
|
160
222
|
versions.at(version).changes
|
161
223
|
end
|
162
224
|
end
|
163
|
-
end
|
225
|
+
end
|
data/test/lock_test.rb
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class LockTest < Test::Unit::TestCase
|
4
|
+
context 'An unversioned model with locks' do
|
5
|
+
setup do
|
6
|
+
@user = UnversionedLockableUser.create(:name => 'Kurt')
|
7
|
+
end
|
8
|
+
|
9
|
+
should 'have a lock_version field' do
|
10
|
+
assert_not_nil @user.lock_version
|
11
|
+
end
|
12
|
+
|
13
|
+
should 'save just fine when no conflicts' do
|
14
|
+
@user.name = 'Bob'
|
15
|
+
assert @user.save
|
16
|
+
end
|
17
|
+
|
18
|
+
should 'save just fine when given the proper lock version' do
|
19
|
+
u = UnversionedLockableUser.find(@user.id)
|
20
|
+
@user.update_attributes(:name => 'Bill')
|
21
|
+
|
22
|
+
assert u.update_attributes(:name => 'Jose', :lock_version => @user.lock_version)
|
23
|
+
end
|
24
|
+
|
25
|
+
should 'not save when given the wrong version' do
|
26
|
+
assert_raise Versioned::StaleDocumentError do
|
27
|
+
@user.update_attributes(:name => 'Bob', :lock_version => 1111)
|
28
|
+
end
|
29
|
+
|
30
|
+
# lock_version should match what we passed in
|
31
|
+
assert_equal 1111, @user.lock_version
|
32
|
+
end
|
33
|
+
end
|
34
|
+
context 'A versioned model with locks' do
|
35
|
+
setup do
|
36
|
+
@user = LockableUser.create(:name => 'Kurt', :required_field => 'woo!')
|
37
|
+
end
|
38
|
+
|
39
|
+
should 'have a lock_version field' do
|
40
|
+
assert_not_nil @user.lock_version
|
41
|
+
assert_equal @user.version, @user.lock_version
|
42
|
+
end
|
43
|
+
|
44
|
+
should 'save just fine when no conflicts' do
|
45
|
+
@user.name = 'Bob'
|
46
|
+
assert @user.save
|
47
|
+
end
|
48
|
+
|
49
|
+
should 'save just fine when given the proper lock version' do
|
50
|
+
u = LockableUser.find(@user.id)
|
51
|
+
@user.update_attributes(:name => 'Bill')
|
52
|
+
|
53
|
+
assert u.update_attributes(:name => 'Jose', :lock_version => @user.lock_version)
|
54
|
+
end
|
55
|
+
|
56
|
+
should 'not save when given the wrong version' do
|
57
|
+
assert_raise Versioned::StaleDocumentError do
|
58
|
+
@user.update_attributes(:name => 'Bob', :lock_version => 1111)
|
59
|
+
end
|
60
|
+
|
61
|
+
# lock_version should match what we passed in
|
62
|
+
assert_equal 1111, @user.lock_version
|
63
|
+
end
|
64
|
+
|
65
|
+
should 'be revertable with the lock version' do
|
66
|
+
v = @user.lock_version
|
67
|
+
name = @user.name
|
68
|
+
@user.update_attributes(:name => "Schlub")
|
69
|
+
|
70
|
+
assert_not_equal v, @user.lock_version
|
71
|
+
|
72
|
+
@user.revert_to(v)
|
73
|
+
|
74
|
+
assert_equal name, @user.name
|
75
|
+
assert_not_equal v, @user.lock_version #shouldn't have reverted version
|
76
|
+
end
|
77
|
+
|
78
|
+
should 'accept a specified version on create' do
|
79
|
+
u = LockableUser.create(:name => 'Burt', :required_field => 'woo!', :lock_version => 1111)
|
80
|
+
assert_equal 1111, u.lock_version
|
81
|
+
end
|
82
|
+
|
83
|
+
should "have same lock_version when validation fails" do
|
84
|
+
@user.required_field = nil
|
85
|
+
v = @user.lock_version
|
86
|
+
result = @user.save
|
87
|
+
assert !result
|
88
|
+
assert_equal v, @user.lock_version
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
data/test/schema.rb
CHANGED
@@ -17,5 +17,39 @@ class User
|
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
20
|
+
class Loser
|
21
|
+
include MongoMapper::Document
|
22
|
+
extend Versioned::ClassMethods
|
23
|
+
versioned :use_key => :revision
|
24
|
+
key :revision, Integer
|
25
|
+
key :name, String
|
26
|
+
timestamps!
|
27
|
+
|
28
|
+
before_save :set_revision
|
29
|
+
|
30
|
+
def set_revision
|
31
|
+
write_attribute :revision, (Time.now.to_f * 1000).ceil
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class LockableUser
|
36
|
+
include MongoMapper::Document
|
37
|
+
include Versioned
|
38
|
+
locking!
|
39
|
+
|
40
|
+
key :name, String
|
41
|
+
key :required_field, String
|
42
|
+
validates_presence_of :required_field
|
43
|
+
end
|
44
|
+
|
45
|
+
class UnversionedLockableUser
|
46
|
+
include MongoMapper::Document
|
47
|
+
extend Versioned::ClassMethods
|
48
|
+
locking!
|
49
|
+
|
50
|
+
key :name, String
|
51
|
+
end
|
52
|
+
|
20
53
|
User.destroy_all
|
21
|
-
|
54
|
+
Loser.destroy_all
|
55
|
+
Version.destroy_all
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class SpecifiedVersionKey < Test::Unit::TestCase
|
4
|
+
context 'A model with a specified version key' do
|
5
|
+
setup do
|
6
|
+
@name = "Blah"
|
7
|
+
@loser = Loser.create(:name => @name)
|
8
|
+
@version = @loser.version
|
9
|
+
end
|
10
|
+
should 'be used for the initial version' do
|
11
|
+
assert_equal @loser.revision, @loser.version
|
12
|
+
assert_equal @loser.revision, @loser.versions.first.number
|
13
|
+
end
|
14
|
+
|
15
|
+
context 'after an update' do
|
16
|
+
setup do
|
17
|
+
@initial_version = @loser.version
|
18
|
+
@initial_count = @loser.versions.count
|
19
|
+
@initial_name = @loser.name
|
20
|
+
@name = 'Blip'
|
21
|
+
@loser.name = @name
|
22
|
+
@loser.save
|
23
|
+
@version = @loser.version
|
24
|
+
@count = @loser.versions.count
|
25
|
+
end
|
26
|
+
|
27
|
+
should 'have a different version number' do
|
28
|
+
assert_not_equal @initial_version, @loser.version
|
29
|
+
end
|
30
|
+
|
31
|
+
should 'still be using the specified key' do
|
32
|
+
assert_equal @loser.revision, @loser.version
|
33
|
+
assert_equal @loser.revision, @loser.versions.last.number
|
34
|
+
end
|
35
|
+
|
36
|
+
should 'version count should have increased by one' do
|
37
|
+
assert_equal @initial_count + 1, @count
|
38
|
+
end
|
39
|
+
|
40
|
+
should 'revert properly' do
|
41
|
+
@loser.revert
|
42
|
+
@loser.save!
|
43
|
+
assert_equal @initial_name, @loser.name
|
44
|
+
assert_equal @count + 1, @loser.versions.count
|
45
|
+
assert_not_equal @version, @loser.version
|
46
|
+
assert_not_equal @initial_version, @loser.version
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
data/test/test_helper.rb
CHANGED
data/versioned.gemspec
CHANGED
@@ -5,18 +5,19 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{versioned}
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.2.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
-
s.authors = ["twoism", "toastyapps", "jacqui"]
|
12
|
-
s.date = %q{
|
11
|
+
s.authors = ["twoism", "toastyapps", "jacqui", "mrkurt"]
|
12
|
+
s.date = %q{2010-01-21}
|
13
13
|
s.description = %q{Versioning for MongoMapper}
|
14
14
|
s.email = %q{signalstatic@gmail.com}
|
15
15
|
s.extra_rdoc_files = [
|
16
16
|
"README.rdoc"
|
17
17
|
]
|
18
18
|
s.files = [
|
19
|
-
"
|
19
|
+
".gitignore",
|
20
|
+
"README.rdoc",
|
20
21
|
"Rakefile",
|
21
22
|
"VERSION",
|
22
23
|
"lib/version.rb",
|
@@ -27,8 +28,10 @@ Gem::Specification.new do |s|
|
|
27
28
|
"test/comparable_test.rb",
|
28
29
|
"test/creation_test.rb",
|
29
30
|
"test/latest_changes_test.rb",
|
31
|
+
"test/lock_test.rb",
|
30
32
|
"test/revert_test.rb",
|
31
33
|
"test/schema.rb",
|
34
|
+
"test/specified_version_key_test.rb",
|
32
35
|
"test/test_helper.rb",
|
33
36
|
"versioned.gemspec"
|
34
37
|
]
|
@@ -43,8 +46,10 @@ Gem::Specification.new do |s|
|
|
43
46
|
"test/comparable_test.rb",
|
44
47
|
"test/creation_test.rb",
|
45
48
|
"test/latest_changes_test.rb",
|
49
|
+
"test/lock_test.rb",
|
46
50
|
"test/revert_test.rb",
|
47
51
|
"test/schema.rb",
|
52
|
+
"test/specified_version_key_test.rb",
|
48
53
|
"test/test_helper.rb"
|
49
54
|
]
|
50
55
|
|
metadata
CHANGED
@@ -1,17 +1,18 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: versioned
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- twoism
|
8
8
|
- toastyapps
|
9
9
|
- jacqui
|
10
|
+
- mrkurt
|
10
11
|
autorequire:
|
11
12
|
bindir: bin
|
12
13
|
cert_chain: []
|
13
14
|
|
14
|
-
date:
|
15
|
+
date: 2010-01-21 00:00:00 -05:00
|
15
16
|
default_executable:
|
16
17
|
dependencies: []
|
17
18
|
|
@@ -24,6 +25,7 @@ extensions: []
|
|
24
25
|
extra_rdoc_files:
|
25
26
|
- README.rdoc
|
26
27
|
files:
|
28
|
+
- .gitignore
|
27
29
|
- README.rdoc
|
28
30
|
- Rakefile
|
29
31
|
- VERSION
|
@@ -35,8 +37,10 @@ files:
|
|
35
37
|
- test/comparable_test.rb
|
36
38
|
- test/creation_test.rb
|
37
39
|
- test/latest_changes_test.rb
|
40
|
+
- test/lock_test.rb
|
38
41
|
- test/revert_test.rb
|
39
42
|
- test/schema.rb
|
43
|
+
- test/specified_version_key_test.rb
|
40
44
|
- test/test_helper.rb
|
41
45
|
- versioned.gemspec
|
42
46
|
has_rdoc: true
|
@@ -73,6 +77,8 @@ test_files:
|
|
73
77
|
- test/comparable_test.rb
|
74
78
|
- test/creation_test.rb
|
75
79
|
- test/latest_changes_test.rb
|
80
|
+
- test/lock_test.rb
|
76
81
|
- test/revert_test.rb
|
77
82
|
- test/schema.rb
|
83
|
+
- test/specified_version_key_test.rb
|
78
84
|
- test/test_helper.rb
|