dm-lock 0.0.1

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