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 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
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 => 1)
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 => (last_version + 1))
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 -1
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? || version == 1
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
- Version.destroy_all
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
@@ -8,3 +8,5 @@ require 'mongo_mapper'
8
8
  require 'versioned'
9
9
  require 'schema'
10
10
  begin; require 'redgreen'; rescue LoadError; end
11
+
12
+
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.1.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{2009-12-17}
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
- "README.rdoc",
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.1.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: 2009-12-17 00:00:00 -05:00
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