Skip to content

Commit

Permalink
Refactoring with a number of improvements:
Browse files Browse the repository at this point in the history
* can use the regular #save or #save! instead of #save_optimistic!
* can now use locking with #destroy
* can use #unlock to persist without locking
  • Loading branch information
jingoro committed May 29, 2012
1 parent 2b0af3a commit a9909be
Show file tree
Hide file tree
Showing 21 changed files with 372 additions and 88 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
Gemfile.lock
pkg/*
.idea
*.iml
*.iml
.rvmrc
File renamed without changes.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ To use it, all you have to do is add include Mongoid::Lockable
end

This will add a _lock_version field in the document which will be incremented every time a save is called.
In order a to save a document in a save manner, use the new <code>save_optimistic!</code> and <code>rescue Mongoid::Errors::StaleDocument</code> to handle applicative logic in case the object was changed.
Be sure to rescue `Mongoid::Errors::StaleDocument` to handle applicative logic in case the object was changed.

For example:

Expand All @@ -37,7 +37,7 @@ For example:
begin
post = Post.find(params[:id])
post.text += "---UPDATE--- " + params[:more_text]
post.save_optimistic!
post.save
rescue Mongoid::Errors::StaleDocument
retry
end
Expand Down
5 changes: 5 additions & 0 deletions Rakefile
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
require "bundler/gem_tasks"

require 'rspec/core/rake_task'
RSpec::Core::RakeTask.new(:spec)

task :default => :spec
7 changes: 5 additions & 2 deletions lib/config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,8 @@ en:
mongoid:
errors:
messages:
stale:
Document class %{klass} with id(s) is stale and should be reloaded.
stale_document:
update:
"Attempted to update a stale document: %{klass}"
destroy:
"Attempted to destroy a stale document: %{klass}"
28 changes: 28 additions & 0 deletions lib/mongoid/errors/stale_document.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
require 'mongoid/errors/mongoid_error'

module Mongoid
module Errors

# Raised when trying to update a document that has been updated by
# another process.
#
# @example Create the error.
# StaleDocument.new('update', document)
class StaleDocument < MongoidError

attr_reader :action, :document

def initialize(action, document)
@action = action
@document = document

super(
translate(
"stale_document.#{action}",
{ :klass => document.class.name }
)
)
end
end
end
end
13 changes: 13 additions & 0 deletions lib/mongoid/lockable.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module Mongoid

module Lockable

extend ActiveSupport::Concern

included do
ActiveSupport::Deprecation.warn 'Mongoid::Lockable is deprecated and will be removed. Use Mongoid::OptimisticLocking instead.', caller
include Mongoid::OptimisticLocking
end

end
end
39 changes: 39 additions & 0 deletions lib/mongoid/optimistic_locking.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
require 'mongoid'
require 'mongoid/errors/stale_document'
require 'mongoid/lockable'
require 'mongoid/optimistic_locking/deprecated'
require 'mongoid/optimistic_locking/lock_version'
require 'mongoid/optimistic_locking/operations'
require 'mongoid/optimistic_locking/threaded_with_unlocked'
require 'mongoid/optimistic_locking/unlocked'
require 'mongoid/optimistic_locking/version'

# monkey patch Threaded
Mongoid::Threaded.send :include, Mongoid::OptimisticLocking::ThreadedWithUnlocked

# add english load path to translations
I18n.load_path << File.expand_path('../../config/locales/en.yml', __FILE__)

module Mongoid
# == What is Optimistic Locking
#
# See <http://api.rubyonrails.org/classes/ActiveRecord/Locking/Optimistic.html>.
#
# == Usage
#
# TODO ...
module OptimisticLocking

extend ActiveSupport::Concern

include Deprecated
include LockVersion
include Operations
include Unlocked

included do
field LOCKING_FIELD, :type => Integer, :default => 0
end

end
end
12 changes: 12 additions & 0 deletions lib/mongoid/optimistic_locking/deprecated.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module Mongoid
module OptimisticLocking
module Deprecated

def save_optimistic!(*args)
ActiveSupport::Deprecation.warn 'save_optimistic! is deprecated and will be removed. Use save or save! instead', caller
save! *args
end

end
end
end
29 changes: 29 additions & 0 deletions lib/mongoid/optimistic_locking/lock_version.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
module Mongoid
module OptimisticLocking
module LockVersion

LOCKING_FIELD = :_lock_version

private

attr_reader :lock_version_for_selector

def set_lock_version_for_selector
@lock_version_for_selector = self[LOCKING_FIELD]
yield
rescue Exception
@lock_version_for_selector = nil
raise
end

def increment_lock_version
self[LOCKING_FIELD] = self[LOCKING_FIELD] ? self[LOCKING_FIELD] + 1 : 1
yield
rescue Exception
self[LOCKING_FIELD] = self[LOCKING_FIELD] ? self[LOCKING_FIELD] - 1 : 0
raise
end

end
end
end
53 changes: 53 additions & 0 deletions lib/mongoid/optimistic_locking/operations.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
module Mongoid
module OptimisticLocking
module Operations

def insert(*args)
return super unless optimistic_locking?
increment_lock_version do
super
end
end

def update(*args)
return super unless optimistic_locking?
set_lock_version_for_selector do
increment_lock_version do
result = super
unless Mongoid.database.command({:getlasterror => 1})['updatedExisting']
raise Mongoid::Errors::StaleDocument.new('update', self)
end
result
end
end
end

def remove(*args)
return super unless optimistic_locking?
set_lock_version_for_selector do
result = super
unless Mongoid.database.command({:getlasterror => 1})['updatedExisting']
raise Mongoid::Errors::StaleDocument.new('destroy', self)
end
result
end
end

def atomic_selector
result = super
if optimistic_locking? && lock_version_for_selector
key =
if metadata && metadata.embedded?
path = metadata.path(self)
"#{path.path}._lock_version"
else
'_lock_version'
end
result[key] = lock_version_for_selector
end
result
end

end
end
end
30 changes: 30 additions & 0 deletions lib/mongoid/optimistic_locking/threaded_with_unlocked.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
module Mongoid
module OptimisticLocking
module ThreadedWithUnlocked

extend ActiveSupport::Concern

module ClassMethods

def optimistic_locking?
!unlocked
end

def unlocked
!!Thread.current["[mongoid]:unlocked"]
end

def unlocked=(value)
Thread.current["[mongoid]:unlocked"] = value
end

def clear_options!
self.unlocked = false
super
end

end

end
end
end
26 changes: 26 additions & 0 deletions lib/mongoid/optimistic_locking/unlocked.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
module Mongoid
module OptimisticLocking
module Unlocked

extend ActiveSupport::Concern

def unlocked
Threaded.unlocked = true
self
end

def optimistic_locking?
Threaded.optimistic_locking?
end

module ClassMethods

def unlocked
Threaded.unlocked = true
end

end

end
end
end
5 changes: 5 additions & 0 deletions lib/mongoid/optimistic_locking/version.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module Mongoid
module OptimisticLocking
VERSION = '0.0.1'
end
end
4 changes: 1 addition & 3 deletions lib/mongoid_optimistic_locking.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
require "mongoid_optimistic_locking/version"
require File.join(File.dirname(__FILE__), 'mongoid_optimistic_locking/lockable')
I18n.load_path << File.join(File.dirname(__FILE__), "config", "locales", "en.yml")
require 'mongoid/optimistic_locking'
24 changes: 0 additions & 24 deletions lib/mongoid_optimistic_locking/errors.rb

This file was deleted.

46 changes: 0 additions & 46 deletions lib/mongoid_optimistic_locking/lockable.rb

This file was deleted.

3 changes: 0 additions & 3 deletions lib/mongoid_optimistic_locking/version.rb

This file was deleted.

6 changes: 3 additions & 3 deletions mongoid_optimistic_locking.gemspec
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
# -*- encoding: utf-8 -*-
$:.push File.expand_path("../lib", __FILE__)
require "mongoid_optimistic_locking/version"
require "mongoid/optimistic_locking/version"

Gem::Specification.new do |s|
s.name = "mongoid_optimistic_locking"
s.version = MongoidOptimisticLocking::VERSION
s.version = Mongoid::OptimisticLocking::VERSION
s.authors = ["Alon Burg"]
s.email = ["burgalon@gmail.com"]
s.homepage = ""
s.summary = %q{Allows optimisitic locking for Mongoid models}
s.description = %q{Allows optimisitic locking for Mongoid models. See https://github.com/burgalon/mongoid-optimistic-locking}
s.description = %q{Allows optimisitic locking for Mongoid models. See https://github.com/burgalon/mongoid_optimistic_locking}

s.add_development_dependency 'rspec', '~> 2.6'
s.add_development_dependency 'mongoid', '~> 2.4'
Expand Down
Loading

0 comments on commit a9909be

Please sign in to comment.