double_entry 0.0.1.pre → 0.1.0.pre.pre.alpha

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.
Files changed (64) hide show
  1. checksums.yaml +13 -5
  2. data/.gitignore +5 -6
  3. data/.rspec +1 -0
  4. data/.travis.yml +19 -0
  5. data/.yardopts +2 -0
  6. data/Gemfile +0 -1
  7. data/LICENSE.md +19 -0
  8. data/README.md +221 -14
  9. data/Rakefile +12 -0
  10. data/double_entry.gemspec +30 -15
  11. data/gemfiles/Gemfile.rails-3.2.0 +5 -0
  12. data/gemfiles/Gemfile.rails-4.0.0 +5 -0
  13. data/gemfiles/Gemfile.rails-4.1.0 +5 -0
  14. data/lib/active_record/locking_extensions.rb +61 -0
  15. data/lib/double_entry.rb +267 -2
  16. data/lib/double_entry/account.rb +82 -0
  17. data/lib/double_entry/account_balance.rb +31 -0
  18. data/lib/double_entry/aggregate.rb +118 -0
  19. data/lib/double_entry/aggregate_array.rb +65 -0
  20. data/lib/double_entry/configurable.rb +52 -0
  21. data/lib/double_entry/day_range.rb +38 -0
  22. data/lib/double_entry/hour_range.rb +40 -0
  23. data/lib/double_entry/line.rb +147 -0
  24. data/lib/double_entry/line_aggregate.rb +37 -0
  25. data/lib/double_entry/line_check.rb +118 -0
  26. data/lib/double_entry/locking.rb +187 -0
  27. data/lib/double_entry/month_range.rb +92 -0
  28. data/lib/double_entry/reporting.rb +16 -0
  29. data/lib/double_entry/time_range.rb +55 -0
  30. data/lib/double_entry/time_range_array.rb +43 -0
  31. data/lib/double_entry/transfer.rb +70 -0
  32. data/lib/double_entry/version.rb +3 -1
  33. data/lib/double_entry/week_range.rb +99 -0
  34. data/lib/double_entry/year_range.rb +39 -0
  35. data/lib/generators/double_entry/install/install_generator.rb +22 -0
  36. data/lib/generators/double_entry/install/templates/migration.rb +68 -0
  37. data/script/jack_hammer +201 -0
  38. data/script/setup.sh +8 -0
  39. data/spec/active_record/locking_extensions_spec.rb +54 -0
  40. data/spec/double_entry/account_balance_spec.rb +8 -0
  41. data/spec/double_entry/account_spec.rb +23 -0
  42. data/spec/double_entry/aggregate_array_spec.rb +75 -0
  43. data/spec/double_entry/aggregate_spec.rb +168 -0
  44. data/spec/double_entry/double_entry_spec.rb +391 -0
  45. data/spec/double_entry/line_aggregate_spec.rb +8 -0
  46. data/spec/double_entry/line_check_spec.rb +88 -0
  47. data/spec/double_entry/line_spec.rb +72 -0
  48. data/spec/double_entry/locking_spec.rb +154 -0
  49. data/spec/double_entry/month_range_spec.rb +131 -0
  50. data/spec/double_entry/reporting_spec.rb +25 -0
  51. data/spec/double_entry/time_range_array_spec.rb +149 -0
  52. data/spec/double_entry/time_range_spec.rb +43 -0
  53. data/spec/double_entry/week_range_spec.rb +88 -0
  54. data/spec/generators/double_entry/install/install_generator_spec.rb +33 -0
  55. data/spec/spec_helper.rb +47 -0
  56. data/spec/support/accounts.rb +26 -0
  57. data/spec/support/blueprints.rb +34 -0
  58. data/spec/support/database.example.yml +16 -0
  59. data/spec/support/database.travis.yml +18 -0
  60. data/spec/support/double_entry_spec_helper.rb +19 -0
  61. data/spec/support/reporting_configuration.rb +6 -0
  62. data/spec/support/schema.rb +71 -0
  63. metadata +277 -18
  64. data/LICENSE.txt +0 -22
