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 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