versioned 0.1.0 → 0.2.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 +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
|