checksums.yaml CHANGED
@@ -1,7 +1,15 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 513e0ed61ff5a07f7a0cf4da5ff19264649a8c8f
4
- data.tar.gz: 2edc588b822646885c8965f5d10d8bc075849eea
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ Y2ZlMDRhZWIzMzM2NDg0MmRlNmNhY2ZkNzY0Nzc3NjhlYWMwNjJiMw==
5
+ data.tar.gz: !binary |-
6
+ NDFhYzZhYmY1Zjg1NzYxMzM4NzA1NzIzMWFkMTJmMDBlYzRjOGE4Yg==
5
7
  SHA512:
6
- metadata.gz: 034b7dacb2d35b0590b74cf4712ac21a54fb201cf8de6fba36a7a0dbd2d840ad519957b0c96ce2d94f50ec59f9c87c2e89e55bf219d23fef1a93600259301ca1
7
- data.tar.gz: 0f22cd5a079652573a20329cf8bf961f0842b3f13c95b8dc2f037b7cf512f0cfc042edbfbe1804fd84ecdf77fc0bfb3df2481c3c39b7f2ce28f2fdd860d417e5
8
+ metadata.gz: !binary |-
9
+ MzZmNTNmOGVlYTgyN2I4MjljZGU5ZGZlODJkMTMyM2JlNWQyMmY3OTQxY2Fh
10
+ NDVjZmYxNmQ1OGNiMTA5ODUwM2U5ZjJlMmEzMjY4MmMyOWY4Y2M0ZmI5YzJj
11
+ OTU5MDQ0MzMxODk4NGNjMGI3Yzk2ZjYxZjc4ODkyODU0MGNhM2Y=
12
+ data.tar.gz: !binary |-
13
+ MDcxOWE3OTFkNzQ3ODk1MTVhYzFjYTlmMWNiNGM4ZmU5N2UzMDc3M2U5Zjkz
14
+ YTU5MDVhM2JmODQ0ZTgzYmVkZjdiNDRhZWE0ZDI0YjQwYjBkNWEzZGEzOGQw
15
+ YWE3OTE2ODlmODc3ZjY2YTE1ODBjYzcxNmY4M2M0MTJkMTVhMWY=
data/.gitignore CHANGED
@@ -3,7 +3,7 @@
3
3
  .bundle
4
4
  .config
5
5
  .yardoc
6
- Gemfile.lock
6
+ .ruby-version
7
7
  InstalledFiles
8
8
  _yardoc
9
9
  coverage
@@ -12,11 +12,10 @@ lib/bundler/man
12
12
  pkg
13
13
  rdoc
14
14
  spec/reports
15
+ spec/support/database.yml
15
16
  test/tmp
16
17
  test/version_tmp
17
18
  tmp
18
- *.bundle
19
- *.so
20
- *.o
21
- *.a
22
- mkmf.log
19
+ bin/
20
+ log/
21
+ /Gemfile.lock
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --colour
data/.travis.yml ADDED
@@ -0,0 +1,19 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1.2
4
+ - 2.0.0
5
+ - 1.9.3
6
+ env:
7
+ - DB=mysql
8
+ - DB=postgres
9
+ before_script:
10
+ - cp spec/support/database.travis.yml spec/support/database.yml
11
+ - mysql -e 'create database double_entry_test;'
12
+ - psql -c 'create database double_entry_test;' -U postgres
13
+ script:
14
+ - rake spec
15
+ - ruby script/jack_hammer -t 2000
16
+ gemfile:
17
+ - gemfiles/Gemfile.rails-3.2.0
18
+ - gemfiles/Gemfile.rails-4.0.0
19
+ - gemfiles/Gemfile.rails-4.1.0
data/.yardopts ADDED
@@ -0,0 +1,2 @@
1
+ --markup markdown
2
+ - LICENSE.md
data/Gemfile CHANGED
@@ -1,4 +1,3 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- # Specify your gem's dependencies in double_entry.gemspec
4
3
  gemspec
