activerecord-tablelocks 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in activerecord-tablelocks.gemspec
4
+ gemspec
5
+
6
+ gem 'pg'
7
+
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Sernin van de Krol
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,56 @@
1
+ ## ActiveRecord Tablelocks
2
+
3
+ This gem adds table locking functionality to ActiveRecord.
4
+ Every save and destroy action is already done using a transaction, but now you can add locking to this process to make sure you really have unique records.
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ gem 'activerecord-tablelocks'
11
+
12
+ And then execute:
13
+
14
+ $ bundle
15
+
16
+ Or install it yourself as:
17
+
18
+ $ gem install activerecord-tablelocks
19
+
20
+ ## Usage
21
+
22
+ In your models use the following to lock the table when editing records:
23
+
24
+ class User < ActiveRecord::Base
25
+ validates :login, :uniqueness => true, :presence => true
26
+ # The following line enables locking to make sure the uniqueness constraint holds.
27
+ enable_locking
28
+ end
29
+
30
+ If you need to lock multiple table, you can specify this in two ways.
31
+
32
+ Using class names:
33
+
34
+ class EmailBox < ActiveRecord::Base
35
+ enable_locking :class_names => ['EmailAlias']
36
+ # The class name needs to be a String, but doesn't need to be in an Array.
37
+ # Don't forget to add validations, this gem doesn't do that for you.
38
+ end
39
+
40
+ Using table names:
41
+
42
+ class EmailAlias < ActiveRecord::Base
43
+ enable_locking :table_names => ['email_boxes']
44
+ # The table name needs to be a String, but doesn't need to be in an Array.
45
+ # The gem quotes the table name for you, so you don't need to that here.
46
+ # Don't forget to add validations, this gem doesn't do that for you.
47
+ end
48
+
49
+
50
+ ## Contributing
51
+
52
+ 1. Fork it
53
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
54
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
55
+ 4. Push to the branch (`git push origin my-new-feature`)
56
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'activerecord/tablelocks/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "activerecord-tablelocks"
8
+ spec.version = Activerecord::Tablelocks::VERSION
9
+ spec.authors = ["Sernin van de Krol"]
10
+ spec.email = ["serninpc@paneidos.net"]
11
+ spec.description = %q{This gem enables the use of database specific table locks when saving or destroying your ActiveRecord objects. This ensures no race conditions exist when using e.g. validates_uniqueness_of.}
12
+ spec.summary = %q{Use native table locks of your database for your ActiveRecord models}
13
+ spec.homepage = "https://github.com/paneidos/activerecord-tablelocks"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split("\n")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "activerecord", ">= 3.2.0"
22
+
23
+ spec.add_development_dependency "bundler", ">= 1.2"
24
+ spec.add_development_dependency "rake"
25
+ spec.add_development_dependency "railties"
26
+ spec.add_development_dependency "rspec", "~> 2.14.1"
27
+ end
@@ -0,0 +1,9 @@
1
+ require 'active_support'
2
+
3
+ if defined? Rails
4
+ require 'activerecord/tablelocks/railties'
5
+ else
6
+ ActiveSupport.on_load :active_record do
7
+ require 'activerecord/tablelocks/activerecord'
8
+ end
9
+ end
@@ -0,0 +1,56 @@
1
+ # Extends AR to add table locking
2
+ module ActiveRecord
3
+ class Base
4
+ class << self
5
+ attr_reader :locking_enabled
6
+
7
+ def enable_locking(options = {})
8
+ @locking_enabled = true
9
+ @lock_targets = {
10
+ :class_names => [*options[:class_names]].compact,
11
+ :table_names => [*options[:table_names]].compact
12
+ }
13
+ end
14
+
15
+ def lock_targets
16
+ @lock_targets ||= {
17
+ :class_names => [],
18
+ :table_names => []
19
+ }
20
+ end
21
+
22
+ def tables_to_lock
23
+ return [] unless @locking_enabled
24
+ [quoted_table_name,
25
+ *@lock_targets[:table_names].map{|table_name| connection.quote_table_name(table_name) },
26
+ *@lock_targets[:class_names].map{|class_name| class_name.constantize.quoted_table_name }].sort.uniq
27
+ end
28
+ end
29
+
30
+ def add_to_transaction
31
+ if self.class.locking_enabled
32
+ self.class.connection.lock_tables self.class.tables_to_lock
33
+ end
34
+ super
35
+ end
36
+ end
37
+ module ConnectionAdapters
38
+ class AbstractAdapter
39
+ def self.inherited(subclass)
40
+ case subclass.name
41
+ when "ActiveRecord::ConnectionAdapters::PostgreSQLAdapter"
42
+ require 'activerecord/tablelocks/activerecord/postgres'
43
+ end
44
+ end
45
+ def lock_table(quoted_table_name)
46
+ logger.warn "WARNING: Locking is not supported for your database!"
47
+ end
48
+ def lock_tables(quoted_table_names)
49
+ logger.warn "WARNING: Locking is not supported for your database!"
50
+ end
51
+ end
52
+ if defined?(PostgreSQLAdapter)
53
+ require 'activerecord/tablelocks/activerecord/postgres'
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,14 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ class PostgreSQLAdapter
4
+ def lock_table(quoted_table_name)
5
+ execute "LOCK TABLE #{quoted_table_name} IN EXCLUSIVE MODE"
6
+ end
7
+ def lock_tables(quoted_table_names)
8
+ quoted_table_names.each do |quoted_table_name|
9
+ lock_table quoted_table_name
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,10 @@
1
+ require 'rails'
2
+
3
+ class Tablelocks < Rails::Railtie
4
+
5
+ initializer 'activerecord-tablelocks' do
6
+ ActiveSupport.on_load :active_record do
7
+ require 'activerecord/tablelocks/activerecord'
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,5 @@
1
+ module Activerecord
2
+ module Tablelocks
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+ describe "Extension methods" do
3
+ describe "ActiveRecord objects" do
4
+ it "should accept zero arguments for 'enable_locking'" do
5
+ klass = Class.new(ActiveRecord::Base)
6
+ lambda { klass.enable_locking }.should_not raise_error
7
+ end
8
+
9
+ it "should accept a hash with options" do
10
+ klass = Class.new(ActiveRecord::Base)
11
+ lambda { klass.enable_locking({}) }.should_not raise_error
12
+ end
13
+
14
+ it "should have an empty list at the start" do
15
+ klass = Class.new(ActiveRecord::Base)
16
+ klass.lock_targets[:class_names].should == []
17
+ klass.lock_targets[:table_names].should == []
18
+ end
19
+
20
+ it "should save class_names and table_names from options" do
21
+ klass = Class.new(ActiveRecord::Base)
22
+ klass.enable_locking({ class_names: ['User', 'Group'], table_names: ['users_groups'] })
23
+ klass.lock_targets[:class_names].sort.should == ['Group','User']
24
+ klass.lock_targets[:table_names].sort.should == ['users_groups']
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,39 @@
1
+ require 'spec_helper'
2
+ require 'models/page'
3
+ require 'models/comment'
4
+ require 'models/user'
5
+ require 'models/group'
6
+
7
+ describe "Locking target" do
8
+ it "should be empty by default" do
9
+ Page.tables_to_lock.should == []
10
+ end
11
+
12
+ it "should include the class' own table by default" do
13
+ Comment.tables_to_lock.should include(Comment.quoted_table_name)
14
+ end
15
+
16
+ it "should include all specified classes" do
17
+ User.tables_to_lock.should include(User.quoted_table_name)
18
+ User.tables_to_lock.should include(Group.quoted_table_name)
19
+ end
20
+
21
+ it "should include all specified tables" do
22
+ Group.tables_to_lock.should include(User.quoted_table_name)
23
+ Group.tables_to_lock.should include(Group.quoted_table_name)
24
+ end
25
+
26
+ it "should have the tables to lock sorted" do
27
+ User.tables_to_lock.sort.should == User.tables_to_lock
28
+ Comment.tables_to_lock.sort.should == Comment.tables_to_lock
29
+ Page.tables_to_lock.sort.should == Page.tables_to_lock
30
+ Group.tables_to_lock.sort.should == Group.tables_to_lock
31
+ end
32
+
33
+ it "should not contain duplicate table names" do
34
+ User.tables_to_lock.uniq.should == User.tables_to_lock
35
+ Comment.tables_to_lock.uniq.should == Comment.tables_to_lock
36
+ Page.tables_to_lock.uniq.should == Page.tables_to_lock
37
+ Group.tables_to_lock.uniq.should == Group.tables_to_lock
38
+ end
39
+ end
@@ -0,0 +1,30 @@
1
+ class Comment < ActiveRecord::Base
2
+ enable_locking
3
+ validates :title, :uniqueness => true, :presence => true
4
+
5
+
6
+ attr_accessor :validate_mutexes
7
+ attr_accessor :commit_mutexes
8
+ attr_accessor :wait_time
9
+ after_validation :release_mutexes
10
+ after_validation :wait
11
+
12
+ def wait
13
+ if wait_time.present?
14
+ sleep wait_time
15
+ end
16
+ end
17
+
18
+ def release_mutexes
19
+ if validate_mutexes.present?
20
+ validate_mutexes.each do |mutex|
21
+ mutex.unlock
22
+ end
23
+ end
24
+ if commit_mutexes.present?
25
+ commit_mutexes.each do |mutex|
26
+ mutex.lock
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,22 @@
1
+ class Group < ActiveRecord::Base
2
+ enable_locking :table_names => "users", :class_names => "User"
3
+
4
+ validates :name, :uniqueness => true, :presence => true
5
+
6
+
7
+
8
+
9
+
10
+ attr_accessor :validate_mutexes
11
+ attr_accessor :commit_mutexes
12
+ after_validation :release_mutexes
13
+
14
+ def release_mutexes
15
+ validate_mutexes.each do |mutex|
16
+ mutex.unlock
17
+ end
18
+ commit_mutexes.each do |mutex|
19
+ mutex.lock
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,3 @@
1
+ class Page < ActiveRecord::Base
2
+ # No validators in here
3
+ end
@@ -0,0 +1,18 @@
1
+ class User < ActiveRecord::Base
2
+ enable_locking :class_names => "Group"
3
+
4
+ validates :name, :uniqueness => true, :presence => true
5
+
6
+ attr_accessor :validate_mutexes
7
+ attr_accessor :commit_mutexes
8
+ after_validation :release_mutexes
9
+
10
+ def release_mutexes
11
+ validate_mutexes.each do |mutex|
12
+ mutex.unlock
13
+ end
14
+ commit_mutexes.each do |mutex|
15
+ mutex.lock
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,9 @@
1
+ require 'spec_helper'
2
+ require 'models/page'
3
+
4
+ describe "Normal behaviour" do
5
+ it "is not affected by this gem" do
6
+ page = Page.new(name: "Home", content: "Lorem ipsum dolor, ... you know this.")
7
+ page.save.should == true
8
+ end
9
+ end
@@ -0,0 +1,208 @@
1
+ require 'spec_helper'
2
+ require 'models/comment'
3
+
4
+ describe "Race conditions" do
5
+ it "should prevent race conditions on a single table" do
6
+ comment1 = Comment.new(title: "TITLE")
7
+ comment2 = Comment.new(title: "TITLE")
8
+
9
+ mutex1 = Mutex.new
10
+ mutex2 = Mutex.new
11
+ mutex3 = Mutex.new
12
+ mutex4 = Mutex.new
13
+
14
+ # This should not run within reasonable time
15
+
16
+ # main thread:
17
+ # 1. lock mutex1
18
+ # 2. lock mutex2
19
+ # 3. start threads
20
+ # 4. wait for mutex3 and mutex4 to be locked
21
+ # 4. release mutex1 and mutex2
22
+
23
+ # comment1:
24
+ # 0. lock mutex3
25
+ # 1. lock mutex1
26
+ # 2. run validations
27
+ # 3. release mutex1 and mutex3
28
+ # 4. lock mutex4
29
+ # 5. commit
30
+ # 6. release mutex4
31
+
32
+ # comment2:
33
+ # 1. lock mutex4
34
+ # 2. lock mutex2
35
+ # 2. run validations
36
+ # 3. release mutex2 and mutex4
37
+ # 4. lock mutex3
38
+ # 5. commit
39
+ # 6. release mutex3
40
+
41
+ mutex1.lock
42
+ mutex2.lock
43
+
44
+ comment1.validate_mutexes = [mutex1,mutex3]
45
+ comment1.commit_mutexes = [mutex4]
46
+ comment2.validate_mutexes = [mutex2,mutex4]
47
+ comment2.commit_mutexes = [mutex3]
48
+
49
+ # Start threads
50
+ thread1 = Thread.new do
51
+ mutex3.lock
52
+ mutex1.lock
53
+ ActiveRecord::Base.connection_pool.with_connection do |conn|
54
+ comment1.save
55
+ end
56
+ mutex4.unlock
57
+ end
58
+
59
+ thread2 = Thread.new do
60
+ mutex4.lock
61
+ mutex2.lock
62
+ ActiveRecord::Base.connection_pool.with_connection do |conn|
63
+ comment2.save
64
+ end
65
+ mutex3.unlock
66
+ end
67
+
68
+ until mutex3.locked?
69
+ sleep 0.001
70
+ end
71
+ until mutex4.locked?
72
+ sleep 0.001
73
+ end
74
+
75
+ mutex1.unlock
76
+ mutex2.unlock
77
+ thread1.join(3).should be_nil
78
+ thread2.join(3).should be_nil
79
+
80
+ comment1.persisted?.should == false
81
+ comment2.persisted?.should == false
82
+
83
+ # Clean up the threads
84
+ thread1.exit
85
+ thread2.exit
86
+
87
+ end
88
+ it "should prevent race conditions on validations spanning multiple tables" do
89
+ user = User.new(name: "root")
90
+ group = Group.new(name: "root")
91
+
92
+ mutex1 = Mutex.new
93
+ mutex2 = Mutex.new
94
+ mutex3 = Mutex.new
95
+ mutex4 = Mutex.new
96
+
97
+ # This should not run within reasonable time
98
+
99
+ # main thread:
100
+ # 1. lock mutex1
101
+ # 2. lock mutex2
102
+ # 3. start threads
103
+ # 4. wait for mutex3 and mutex4 to be locked
104
+ # 4. release mutex1 and mutex2
105
+
106
+ # user:
107
+ # 0. lock mutex3
108
+ # 1. lock mutex1
109
+ # 2. run validations
110
+ # 3. release mutex1 and mutex3
111
+ # 4. lock mutex4
112
+ # 5. commit
113
+ # 6. release mutex4
114
+
115
+ # group:
116
+ # 1. lock mutex4
117
+ # 2. lock mutex2
118
+ # 2. run validations
119
+ # 3. release mutex2 and mutex4
120
+ # 4. lock mutex3
121
+ # 5. commit
122
+ # 6. release mutex3
123
+
124
+ mutex1.lock
125
+ mutex2.lock
126
+
127
+ user.validate_mutexes = [mutex1,mutex3]
128
+ user.commit_mutexes = [mutex4]
129
+ group.validate_mutexes = [mutex2,mutex4]
130
+ group.commit_mutexes = [mutex3]
131
+
132
+ # Start threads
133
+ thread1 = Thread.new do
134
+ mutex3.lock
135
+ mutex1.lock
136
+ ActiveRecord::Base.connection_pool.with_connection do |conn|
137
+ user.save
138
+ end
139
+ mutex4.unlock
140
+ end
141
+
142
+ thread2 = Thread.new do
143
+ mutex4.lock
144
+ mutex2.lock
145
+ ActiveRecord::Base.connection_pool.with_connection do |conn|
146
+ group.save
147
+ end
148
+ mutex3.unlock
149
+ end
150
+
151
+ until mutex3.locked?
152
+ sleep 0.001
153
+ end
154
+ until mutex4.locked?
155
+ sleep 0.001
156
+ end
157
+
158
+ mutex1.unlock
159
+ mutex2.unlock
160
+ thread1.join(3).should be_nil
161
+ thread2.join(3).should be_nil
162
+
163
+ user.persisted?.should == false
164
+ group.persisted?.should == false
165
+
166
+ # Clean up the threads
167
+ thread1.exit
168
+ thread2.exit
169
+ end
170
+
171
+ it "should pass a test which tries to create a huge amount of comments at roughly the same time with random times between validation and saving to the database" do
172
+ puts "This test will take a long time"
173
+ comments = []
174
+ threads = []
175
+ number_of_comments = 100
176
+ timeouts = AtomicInteger.new
177
+ # The wait times are sorted in reverse order to increase the chance of a race condition
178
+ wait_times = list_of_random_numbers(number_of_comments).sort.reverse
179
+ number_of_comments.times do
180
+ comment = Comment.new title: "RACE"
181
+ comment.wait_time = wait_times.shift
182
+ comments << comment
183
+ threads << Thread.new do
184
+ saved = false
185
+ until saved do
186
+ begin
187
+ ActiveRecord::Base.connection_pool.with_connection do |conn|
188
+ comment.save
189
+ saved = true
190
+ end
191
+ rescue ActiveRecord::ConnectionTimeoutError => e
192
+ # This test most likely triggers a lot of timeout errors
193
+ # We can safely ignore those errors, it's intended behaviour of this test
194
+ # To improve the accuracy of the tests, it's recommended to increase the poolsize
195
+ timeouts.increment
196
+ end
197
+ end
198
+ end
199
+ end
200
+ threads.each do |thread|
201
+ thread.join
202
+ end
203
+ comments.map(&:persisted?).select {|x| x == true}.size.should == 1
204
+ if timeouts.value > 0
205
+ puts "There were #{timeouts.value} timeouts during this test, increase your database connection pool size to improve accuracy"
206
+ end
207
+ end
208
+ end
@@ -0,0 +1,14 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ require 'activerecord-tablelocks'
4
+ require 'rspec'
5
+ require 'rspec/autorun'
6
+ require 'active_record'
7
+
8
+ Dir[File.join(File.dirname(__FILE__),"support/**/*.rb")].each { |f| require f }
9
+
10
+ # For more progress output during the long test, uncomment the line below
11
+ # ActiveRecord::Base.logger = Logger.new(STDOUT)
12
+
13
+ RSpec.configure do |config|
14
+ end
@@ -0,0 +1,14 @@
1
+ class AtomicInteger
2
+ attr_reader :value
3
+
4
+ def initialize
5
+ @value = 0
6
+ @mutex = Mutex.new
7
+ end
8
+
9
+ def increment
10
+ @mutex.synchronize do
11
+ @value = @value + 1
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,39 @@
1
+ config = case ENV["ENGINE"]
2
+ when "postgres"
3
+ {
4
+ "adapter" => "postgresql",
5
+ "database" => ENV["DATABASE"],
6
+ "username" => ENV["USERNAME"],
7
+ "password" => ENV["PASSWORD"],
8
+ "pool" => "15"
9
+ }
10
+ else
11
+ raise "Only Postgres is supported at this time"
12
+ end
13
+
14
+ ActiveRecord::Migration.verbose = false
15
+ ActiveRecord::Base.configurations = { "test" => config }
16
+ ActiveRecord::Base.establish_connection("test")
17
+ ActiveRecord::Base.default_timezone = :utc
18
+
19
+
20
+ ActiveRecord::Schema.define do
21
+ ActiveRecord::Base.connection.tables.each do |table|
22
+ drop_table table
23
+ end
24
+ create_table :pages do |t|
25
+ t.string :name
26
+ t.text :content
27
+ end
28
+ create_table :comments do |t|
29
+ t.string :title
30
+ t.text :content
31
+ end
32
+ create_table :users do |t|
33
+ t.string :name
34
+ end
35
+ create_table :groups do |t|
36
+ t.string :name
37
+ end
38
+
39
+ end
@@ -0,0 +1,6 @@
1
+ def list_of_random_numbers(count)
2
+ Kernel.srand RSpec.configuration.seed
3
+ result = count.times.map { Kernel.rand }
4
+ Kernel.srand # reset random generation
5
+ result
6
+ end
metadata ADDED
@@ -0,0 +1,170 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: activerecord-tablelocks
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Sernin van de Krol
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-08-06 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: activerecord
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 3.2.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: 3.2.0
30
+ - !ruby/object:Gem::Dependency
31
+ name: bundler
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '1.2'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '1.2'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rake
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: railties
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: rspec
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ~>
84
+ - !ruby/object:Gem::Version
85
+ version: 2.14.1
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ~>
92
+ - !ruby/object:Gem::Version
93
+ version: 2.14.1
94
+ description: This gem enables the use of database specific table locks when saving
95
+ or destroying your ActiveRecord objects. This ensures no race conditions exist when
96
+ using e.g. validates_uniqueness_of.
97
+ email:
98
+ - serninpc@paneidos.net
99
+ executables: []
100
+ extensions: []
101
+ extra_rdoc_files: []
102
+ files:
103
+ - .gitignore
104
+ - .rspec
105
+ - Gemfile
106
+ - LICENSE.txt
107
+ - README.md
108
+ - Rakefile
109
+ - activerecord-tablelocks.gemspec
110
+ - lib/activerecord-tablelocks.rb
111
+ - lib/activerecord/tablelocks/activerecord.rb
112
+ - lib/activerecord/tablelocks/activerecord/postgres.rb
113
+ - lib/activerecord/tablelocks/railtie.rb
114
+ - lib/activerecord/tablelocks/version.rb
115
+ - spec/extension_methods_spec.rb
116
+ - spec/locking_targets_spec.rb
117
+ - spec/models/comment.rb
118
+ - spec/models/group.rb
119
+ - spec/models/page.rb
120
+ - spec/models/user.rb
121
+ - spec/normal_behaviour_spec.rb
122
+ - spec/race_conditions_spec.rb
123
+ - spec/spec_helper.rb
124
+ - spec/support/atomic_integer.rb
125
+ - spec/support/database.rb
126
+ - spec/support/random.rb
127
+ homepage: https://github.com/paneidos/activerecord-tablelocks
128
+ licenses:
129
+ - MIT
130
+ post_install_message:
131
+ rdoc_options: []
132
+ require_paths:
133
+ - lib
134
+ required_ruby_version: !ruby/object:Gem::Requirement
135
+ none: false
136
+ requirements:
137
+ - - ! '>='
138
+ - !ruby/object:Gem::Version
139
+ version: '0'
140
+ segments:
141
+ - 0
142
+ hash: 3007498809683035239
143
+ required_rubygems_version: !ruby/object:Gem::Requirement
144
+ none: false
145
+ requirements:
146
+ - - ! '>='
147
+ - !ruby/object:Gem::Version
148
+ version: '0'
149
+ segments:
150
+ - 0
151
+ hash: 3007498809683035239
152
+ requirements: []
153
+ rubyforge_project:
154
+ rubygems_version: 1.8.25
155
+ signing_key:
156
+ specification_version: 3
157
+ summary: Use native table locks of your database for your ActiveRecord models
158
+ test_files:
159
+ - spec/extension_methods_spec.rb
160
+ - spec/locking_targets_spec.rb
161
+ - spec/models/comment.rb
162
+ - spec/models/group.rb
163
+ - spec/models/page.rb
164
+ - spec/models/user.rb
165
+ - spec/normal_behaviour_spec.rb
166
+ - spec/race_conditions_spec.rb
167
+ - spec/spec_helper.rb
168
+ - spec/support/atomic_integer.rb
169
+ - spec/support/database.rb
170
+ - spec/support/random.rb