dm-lock 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/History.md ADDED
@@ -0,0 +1,5 @@
1
+
2
+ 0.0.1 / YYYY-MM-DD
3
+ ==================
4
+
5
+ * Initial release
data/Manifest ADDED
@@ -0,0 +1,15 @@
1
+ History.md
2
+ Rakefile
3
+ Readme.md
4
+ lib/dm-lock.rb
5
+ lib/dm-lock/exceptions.rb
6
+ lib/dm-lock/lock.rb
7
+ lib/dm-lock/optimistic.rb
8
+ lib/dm-lock/version.rb
9
+ spec/optimistic_lock_spec.rb
10
+ spec/spec.opts
11
+ spec/spec_helper.rb
12
+ tasks/docs.rake
13
+ tasks/gemspec.rake
14
+ tasks/spec.rake
15
+ Manifest
data/Rakefile ADDED
@@ -0,0 +1,17 @@
1
+
2
+ $:.unshift 'lib'
3
+ require 'dm-lock'
4
+ require 'rubygems'
5
+ require 'rake'
6
+ require 'echoe'
7
+
8
+ Echoe.new "dm-lock", DataMapper::Lock::VERSION do |p|
9
+ p.author = "TJ Holowaychuk"
10
+ p.email = "tj@vision-media.ca"
11
+ p.summary = "Locking utilities for DataMapper"
12
+ p.url = "http://github.com/visionmedia/dm-lock"
13
+ p.runtime_dependencies = []
14
+ p.runtime_dependencies << 'dm-core >=0.10.1'
15
+ end
16
+
17
+ Dir['tasks/**/*.rake'].sort.each { |f| load f }
data/Readme.md ADDED
@@ -0,0 +1,69 @@
1
+
2
+ # DataMapper::Lock
3
+
4
+ Locking utilities for DataMapper >= 0.10.1
5
+
6
+ ## DataMapper::Lock::Optimistic
7
+
8
+ Optimistic locking provides a relatively high performance
9
+ strategy for preventing race-conditions. This strategy does
10
+ not lock database tables, rows, or records.
11
+
12
+ The example below simply creates a propery named :lock_version,
13
+ which is then incremented every time record is saved. At the time of
14
+ saving or destroying a record, we first check the existing version in the
15
+ database, if it is the same we proceed, otherwise we raise DataMapper::Lock::StaleRecordError
16
+ which then allows you to apply merging, error reporting, or business logic
17
+ to resolve the conflict.
18
+
19
+ class Item
20
+ include DataMapper::Resource
21
+ lock :optimistic
22
+ property :id, Serial
23
+ property :name, String
24
+ end
25
+
26
+ // Process or thread 'A'
27
+ foo = Item.get 1
28
+
29
+ // Process or thread 'B'
30
+ foo = Item.get 1
31
+ foo.update :name => 'Baz'
32
+
33
+ // Process or thread 'A'
34
+ foo.update :name => 'Bar'
35
+ // => DataMapper::Lock::StaleRecordError: record has been altered
36
+
37
+ ## DataMapper::Lock::StaleRecordError
38
+
39
+ When raised you may access both the #current_record as well as
40
+ the #stale_record for any merging or reporting needs.
41
+
42
+ ## Contribution
43
+
44
+ * Feel free to fork and commit, email patches, suggestions or anything else you have in mind
45
+
46
+ ## License
47
+
48
+ (The MIT License)
49
+
50
+ Copyright (c) 2009 TJ Holowaychuk <tj@vision-media.ca>
51
+
52
+ Permission is hereby granted, free of charge, to any person obtaining
53
+ a copy of this software and associated documentation files (the
54
+ 'Software'), to deal in the Software without restriction, including
55
+ without limitation the rights to use, copy, modify, merge, publish,
56
+ distribute, sublicense, an d/or sell copies of the Software, and to
57
+ permit persons to whom the Software is furnished to do so, subject to
58
+ the following conditions:
59
+
60
+ The above copyright notice and this permission notice shall be
61
+ included in all copies or substantial portions of the Software.
62
+
63
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
64
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
65
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
66
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
67
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
68
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
69
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/dm-lock.gemspec ADDED
@@ -0,0 +1,33 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{dm-lock}
5
+ s.version = "0.0.1"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["TJ Holowaychuk"]
9
+ s.date = %q{2009-10-08}
10
+ s.description = %q{Locking utilities for DataMapper}
11
+ s.email = %q{tj@vision-media.ca}
12
+ s.extra_rdoc_files = ["lib/dm-lock.rb", "lib/dm-lock/exceptions.rb", "lib/dm-lock/lock.rb", "lib/dm-lock/optimistic.rb", "lib/dm-lock/version.rb", "tasks/docs.rake", "tasks/gemspec.rake", "tasks/spec.rake"]
13
+ s.files = ["History.md", "Rakefile", "Readme.md", "lib/dm-lock.rb", "lib/dm-lock/exceptions.rb", "lib/dm-lock/lock.rb", "lib/dm-lock/optimistic.rb", "lib/dm-lock/version.rb", "spec/optimistic_lock_spec.rb", "spec/spec.opts", "spec/spec_helper.rb", "tasks/docs.rake", "tasks/gemspec.rake", "tasks/spec.rake", "Manifest", "dm-lock.gemspec"]
14
+ s.homepage = %q{http://github.com/visionmedia/dm-lock}
15
+ s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Dm-lock", "--main", "Readme.md"]
16
+ s.require_paths = ["lib"]
17
+ s.rubyforge_project = %q{dm-lock}
18
+ s.rubygems_version = %q{1.3.5}
19
+ s.summary = %q{Locking utilities for DataMapper}
20
+
21
+ if s.respond_to? :specification_version then
22
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
23
+ s.specification_version = 3
24
+
25
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
26
+ s.add_runtime_dependency(%q<dm-core>, [">= 0.10.1"])
27
+ else
28
+ s.add_dependency(%q<dm-core>, [">= 0.10.1"])
29
+ end
30
+ else
31
+ s.add_dependency(%q<dm-core>, [">= 0.10.1"])
32
+ end
33
+ end
data/lib/dm-lock.rb ADDED
@@ -0,0 +1,35 @@
1
+ #--
2
+ # Copyright (c) 2009 TJ Holowaychuk <tj@vision-media.ca>
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ require 'dm-core'
25
+ require 'dm-lock/version'
26
+ require 'dm-lock/lock'
27
+ require 'dm-lock/exceptions'
28
+ require 'dm-lock/optimistic'
29
+
30
+ #--
31
+ # DataMapper
32
+ #++
33
+
34
+ DataMapper::Model.append_inclusions DataMapper::Lock
35
+ DataMapper::Model.append_inclusions DataMapper::Lock::Optimistic
@@ -0,0 +1,26 @@
1
+
2
+ module DataMapper
3
+ module Lock
4
+ class StaleRecordError < StandardError
5
+
6
+ ##
7
+ # Record as-is current in the database.
8
+
9
+ attr_reader :current_record
10
+
11
+ ##
12
+ # Stale record attempted to be destroyed or saved.
13
+
14
+ attr_reader :stale_record
15
+
16
+ ##
17
+ # Initialize with _stale_ and _latest_ records.
18
+
19
+ def initialize stale, latest
20
+ @stale_record, @current_record = stale, latest
21
+ super 'record has been altered'
22
+ end
23
+
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,22 @@
1
+
2
+ module DataMapper
3
+ module Lock
4
+
5
+ ##
6
+ # Provide #lock method to _model_.
7
+
8
+ def self.included model
9
+ model.extend ClassMethods
10
+ end
11
+
12
+ module ClassMethods
13
+
14
+ ##
15
+ # Shortcut for calling lock_METHOD.
16
+
17
+ def lock method, *args
18
+ send :"lock_#{method}", *args
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,70 @@
1
+
2
+ module DataMapper
3
+ module Lock
4
+ module Optimistic
5
+
6
+ ##
7
+ # Validate _model_'s lock_version before attempting
8
+ # to save or destroy.
9
+
10
+ def self.included model
11
+ model.extend ClassMethods
12
+ model.before :destroy, :validate_lock_version
13
+ model.before :save, :validate_lock_version
14
+ model.before :save, :increment_lock_version
15
+ end
16
+
17
+ ##
18
+ # Validate the lock version.
19
+ #
20
+ # * Lets new records passed
21
+ # * Lets un-altered records passed
22
+ # * Lets the record pass unless the lock_version has been changed
23
+ #
24
+ # === Raises
25
+ #
26
+ # DataMapper::Lock::StaleRecordError
27
+ #
28
+
29
+ def validate_lock_version
30
+ if !new? && dirty?
31
+ if lock_version_altered?
32
+ raise DataMapper::Lock::StaleRecordError.new(self, @latest_record)
33
+ end
34
+ end
35
+ end
36
+
37
+ ##
38
+ # Check if the lock_version has been altered and
39
+ # return the latest record.
40
+
41
+ def lock_version_altered?
42
+ @latest_record = self.class.get original_attributes[:id] || id
43
+ @latest_record.lock_version != lock_version
44
+ end
45
+
46
+ ##
47
+ # Increment the lock_version.
48
+
49
+ def increment_lock_version
50
+ self.lock_version += 1
51
+ end
52
+
53
+ module ClassMethods
54
+
55
+ ##
56
+ # Provide optimistic locking support.
57
+ #
58
+ # This method adds a property named :lock_version
59
+ # which is used to track alterations made, preventing
60
+ # multi-thread/process rece conditions while updating or
61
+ # destroying records.
62
+
63
+ def lock_optimistic options = {}
64
+ property :lock_version, Integer, :nullable => true, :default => 0
65
+ end
66
+
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,6 @@
1
+
2
+ module DataMapper
3
+ module Lock
4
+ VERSION = '0.0.1'
5
+ end
6
+ end
@@ -0,0 +1,97 @@
1
+
2
+ require File.dirname(__FILE__) + '/spec_helper'
3
+
4
+ describe DataMapper::Lock::Optimistic do
5
+
6
+ class Item
7
+ include DataMapper::Resource
8
+ property :id, Serial
9
+ property :name, String
10
+ end
11
+
12
+ before :each do
13
+ DataMapper.auto_migrate!
14
+ end
15
+
16
+ describe "#lock" do
17
+ describe "with arguments" do
18
+ it "should call #lock_optimistic" do
19
+ Item.should_receive :lock_optimistic
20
+ Item.lock :optimistic
21
+ end
22
+
23
+ it "should pass addition arguments" do
24
+ Item.should_receive(:lock_optimistic).with({ :foo => 'bar' })
25
+ Item.lock :optimistic, :foo => 'bar'
26
+ end
27
+ end
28
+ end
29
+
30
+ describe "#lock :optimistic" do
31
+ it "should add a :lock_version property" do
32
+ Item.lock :optimistic
33
+ Item.properties.named?(:lock_version).should be_true
34
+ end
35
+
36
+ it "should default :lock_version to 0" do
37
+ Item.new.lock_version.should == 0
38
+ end
39
+
40
+ describe "when saving a valid record" do
41
+ it "should increment :lock_version" do
42
+ foo = Item.new :name => 'foo'
43
+ foo.save
44
+ foo.reload.lock_version.should == 1
45
+ foo.save
46
+ foo.reload.lock_version.should == 2
47
+ end
48
+ end
49
+
50
+ describe "when saving a record which has been altered" do
51
+ it "should raise DataMapper::Lock::StaleRecordError" do
52
+ foo = Item.create :name => 'foo'
53
+ foo2 = Item.first :name => 'foo'
54
+ foo.name = 'updated foo'
55
+ foo2.update :name => 'bar'
56
+ lambda { foo.save }.should raise_error(DataMapper::Lock::StaleRecordError, /altered/)
57
+ end
58
+
59
+ it "should provide the records in the raised exception" do
60
+ foo = Item.create :name => 'foo'
61
+ foo2 = Item.first :name => 'foo'
62
+ foo.name = 'updated foo'
63
+ foo2.update :name => 'bar'
64
+ begin
65
+ foo.save
66
+ rescue DataMapper::Lock::StaleRecordError => e
67
+ e.stale_record.name.should == 'updated foo'
68
+ e.current_record.name.should == 'bar'
69
+ end
70
+ end
71
+ end
72
+
73
+ describe "when destroying a record which has been altered" do
74
+ it "should raise DataMapper::Lock::StaleRecordError" do
75
+ foo = Item.create :name => 'foo'
76
+ foo2 = Item.first :name => 'foo'
77
+ foo.name = 'updated foo'
78
+ foo2.save
79
+ lambda { foo.destroy }.should raise_error(DataMapper::Lock::StaleRecordError, /altered/)
80
+ end
81
+
82
+ it "should provide the records in the raised exception" do
83
+ foo = Item.create :name => 'foo'
84
+ foo2 = Item.first :name => 'foo'
85
+ foo.name = 'updated foo'
86
+ foo2.save
87
+ begin
88
+ foo.destroy
89
+ rescue DataMapper::Lock::StaleRecordError => e
90
+ e.stale_record.name.should == 'updated foo'
91
+ e.current_record.name.should == 'foo'
92
+ end
93
+ end
94
+ end
95
+ end
96
+
97
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format specdoc
@@ -0,0 +1,6 @@
1
+
2
+ $:.unshift File.dirname(__FILE__) + '/../lib'
3
+ require 'rubygems'
4
+ require 'dm-lock'
5
+
6
+ DataMapper.setup :default, 'sqlite3::memory:'
data/tasks/docs.rake ADDED
@@ -0,0 +1,13 @@
1
+
2
+ namespace :docs do
3
+
4
+ desc 'Remove rdoc products'
5
+ task :remove => [:clobber_docs]
6
+
7
+ desc 'Build docs, and open in browser for viewing (specify BROWSER)'
8
+ task :open do
9
+ browser = ENV["BROWSER"] || "safari"
10
+ sh "open -a #{browser} doc/index.html"
11
+ end
12
+
13
+ end
@@ -0,0 +1,3 @@
1
+
2
+ desc 'Build gemspec file'
3
+ task :gemspec => [:build_gemspec]
data/tasks/spec.rake ADDED
@@ -0,0 +1,25 @@
1
+
2
+ require 'spec/rake/spectask'
3
+
4
+ desc "Run all specifications"
5
+ Spec::Rake::SpecTask.new(:spec) do |t|
6
+ t.libs << "lib"
7
+ t.spec_opts = ["--color", "--require", "spec/spec_helper.rb"]
8
+ end
9
+
10
+ namespace :spec do
11
+
12
+ desc "Run all specifications verbosely"
13
+ Spec::Rake::SpecTask.new(:verbose) do |t|
14
+ t.libs << "lib"
15
+ t.spec_opts = ["--color", "--format", "specdoc", "--require", "spec/spec_helper.rb"]
16
+ end
17
+
18
+ desc "Run specific specification verbosely (specify SPEC)"
19
+ Spec::Rake::SpecTask.new(:select) do |t|
20
+ t.libs << "lib"
21
+ t.spec_files = [ENV["SPEC"]]
22
+ t.spec_opts = ["--color", "--format", "specdoc", "--require", "spec/spec_helper.rb"]
23
+ end
24
+
25
+ end
metadata ADDED
@@ -0,0 +1,91 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dm-lock
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - TJ Holowaychuk
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-10-08 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: dm-core
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 0.10.1
24
+ version:
25
+ description: Locking utilities for DataMapper
26
+ email: tj@vision-media.ca
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files:
32
+ - lib/dm-lock.rb
33
+ - lib/dm-lock/exceptions.rb
34
+ - lib/dm-lock/lock.rb
35
+ - lib/dm-lock/optimistic.rb
36
+ - lib/dm-lock/version.rb
37
+ - tasks/docs.rake
38
+ - tasks/gemspec.rake
39
+ - tasks/spec.rake
40
+ files:
41
+ - History.md
42
+ - Rakefile
43
+ - Readme.md
44
+ - lib/dm-lock.rb
45
+ - lib/dm-lock/exceptions.rb
46
+ - lib/dm-lock/lock.rb
47
+ - lib/dm-lock/optimistic.rb
48
+ - lib/dm-lock/version.rb
49
+ - spec/optimistic_lock_spec.rb
50
+ - spec/spec.opts
51
+ - spec/spec_helper.rb
52
+ - tasks/docs.rake
53
+ - tasks/gemspec.rake
54
+ - tasks/spec.rake
55
+ - Manifest
56
+ - dm-lock.gemspec
57
+ has_rdoc: true
58
+ homepage: http://github.com/visionmedia/dm-lock
59
+ licenses: []
60
+
61
+ post_install_message:
62
+ rdoc_options:
63
+ - --line-numbers
64
+ - --inline-source
65
+ - --title
66
+ - Dm-lock
67
+ - --main
68
+ - Readme.md
69
+ require_paths:
70
+ - lib
71
+ required_ruby_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: "0"
76
+ version:
77
+ required_rubygems_version: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: "1.2"
82
+ version:
83
+ requirements: []
84
+
85
+ rubyforge_project: dm-lock
86
+ rubygems_version: 1.3.5
87
+ signing_key:
88
+ specification_version: 3
89
+ summary: Locking utilities for DataMapper
90
+ test_files: []
91
+