double_entry 0.10.0 → 0.10.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.
- checksums.yaml +4 -4
- data/.rspec +2 -1
- data/.rubocop.yml +55 -0
- data/.travis.yml +23 -12
- data/README.md +5 -1
- data/Rakefile +8 -3
- data/double_entry.gemspec +4 -3
- data/lib/active_record/locking_extensions.rb +28 -40
- data/lib/active_record/locking_extensions/log_subscriber.rb +4 -4
- data/lib/double_entry.rb +0 -2
- data/lib/double_entry/account.rb +13 -16
- data/lib/double_entry/account_balance.rb +0 -4
- data/lib/double_entry/balance_calculator.rb +4 -5
- data/lib/double_entry/configurable.rb +0 -2
- data/lib/double_entry/configuration.rb +2 -3
- data/lib/double_entry/errors.rb +2 -2
- data/lib/double_entry/line.rb +13 -16
- data/lib/double_entry/locking.rb +13 -18
- data/lib/double_entry/reporting.rb +2 -3
- data/lib/double_entry/reporting/aggregate.rb +90 -88
- data/lib/double_entry/reporting/aggregate_array.rb +58 -58
- data/lib/double_entry/reporting/day_range.rb +37 -35
- data/lib/double_entry/reporting/hour_range.rb +40 -37
- data/lib/double_entry/reporting/line_aggregate.rb +27 -28
- data/lib/double_entry/reporting/month_range.rb +67 -67
- data/lib/double_entry/reporting/time_range.rb +40 -38
- data/lib/double_entry/reporting/time_range_array.rb +3 -5
- data/lib/double_entry/reporting/week_range.rb +77 -78
- data/lib/double_entry/reporting/year_range.rb +27 -27
- data/lib/double_entry/transfer.rb +14 -15
- data/lib/double_entry/validation/line_check.rb +92 -86
- data/lib/double_entry/version.rb +1 -1
- data/lib/generators/double_entry/install/install_generator.rb +1 -2
- data/lib/generators/double_entry/install/templates/migration.rb +0 -2
- data/script/jack_hammer +1 -1
- data/spec/active_record/locking_extensions_spec.rb +45 -38
- data/spec/double_entry/account_balance_spec.rb +4 -5
- data/spec/double_entry/account_spec.rb +43 -44
- data/spec/double_entry/balance_calculator_spec.rb +6 -8
- data/spec/double_entry/configuration_spec.rb +14 -16
- data/spec/double_entry/line_spec.rb +25 -26
- data/spec/double_entry/locking_spec.rb +34 -39
- data/spec/double_entry/reporting/aggregate_array_spec.rb +8 -10
- data/spec/double_entry/reporting/aggregate_spec.rb +84 -44
- data/spec/double_entry/reporting/line_aggregate_spec.rb +7 -6
- data/spec/double_entry/reporting/month_range_spec.rb +109 -103
- data/spec/double_entry/reporting/time_range_array_spec.rb +145 -135
- data/spec/double_entry/reporting/time_range_spec.rb +36 -35
- data/spec/double_entry/reporting/week_range_spec.rb +82 -76
- data/spec/double_entry/reporting_spec.rb +9 -13
- data/spec/double_entry/transfer_spec.rb +13 -15
- data/spec/double_entry/validation/line_check_spec.rb +73 -79
- data/spec/double_entry_spec.rb +65 -68
- data/spec/generators/double_entry/install/install_generator_spec.rb +7 -10
- data/spec/spec_helper.rb +68 -10
- data/spec/support/accounts.rb +2 -4
- data/spec/support/double_entry_spec_helper.rb +4 -4
- data/spec/support/gemfiles/Gemfile.rails-3.2.x +1 -0
- metadata +31 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a89a7b1a2bfa39a0c51b309f107fc625af9643f0
|
4
|
+
data.tar.gz: 1ac1862d044333c98bb26e78c6ba7d2c508203b4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 235592b9bce67bfdbb4aa474e722a9dd54573ad495797fc1338521d6807e7d51648c7d3eb6258bbde8bdfa8b4b04a5fb6993dca63f21aadd123b9d76d4364dd3
|
7
|
+
data.tar.gz: 28f0afca58a850bca2efc4a28bfa967639f54e383b5b16cc9e5959cef65b5eb8f3d8a72efb0e519f745e9491252fe2ff5c7ca86fa90168e6e41da9a2e1152765
|
data/.rspec
CHANGED
@@ -1 +1,2 @@
|
|
1
|
-
--
|
1
|
+
--color
|
2
|
+
--require spec_helper
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
AllCops:
|
2
|
+
Exclude:
|
3
|
+
- .bundle/**/*
|
4
|
+
- bin/**/*
|
5
|
+
- tmp/**/*
|
6
|
+
- script/jack_hammer
|
7
|
+
- spec/support/schema.rb
|
8
|
+
- lib/generators/double_entry/install/templates/**/*
|
9
|
+
|
10
|
+
Style/AccessModifierIndentation:
|
11
|
+
EnforcedStyle: outdent
|
12
|
+
|
13
|
+
Style/DotPosition:
|
14
|
+
EnforcedStyle: trailing
|
15
|
+
|
16
|
+
Style/GuardClause:
|
17
|
+
Enabled: false
|
18
|
+
|
19
|
+
Style/HashSyntax:
|
20
|
+
EnforcedStyle: hash_rockets
|
21
|
+
|
22
|
+
Style/SpecialGlobalVars:
|
23
|
+
Enabled: false
|
24
|
+
|
25
|
+
Style/TrailingComma:
|
26
|
+
EnforcedStyleForMultiline: comma
|
27
|
+
|
28
|
+
# TODO Enable these checks and fix codes
|
29
|
+
|
30
|
+
Style/ClassVars:
|
31
|
+
Enabled: false
|
32
|
+
|
33
|
+
Style/Documentation:
|
34
|
+
Enabled: false
|
35
|
+
|
36
|
+
Style/DoubleNegation:
|
37
|
+
Enabled: false
|
38
|
+
|
39
|
+
Style/ModuleFunction:
|
40
|
+
Enabled: false
|
41
|
+
|
42
|
+
Metrics/AbcSize:
|
43
|
+
Max: 47 #15
|
44
|
+
|
45
|
+
Metrics/CyclomaticComplexity:
|
46
|
+
Max: 13 #6
|
47
|
+
|
48
|
+
Metrics/LineLength:
|
49
|
+
Max: 185
|
50
|
+
|
51
|
+
Metrics/MethodLength:
|
52
|
+
Max: 30 # 10
|
53
|
+
|
54
|
+
Metrics/PerceivedComplexity:
|
55
|
+
Max: 13 #7
|
data/.travis.yml
CHANGED
@@ -1,12 +1,4 @@
|
|
1
1
|
language: ruby
|
2
|
-
rvm:
|
3
|
-
- 1.9.3
|
4
|
-
- 2.0.0
|
5
|
-
- 2.1.5
|
6
|
-
env:
|
7
|
-
- DB=mysql
|
8
|
-
- DB=postgres
|
9
|
-
- DB=sqlite
|
10
2
|
before_script:
|
11
3
|
- cp spec/support/database.travis.yml spec/support/database.yml
|
12
4
|
- mysql -e 'create database double_entry_test;'
|
@@ -14,7 +6,26 @@ before_script:
|
|
14
6
|
script:
|
15
7
|
- rake spec
|
16
8
|
- ruby script/jack_hammer -t 2000
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
9
|
+
matrix:
|
10
|
+
include:
|
11
|
+
- rvm: 1.9.3
|
12
|
+
gemfile: spec/support/gemfiles/Gemfile.rails-4.2.x
|
13
|
+
env: DB=mysql
|
14
|
+
- rvm: 2.0.0
|
15
|
+
gemfile: spec/support/gemfiles/Gemfile.rails-4.2.x
|
16
|
+
env: DB=mysql
|
17
|
+
- rvm: 2.2.0
|
18
|
+
gemfile: spec/support/gemfiles/Gemfile.rails-4.1.x
|
19
|
+
env: DB=mysql
|
20
|
+
- rvm: 2.2.0
|
21
|
+
gemfile: spec/support/gemfiles/Gemfile.rails-4.2.x
|
22
|
+
env: DB=mysql
|
23
|
+
- rvm: 2.2.0
|
24
|
+
gemfile: spec/support/gemfiles/Gemfile.rails-4.2.x
|
25
|
+
env: DB=sqlite
|
26
|
+
- rvm: 2.2.0
|
27
|
+
gemfile: spec/support/gemfiles/Gemfile.rails-4.2.x
|
28
|
+
env: DB=postgres
|
29
|
+
- rvm: 2.1.5
|
30
|
+
gemfile: spec/support/gemfiles/Gemfile.rails-3.2.x
|
31
|
+
env: DB=mysql
|
data/README.md
CHANGED
@@ -1,7 +1,9 @@
|
|
1
1
|
# DoubleEntry
|
2
2
|
|
3
|
+
|
4
|
+
[](https://github.com/envato/double_entry/blob/master/LICENSE.md)
|
3
5
|
[](http://badge.fury.io/rb/double_entry)
|
4
|
-
[](https://travis-ci.org/envato/double_entry)
|
6
|
+
[](https://travis-ci.org/envato/double_entry)
|
5
7
|
[](https://codeclimate.com/github/envato/double_entry)
|
6
8
|
|
7
9
|

|
@@ -21,7 +23,9 @@ DoubleEntry is tested against:
|
|
21
23
|
|
22
24
|
Ruby
|
23
25
|
* 1.9.3
|
26
|
+
* 2.0.0
|
24
27
|
* 2.1.5
|
28
|
+
* 2.2.0
|
25
29
|
|
26
30
|
Rails
|
27
31
|
* 3.2.x
|
data/Rakefile
CHANGED
@@ -1,14 +1,19 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require 'rspec/core/rake_task'
|
2
|
+
require 'bundler/gem_tasks'
|
3
|
+
require 'rubocop/rake_task'
|
3
4
|
|
4
5
|
RSpec::Core::RakeTask.new(:spec) do |t|
|
5
6
|
t.verbose = false
|
7
|
+
t.ruby_opts = '-w'
|
6
8
|
end
|
7
9
|
|
10
|
+
RuboCop::RakeTask.new(:rubocop)
|
11
|
+
|
8
12
|
task :default do
|
9
13
|
%w(mysql postgres sqlite).each do |db|
|
10
14
|
puts "Running tests with `DB=#{db}`"
|
11
15
|
ENV['DB'] = db
|
12
|
-
Rake::Task[
|
16
|
+
Rake::Task['spec'].execute
|
13
17
|
end
|
18
|
+
Rake::Task['rubocop'].execute
|
14
19
|
end
|
data/double_entry.gemspec
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
-
|
3
2
|
lib = File.expand_path('../lib', __FILE__)
|
4
3
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
4
|
|
@@ -10,12 +9,11 @@ Gem::Specification.new do |gem|
|
|
10
9
|
gem.version = DoubleEntry::VERSION
|
11
10
|
gem.authors = ['Anthony Sellitti', 'Keith Pitt', 'Martin Jagusch', 'Martin Spickermann', 'Mark Turnley', 'Orien Madgwick', 'Pete Yandall', 'Stephanie Staub']
|
12
11
|
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
12
|
gem.summary = 'Tools to build your double entry financial ledger'
|
15
13
|
gem.homepage = 'https://github.com/envato/double_entry'
|
16
14
|
|
17
15
|
gem.files = `git ls-files`.split($/)
|
18
|
-
gem.executables = gem.files.grep(%r{
|
16
|
+
gem.executables = gem.files.grep(%r{bin/}).map { |f| File.basename(f) }
|
19
17
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
20
18
|
gem.require_paths = ['lib']
|
21
19
|
|
@@ -28,10 +26,13 @@ Gem::Specification.new do |gem|
|
|
28
26
|
gem.add_development_dependency 'mysql2'
|
29
27
|
gem.add_development_dependency 'pg'
|
30
28
|
gem.add_development_dependency 'sqlite3'
|
29
|
+
|
31
30
|
gem.add_development_dependency 'rspec'
|
32
31
|
gem.add_development_dependency 'rspec-its'
|
32
|
+
gem.add_development_dependency 'rspec-instafail'
|
33
33
|
gem.add_development_dependency 'database_cleaner'
|
34
34
|
gem.add_development_dependency 'generator_spec'
|
35
35
|
gem.add_development_dependency 'machinist'
|
36
36
|
gem.add_development_dependency 'timecop'
|
37
|
+
gem.add_development_dependency 'rubocop'
|
37
38
|
end
|
@@ -1,34 +1,28 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
-
require
|
2
|
+
require 'active_support/notifications'
|
3
3
|
|
4
4
|
module ActiveRecord
|
5
|
-
|
6
5
|
# These methods are available as class methods on ActiveRecord::Base.
|
7
6
|
module LockingExtensions
|
8
|
-
|
9
7
|
# Execute the given block within a database transaction, and retry the
|
10
8
|
# transaction from the beginning if a RestartTransaction exception is raised.
|
11
9
|
def restartable_transaction(&block)
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
retry
|
16
|
-
end
|
10
|
+
transaction(&block)
|
11
|
+
rescue ActiveRecord::RestartTransaction
|
12
|
+
retry
|
17
13
|
end
|
18
14
|
|
19
15
|
# Execute the given block, and retry the current restartable transaction if a
|
20
16
|
# MySQL deadlock occurs.
|
21
17
|
def with_restart_on_deadlock
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
ActiveSupport::Notifications.publish("deadlock_restart.active_record", :exception => exception)
|
18
|
+
yield
|
19
|
+
rescue ActiveRecord::StatementInvalid => exception
|
20
|
+
if exception.message =~ /deadlock/i || exception.message =~ /database is locked/i
|
21
|
+
ActiveSupport::Notifications.publish('deadlock_restart.active_record', :exception => exception)
|
27
22
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
end
|
23
|
+
raise ActiveRecord::RestartTransaction
|
24
|
+
else
|
25
|
+
raise
|
32
26
|
end
|
33
27
|
end
|
34
28
|
|
@@ -42,23 +36,21 @@ module ActiveRecord
|
|
42
36
|
end
|
43
37
|
end
|
44
38
|
|
45
|
-
|
39
|
+
private
|
46
40
|
|
47
41
|
def ignoring_duplicates
|
48
42
|
# Error examples:
|
49
43
|
# PG::Error: ERROR: duplicate key value violates unique constraint
|
50
44
|
# Mysql2::Error: Duplicate entry 'keith' for key 'index_users_on_username': INSERT INTO `users...
|
51
45
|
# ActiveRecord::RecordNotUnique SQLite3::ConstraintException: column username is not unique: INSERT INTO "users"...
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
ActiveSupport::Notifications.publish("duplicate_ignore.active_record", :exception => exception)
|
46
|
+
yield
|
47
|
+
rescue ActiveRecord::StatementInvalid, ActiveRecord::RecordNotUnique => exception
|
48
|
+
if exception.message =~ /duplicate/i || exception.message =~ /ConstraintException/
|
49
|
+
ActiveSupport::Notifications.publish('duplicate_ignore.active_record', :exception => exception)
|
57
50
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
end
|
51
|
+
# Just ignore it...someone else has already created the record.
|
52
|
+
else
|
53
|
+
raise
|
62
54
|
end
|
63
55
|
end
|
64
56
|
|
@@ -66,18 +58,16 @@ module ActiveRecord
|
|
66
58
|
# Error examples:
|
67
59
|
# PG::Error: ERROR: deadlock detected
|
68
60
|
# Mysql::Error: Deadlock found when trying to get lock
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
ActiveSupport::Notifications.publish("deadlock_retry.active_record", :exception => exception)
|
61
|
+
yield
|
62
|
+
rescue ActiveRecord::StatementInvalid, ActiveRecord::RecordNotUnique => exception
|
63
|
+
if exception.message =~ /deadlock/i || exception.message =~ /database is locked/i
|
64
|
+
# Somebody else is in the midst of creating the record. We'd better
|
65
|
+
# retry, so we ensure they're done before we move on.
|
66
|
+
ActiveSupport::Notifications.publish('deadlock_retry.active_record', :exception => exception)
|
76
67
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
end
|
68
|
+
retry
|
69
|
+
else
|
70
|
+
raise
|
81
71
|
end
|
82
72
|
end
|
83
73
|
end
|
@@ -85,8 +75,6 @@ module ActiveRecord
|
|
85
75
|
# Raise this inside a restartable_transaction to retry the transaction from the beginning.
|
86
76
|
class RestartTransaction < RuntimeError
|
87
77
|
end
|
88
|
-
|
89
78
|
end
|
90
79
|
|
91
|
-
|
92
80
|
ActiveRecord::Base.extend(ActiveRecord::LockingExtensions)
|
@@ -1,21 +1,21 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
-
require
|
2
|
+
require 'active_support/log_subscriber'
|
3
3
|
|
4
4
|
module ActiveRecord
|
5
5
|
module LockingExtensions
|
6
6
|
class LogSubscriber < ActiveSupport::LogSubscriber
|
7
7
|
def deadlock_restart(event)
|
8
|
-
info
|
8
|
+
info 'Deadlock causing restart'
|
9
9
|
debug event[:exception]
|
10
10
|
end
|
11
11
|
|
12
12
|
def deadlock_retry(event)
|
13
|
-
info
|
13
|
+
info 'Deadlock causing retry'
|
14
14
|
debug event[:exception]
|
15
15
|
end
|
16
16
|
|
17
17
|
def duplicate_ignore(event)
|
18
|
-
info
|
18
|
+
info 'Duplicate ignored'
|
19
19
|
debug event[:exception]
|
20
20
|
end
|
21
21
|
|
data/lib/double_entry.rb
CHANGED
@@ -23,9 +23,7 @@ require 'double_entry/validation'
|
|
23
23
|
# This module provides the public interfaces for everything to do with
|
24
24
|
# transferring money around the system.
|
25
25
|
module DoubleEntry
|
26
|
-
|
27
26
|
class << self
|
28
|
-
|
29
27
|
# Get the particular account instance with the provided identifier and
|
30
28
|
# scope.
|
31
29
|
#
|
data/lib/double_entry/account.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
module DoubleEntry
|
3
3
|
class Account
|
4
|
-
|
5
4
|
class << self
|
6
5
|
attr_writer :accounts, :scope_identifier_max_length, :account_identifier_max_length
|
7
6
|
|
@@ -39,18 +38,18 @@ module DoubleEntry
|
|
39
38
|
end
|
40
39
|
|
41
40
|
def find(identifier, scoped)
|
42
|
-
|
41
|
+
found_account = detect do |account|
|
43
42
|
account.identifier == identifier && account.scoped? == scoped
|
44
43
|
end
|
45
|
-
|
46
|
-
|
44
|
+
fail UnknownAccount, "account: #{identifier} scoped?: #{scoped}" unless found_account
|
45
|
+
found_account
|
47
46
|
end
|
48
47
|
|
49
48
|
def <<(account)
|
50
49
|
if any? { |a| a.identifier == account.identifier }
|
51
|
-
|
50
|
+
fail DuplicateAccount
|
52
51
|
else
|
53
|
-
super
|
52
|
+
super
|
54
53
|
end
|
55
54
|
end
|
56
55
|
|
@@ -72,7 +71,7 @@ module DoubleEntry
|
|
72
71
|
when String, Fixnum
|
73
72
|
value
|
74
73
|
else
|
75
|
-
|
74
|
+
fail AccountScopeMismatchError, "Expected instance of `#{@active_record_class}`, received instance of `#{value.class}`"
|
76
75
|
end
|
77
76
|
end
|
78
77
|
end
|
@@ -115,8 +114,8 @@ module DoubleEntry
|
|
115
114
|
self == other
|
116
115
|
end
|
117
116
|
|
118
|
-
def <=>(
|
119
|
-
[scope_identity.to_s, identifier.to_s] <=> [
|
117
|
+
def <=>(other)
|
118
|
+
[scope_identity.to_s, identifier.to_s] <=> [other.scope_identity.to_s, other.identifier.to_s]
|
120
119
|
end
|
121
120
|
|
122
121
|
def hash
|
@@ -135,14 +134,13 @@ module DoubleEntry
|
|
135
134
|
to_s
|
136
135
|
end
|
137
136
|
|
138
|
-
|
137
|
+
private
|
139
138
|
|
140
139
|
def ensure_scope_is_valid
|
141
140
|
identity = scope_identity
|
142
141
|
if identity && identity.length > Account.scope_identifier_max_length
|
143
|
-
|
144
|
-
|
145
|
-
)
|
142
|
+
fail ScopeIdentifierTooLongError,
|
143
|
+
"scope identifier '#{identity}' is too long. Please limit it to #{Account.scope_identifier_max_length} characters."
|
146
144
|
end
|
147
145
|
end
|
148
146
|
end
|
@@ -156,9 +154,8 @@ module DoubleEntry
|
|
156
154
|
@negative_only = args[:negative_only]
|
157
155
|
@currency = args[:currency] || Money.default_currency
|
158
156
|
if identifier.length > Account.account_identifier_max_length
|
159
|
-
|
160
|
-
|
161
|
-
)
|
157
|
+
fail AccountIdentifierTooLongError,
|
158
|
+
"account identifier '#{identifier}' is too long. Please limit it to #{Account.account_identifier_max_length} characters."
|
162
159
|
end
|
163
160
|
end
|
164
161
|
|
@@ -1,6 +1,5 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
module DoubleEntry
|
3
|
-
|
4
3
|
# Account balance records cache the current balance for each account. They
|
5
4
|
# also provide a database representation of an account that we can use to do
|
6
5
|
# DB level locking.
|
@@ -9,7 +8,6 @@ module DoubleEntry
|
|
9
8
|
#
|
10
9
|
# Account balances are created on demand when transfers occur.
|
11
10
|
class AccountBalance < ActiveRecord::Base
|
12
|
-
|
13
11
|
delegate :currency, :to => :account
|
14
12
|
|
15
13
|
def balance
|
@@ -35,7 +33,5 @@ module DoubleEntry
|
|
35
33
|
scope = scope.lock(true) if options[:lock]
|
36
34
|
scope.first
|
37
35
|
end
|
38
|
-
|
39
36
|
end
|
40
|
-
|
41
37
|
end
|