data/LICENSE.md ADDED
@@ -0,0 +1,19 @@
1
+ Copyright © 2014 Envato Pty Ltd
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.md CHANGED
@@ -1,29 +1,236 @@
1
1
  # DoubleEntry
2
2
 
3
- TODO: Write a gem description
3
+ [![Gem Version](https://badge.fury.io/rb/double_entry.svg)](http://badge.fury.io/rb/double_entry)
4
+ [![Build Status](https://travis-ci.org/envato/double_entry.svg)](https://travis-ci.org/envato/double_entry)
5
+ [![Code Climate](https://codeclimate.com/github/envato/double_entry.png)](https://codeclimate.com/github/envato/double_entry)
6
+
7
+ ![Show me the Money](http://24.media.tumblr.com/tumblr_m3bwbqNJIG1rrgbmqo1_500.gif)
8
+
9
+ Keep track of all the monies!
10
+
11
+ DoubleEntry is an accounting system based on the principles of a
12
+ [Double-entry Bookkeeping](http://en.wikipedia.org/wiki/Double-entry_bookkeeping_system)
13
+ system. While this gem acts like a double-entry bookkeeping system, as it creates
14
+ two entries in the database for each transfer, it does *not* enforce accounting rules.
15
+
16
+ DoubleEntry uses the Money gem to avoid floating point rounding errors.
17
+
18
+ ## Compatibility
19
+
20
+ DoubleEntry has been tested with:
21
+
22
+ Ruby Versions: 1.9.3, 2.0.0, 2.1.2
23
+
24
+ Rails Versions: Rails 3.2.x, 4.0.x, 4.1.x
25
+
26
+ **Databases Supported:**
27
+ * MySQL
28
+ * PostgreSQL
4
29
 
5
30
  ## Installation
6
31
 
7
- Add this line to your application's Gemfile:
32
+ In your application's `Gemfile`, add:
8
33
 
9
34
  gem 'double_entry'
10
35
 
11
- And then execute:
36
+ Then run:
37
+
38
+ bundle
39
+ rails generate double_entry:install
40
+
41
+ Run migration files:
42
+
43
+ rake db:migrate
44
+
45
+
46
+ ## Interface
47
+
48
+ The entire API for recording financial transactions is available through a few
49
+ methods in the **DoubleEntry** module. For full details on
50
+ what the API provides, please view the documentation on these methods.
51
+
52
+ A configuration file should be used to define a set of accounts, and potential
53
+ transfers between those accounts. See the Configuration section for more details.
54
+
55
+
56
+ ### Accounts
57
+
58
+ Money is kept in Accounts.
59
+
60
+ Each Account has a scope, which is used to subdivide the account into smaller
61
+ accounts. For example, an account can be scoped by user to ensure that each
62
+ user has their own individual account.
63
+
64
+ Scoping accounts is recommended. Unscoped accounts may perform more slowly
65
+ than scoped accounts due to lock contention.
66
+
67
+ To get a particular account:
68
+
69
+ account = DoubleEntry.account(:spending, :scope => user)
70
+
71
+ (This actually returns an Account::Instance object.)
72
+
73
+ See **DoubleEntry::Account** for more info.
74
+
75
+
76
+ ### Balances
77
+
78
+ Calling:
79
+
80
+ account.balance
81
+
82
+ will return the current balance for an account as a Money object.
83
+
84
+
85
+ ### Transfers
86
+
87
+ To transfer money between accounts:
88
+
89
+ DoubleEntry.transfer(20.dollars, :from => account_a, :to => account_b, :code => :purchase)
90
+
91
+ The possible transfers, and their codes, should be defined in the configuration.
92
+
93
+ See **DoubleEntry::Transfer** for more info.
94
+
95
+
96
+ ### Locking
97
+
98
+ If you're doing more than one transfer in a single financial transaction, or
99
+ you're doing other database operations along with the transfer, you'll need to
100
+ manually lock the accounts you're using:
101
+
102
+ DoubleEntry.lock_accounts(account_a, account_b) do
103
+ # Do some other stuff in here...
104
+ DoubleEntry.transfer(20.dollars, :from => account_a, :to => account_b, :code => :purchase)
105
+ end
106
+
107
+ The lock_accounts call generates a database transaction, which must be the
108
+ outermost transaction.
109
+
110
+ See **DoubleEntry::Locking** for more info.
111
+
112
+
113
+ ## Implementation
114
+
115
+ All transfers and balances are stored in the lines table. As this is a
116
+ double-entry accounting system, each transfer generates two lines table
117
+ entries: one for the source account, and one for the destination.
118
+
119
+ Lines table entries also store the running balance for the account. To retrieve
120
+ the current balance for an account, we find the most recent lines table entry
121
+ for it.
122
+
123
+ See **DoubleEntry::Line** for more info.
124
+
125
+ AccountBalance records cache the current balance for each Account, and are used
126
+ to perform database level locking.
127
+
128
+ ## Configuration
129
+
130
+ A configuration file should be used to define a set of accounts, optional scopes on
131
+ the accounts, and permitted transfers between those accounts.
132
+
133
+ The configuration file should be kept in your application's load path. For example,
134
+ *config/initializers/double_entry.rb*
135
+
136
+ For example, the following specifies two accounts, account_a and account_b.
137
+ Each account is scoped by User (where User is an object with an ID), meaning
138
+ each user can have their own account of each type.
139
+
140
+ This configuration also specifies that money can be transferred between the two accounts.
141
+
142
+ ```ruby
143
+ require 'double_entry'
144
+
145
+ DoubleEntry.accounts = DoubleEntry::Account::Set.new.tap do |accounts|
146
+ user_scope = lambda do |user_identifier|
147
+ if user_identifier.is_a?(User)
148
+ user_identifier.id
149
+ else
150
+ user_identifier
151
+ end
152
+ end
153
+
154
+ accounts << DoubleEntry::Account.new(identifier: :savings, scope_identifier: user_scope, positive_only: true)
155
+ accounts << DoubleEntry::Account.new(identifier: :checking, scope_identifier: user_scope)
156
+ end
157
+
158
+ DoubleEntry.transfers = DoubleEntry::Transfer::Set.new.tap do |transfers|
159
+ transfers << DoubleEntry::Transfer.new(from: :checking, to: :savings, code: :deposit)
160
+ transfers << DoubleEntry::Transfer.new(from: :savings, to: :checking, code: :withdraw)
161
+ end
162
+ ```
163
+
164
+ ## Jackhammer
165
+
166
+ Run a concurrency test on the code.
167
+
168
+ This spawns a bunch of processes, and does random transactions between a set
169
+ of accounts, then validates that all the numbers add up at the end.
170
+
171
+ You can also tell out it to flush out the account balances table at regular
172
+ intervals, to validate that new account balances records get created with the
173
+ correct balances from the lines table.
174
+
175
+ ./script/jack_hammer -t 20
176
+ Cleaning out the database...
177
+ Setting up 5 accounts...
178
+ Spawning 20 processes...
179
+ Flushing balances
180
+ Process 1 running 1 transfers...
181
+ Process 0 running 1 transfers...
182
+ Process 3 running 1 transfers...
183
+ Process 2 running 1 transfers...
184
+ Process 4 running 1 transfers...
185
+ Process 5 running 1 transfers...
186
+ Process 6 running 1 transfers...
187
+ Process 7 running 1 transfers...
188
+ Process 8 running 1 transfers...
189
+ Process 9 running 1 transfers...
190
+ Process 10 running 1 transfers...
191
+ Process 11 running 1 transfers...
192
+ Process 12 running 1 transfers...
193
+ Process 13 running 1 transfers...
194
+ Process 14 running 1 transfers...
195
+ Process 16 running 1 transfers...
196
+ Process 15 running 1 transfers...
197
+ Process 17 running 1 transfers...
198
+ Process 19 running 1 transfers...
199
+ Process 18 running 1 transfers...
200
+ Reconciling...
201
+ All the Line records were written, FTW!
202
+ All accounts reconciled, FTW!
203
+ Done successfully :)
204
+
205
+ ## Future Direction
206
+
207
+ No immediate to-do's.
208
+
209
+ ## Development Environment Setup
210
+
211
+ 1. Clone this repo.
212
+
213
+ git clone git@github.com:envato/double_entry.git && cd double_entry
214
+
215
+ 2. Run the included setup script to install the gem dependencies.
216
+
217
+ ./script/setup.sh
218
+
219
+ 3. Install MySQL and PostgreSQL. The tests run using both databases.
220
+ 4. Create a database in MySQL.
221
+
222
+ mysql -u root -e 'create database double_entry_test;'
12
223
 
13
- $ bundle
224
+ 5. Create a database in PostgreSQL.
14
225
 
15
- Or install it yourself as:
226
+ psql -c 'create database double_entry_test;' -U postgres
16
227
 
17
- $ gem install double_entry
228
+ 6. Specify how the tests should connect to the database
18
229
 
19
- ## Usage
230
+ cp spec/support/database.example.yml spec/support/database.yml
231
+ vim spec/support/database.yml
20
232
 
21
- TODO: Write usage instructions here
233
+ 7. Run the tests
22
234
 
23
- ## Contributing
235
+ bundle exec rake
24
236
 
25
- 1. Fork it ( https://github.com/[my-github-username]/double_entry/fork )
26
- 2. Create your feature branch (`git checkout -b my-new-feature`)
27
- 3. Commit your changes (`git commit -am 'Add some feature'`)
28
- 4. Push to the branch (`git push origin my-new-feature`)
29
- 5. Create a new Pull Request
data/Rakefile CHANGED
@@ -1,2 +1,14 @@
1
+ require "rspec/core/rake_task"
1
2
  require "bundler/gem_tasks"
2
3
 
4
+ RSpec::Core::RakeTask.new(:spec) do |t|
5
+ t.verbose = false
6
+ end
7
+
8
+ task :default do
9
+ %w(mysql postgres).each do |db|
10
+ puts "Running tests with `DB=#{db}`"
11
+ ENV['DB'] = db
12
+ Rake::Task["spec"].execute
13
+ end
14
+ end
data/double_entry.gemspec CHANGED
@@ -1,22 +1,37 @@
1
- # coding: utf-8
1
+ # encoding: utf-8
2
+
2
3
  lib = File.expand_path('../lib', __FILE__)
3
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+
4
6
  require 'double_entry/version'
5
7
 
6
- Gem::Specification.new do |spec|
7
- spec.name = "double_entry"
8
- spec.version = DoubleEntry::VERSION
9
- spec.authors = ["Orien Madgwick"]
10
- spec.email = ["_@orien.io"]
11
- spec.summary = "Tools to build your double entry financial ledger"
12
- spec.homepage = "https://github.com/envato/double_entry"
13
- spec.license = "MIT"
8
+ Gem::Specification.new do |gem|
9
+ gem.name = 'double_entry'
10
+ gem.version = DoubleEntry::VERSION
11
+ gem.authors = ['Anthony Sellitti', 'Keith Pitt', 'Martin Jagusch', 'Martin Spickermann', 'Mark Turnley', 'Orien Madgwick', 'Pete Yandall', 'Stephanie Staub']
12
+ gem.email = ['anthony.sellitti@envato.com', 'me@keithpitt.com', '_@mj.io', 'spickemann@gmail.com', 'mark@envato.com', '_@orien.io', 'pete@envato.com', 'staub.steph@gmail.com']
13
+ # gem.description = %q{}
14
+ gem.summary = 'Tools to build your double entry financial ledger'
15
+ gem.homepage = 'https://github.com/envato/double_entry'
16
+
17
+ gem.files = `git ls-files`.split($/)
18
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
19
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
20
+ gem.require_paths = ['lib']
14
21
 
15
- spec.files = `git ls-files -z`.split("\x0")
16
- spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
- spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
- spec.require_paths = ["lib"]
22
+ gem.add_dependency 'money', '>= 5.1.0'
23
+ gem.add_dependency 'encapsulate_as_money'
24
+ gem.add_dependency 'activerecord', '>= 3.2.9'
25
+ gem.add_dependency 'activesupport', '>= 3.0.0'
26
+ gem.add_dependency 'railties', '>= 3.0.0'
19
27
 
20
- spec.add_development_dependency "bundler", "~> 1.6"
21
- spec.add_development_dependency "rake"
28
+ gem.add_development_dependency 'rake'
29
+ gem.add_development_dependency 'mysql2'
30
+ gem.add_development_dependency 'pg'
31
+ gem.add_development_dependency 'rspec'
32
+ gem.add_development_dependency 'rspec-its'
33
+ gem.add_development_dependency 'database_cleaner'
34
+ gem.add_development_dependency 'generator_spec'
35
+ gem.add_development_dependency 'machinist'
36
+ gem.add_development_dependency 'timecop'
22
37
  end
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec :path => '../'
4
+
5
+ gem 'rails', '~>3.2.0'
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec :path => '../'
4
+
5
+ gem 'rails', '~>4.0.0'
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec :path => '../'
4
+
5
+ gem 'rails', '~>4.1.0'
@@ -0,0 +1,61 @@
1
+ # encoding: utf-8
2
+ module ActiveRecord
3
+
4
+ # These methods are available as class methods on ActiveRecord::Base.
5
+ module LockingExtensions
6
+
7
+ # Execute the given block within a database transaction, and retry the
8
+ # transaction from the beginning if a RestartTransaction exception is raised.
9
+ def restartable_transaction(&block)
10
+ begin
11
+ transaction(&block)
12
+ rescue ActiveRecord::RestartTransaction
13
+ retry
14
+ end
15
+ end
16
+
17
+ # Execute the given block, and retry the current restartable transaction if a
18
+ # MySQL deadlock occurs.
19
+ def with_restart_on_deadlock
20
+ begin
21
+ yield
22
+ rescue ActiveRecord::StatementInvalid => exception
23
+ if exception.message =~ /deadlock/i
24
+ raise ActiveRecord::RestartTransaction
25
+ else
26
+ raise
27
+ end
28
+ end
29
+ end
30
+
31
+ # Create the record, but ignore the exception if there's a duplicate.
32
+ def create_ignoring_duplicates!(*args)
33
+ # Error examples:
34
+ # PG::Error: ERROR: deadlock detected
35
+ # Mysql::Error: Deadlock found when trying to get lock
36
+ # PG::Error: ERROR: duplicate key value violates unique constraint
37
+ # Mysql2::Error: Duplicate entry 'keith' for key 'index_users_on_username': INSERT INTO `users...
38
+ begin
39
+ create!(*args)
40
+ rescue ActiveRecord::StatementInvalid => exception
41
+ if exception.message =~ /duplicate/i
42
+ # Just ignore it...someone else has already created the record.
43
+ elsif exception.message =~ /deadlock/i
44
+ # Somebody else is in the midst of creating the record. We'd better
45
+ # retry, so we ensure they're done before we move on.
46
+ retry
47
+ else
48
+ raise
49
+ end
50
+ end
51
+ end
52
+
53
+ end
54
+
55
+ # Raise this inside a restartable_transaction to retry the transaction from the beginning.
56
+ class RestartTransaction < RuntimeError
57
+ end
58
+
59
+ end
60
+
61
+ ActiveRecord::Base.extend(ActiveRecord::LockingExtensions)