activerecord-tablelocks 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/.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