double_entry 1.0.1 → 2.0.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/CHANGELOG.md +432 -0
- data/README.md +36 -9
- data/double_entry.gemspec +20 -48
- data/lib/active_record/locking_extensions.rb +3 -3
- data/lib/active_record/locking_extensions/log_subscriber.rb +1 -1
- data/lib/double_entry/account.rb +38 -45
- data/lib/double_entry/account_balance.rb +18 -1
- data/lib/double_entry/errors.rb +13 -13
- data/lib/double_entry/line.rb +3 -2
- data/lib/double_entry/reporting.rb +26 -38
- data/lib/double_entry/reporting/aggregate.rb +43 -23
- data/lib/double_entry/reporting/aggregate_array.rb +16 -13
- data/lib/double_entry/reporting/line_aggregate.rb +3 -2
- data/lib/double_entry/reporting/line_aggregate_filter.rb +8 -10
- data/lib/double_entry/reporting/line_metadata_filter.rb +33 -0
- data/lib/double_entry/transfer.rb +33 -27
- data/lib/double_entry/validation.rb +1 -0
- data/lib/double_entry/validation/account_fixer.rb +36 -0
- data/lib/double_entry/validation/line_check.rb +22 -40
- data/lib/double_entry/version.rb +1 -1
- data/lib/generators/double_entry/install/install_generator.rb +7 -1
- data/lib/generators/double_entry/install/templates/migration.rb +27 -25
- metadata +33 -243
- data/.gitignore +0 -32
- data/.rspec +0 -2
- data/.travis.yml +0 -29
- data/.yardopts +0 -2
- data/Gemfile +0 -2
- data/Rakefile +0 -15
- data/script/jack_hammer +0 -210
- data/script/setup.sh +0 -8
- data/spec/active_record/locking_extensions_spec.rb +0 -110
- data/spec/double_entry/account_balance_spec.rb +0 -7
- data/spec/double_entry/account_spec.rb +0 -130
- data/spec/double_entry/balance_calculator_spec.rb +0 -88
- data/spec/double_entry/configuration_spec.rb +0 -50
- data/spec/double_entry/line_spec.rb +0 -80
- data/spec/double_entry/locking_spec.rb +0 -214
- data/spec/double_entry/performance/double_entry_performance_spec.rb +0 -32
- data/spec/double_entry/performance/reporting/aggregate_performance_spec.rb +0 -50
- data/spec/double_entry/reporting/aggregate_array_spec.rb +0 -123
- data/spec/double_entry/reporting/aggregate_spec.rb +0 -205
- data/spec/double_entry/reporting/line_aggregate_filter_spec.rb +0 -90
- data/spec/double_entry/reporting/line_aggregate_spec.rb +0 -39
- data/spec/double_entry/reporting/month_range_spec.rb +0 -139
- data/spec/double_entry/reporting/time_range_array_spec.rb +0 -169
- data/spec/double_entry/reporting/time_range_spec.rb +0 -45
- data/spec/double_entry/reporting/week_range_spec.rb +0 -103
- data/spec/double_entry/reporting_spec.rb +0 -181
- data/spec/double_entry/transfer_spec.rb +0 -93
- data/spec/double_entry/validation/line_check_spec.rb +0 -99
- data/spec/double_entry_spec.rb +0 -428
- data/spec/generators/double_entry/install/install_generator_spec.rb +0 -30
- data/spec/spec_helper.rb +0 -118
- data/spec/support/accounts.rb +0 -21
- data/spec/support/blueprints.rb +0 -43
- data/spec/support/database.example.yml +0 -21
- data/spec/support/database.travis.yml +0 -24
- data/spec/support/double_entry_spec_helper.rb +0 -27
- data/spec/support/gemfiles/Gemfile.rails-3.2.x +0 -8
- data/spec/support/gemfiles/Gemfile.rails-4.1.x +0 -6
- data/spec/support/gemfiles/Gemfile.rails-4.2.x +0 -5
- data/spec/support/gemfiles/Gemfile.rails-5.0.x +0 -5
- data/spec/support/performance_helper.rb +0 -26
- data/spec/support/reporting_configuration.rb +0 -6
- data/spec/support/schema.rb +0 -74
@@ -1,88 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
RSpec.describe DoubleEntry::BalanceCalculator do
|
4
|
-
describe '#calculate' do
|
5
|
-
let(:account) { DoubleEntry.account(:test, :scope => scope) }
|
6
|
-
let(:scope) { User.make! }
|
7
|
-
let(:from) { nil }
|
8
|
-
let(:to) { nil }
|
9
|
-
let(:at) { nil }
|
10
|
-
let(:code) { nil }
|
11
|
-
let(:codes) { nil }
|
12
|
-
let(:relation) { double.as_null_object }
|
13
|
-
|
14
|
-
before do
|
15
|
-
allow(DoubleEntry::Line).to receive(:where).and_return(relation)
|
16
|
-
DoubleEntry::BalanceCalculator.calculate(
|
17
|
-
account,
|
18
|
-
:scope => scope,
|
19
|
-
:from => from,
|
20
|
-
:to => to,
|
21
|
-
:at => at,
|
22
|
-
:code => code,
|
23
|
-
:codes => codes,
|
24
|
-
)
|
25
|
-
end
|
26
|
-
|
27
|
-
describe 'what happens with different times' do
|
28
|
-
context 'when we want to sum the lines before a given created_at date' do
|
29
|
-
let(:at) { Time.parse('2014-06-19 15:09:18 +1000') }
|
30
|
-
|
31
|
-
it 'scopes the lines summed to times before (or at) the given time' do
|
32
|
-
expect(relation).to have_received(:where).with(
|
33
|
-
'created_at <= ?', Time.parse('2014-06-19 15:09:18 +1000')
|
34
|
-
)
|
35
|
-
end
|
36
|
-
|
37
|
-
context 'when a time range is also specified' do
|
38
|
-
let(:from) { Time.parse('2014-06-19 10:09:18 +1000') }
|
39
|
-
let(:to) { Time.parse('2014-06-19 20:09:18 +1000') }
|
40
|
-
|
41
|
-
it 'ignores the time range when summing the lines' do
|
42
|
-
expect(relation).to_not have_received(:where).with(
|
43
|
-
:created_at => Time.parse('2014-06-19 10:09:18 +1000')..Time.parse('2014-06-19 20:09:18 +1000'),
|
44
|
-
)
|
45
|
-
expect(relation).to_not have_received(:sum)
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
context 'when we want to sum the lines between a given range' do
|
51
|
-
let(:from) { Time.parse('2014-06-19 10:09:18 +1000') }
|
52
|
-
let(:to) { Time.parse('2014-06-19 20:09:18 +1000') }
|
53
|
-
|
54
|
-
it 'scopes the lines summed to times within the given range' do
|
55
|
-
expect(relation).to have_received(:where).with(
|
56
|
-
:created_at => Time.parse('2014-06-19 10:09:18 +1000')..Time.parse('2014-06-19 20:09:18 +1000'),
|
57
|
-
)
|
58
|
-
expect(relation).to have_received(:sum).with(:amount)
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
context 'when a single code is provided' do
|
64
|
-
let(:code) { 'code1' }
|
65
|
-
|
66
|
-
it 'scopes the lines summed by the given code' do
|
67
|
-
expect(relation).to have_received(:where).with(:code => ['code1'])
|
68
|
-
expect(relation).to have_received(:sum).with(:amount)
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
context 'when a list of codes is provided' do
|
73
|
-
let(:codes) { %w(code1 code2) }
|
74
|
-
|
75
|
-
it 'scopes the lines summed by the given codes' do
|
76
|
-
expect(relation).to have_received(:where).with(:code => %w(code1 code2))
|
77
|
-
expect(relation).to have_received(:sum).with(:amount)
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
context 'when no codes are provided' do
|
82
|
-
it 'does not scope the lines summed by any code' do
|
83
|
-
expect(relation).to_not have_received(:where).with(:code => anything)
|
84
|
-
expect(relation).to_not have_received(:sum).with(:amount)
|
85
|
-
end
|
86
|
-
end
|
87
|
-
end
|
88
|
-
end
|
@@ -1,50 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
RSpec.describe DoubleEntry::Configuration do
|
3
|
-
its(:accounts) { should be_a DoubleEntry::Account::Set }
|
4
|
-
its(:transfers) { should be_a DoubleEntry::Transfer::Set }
|
5
|
-
|
6
|
-
describe 'max lengths' do
|
7
|
-
context 'given a max length has not been set' do
|
8
|
-
its(:code_max_length) { should be 47 }
|
9
|
-
its(:scope_identifier_max_length) { should be 23 }
|
10
|
-
its(:account_identifier_max_length) { should be 31 }
|
11
|
-
end
|
12
|
-
|
13
|
-
context 'given a code max length of 10 has been set' do
|
14
|
-
before { subject.code_max_length = 10 }
|
15
|
-
its(:code_max_length) { should be 10 }
|
16
|
-
end
|
17
|
-
|
18
|
-
context 'given a scope identifier max length of 11 has been set' do
|
19
|
-
before { subject.scope_identifier_max_length = 11 }
|
20
|
-
its(:scope_identifier_max_length) { should be 11 }
|
21
|
-
end
|
22
|
-
|
23
|
-
context 'given an account identifier max length of 9 has been set' do
|
24
|
-
before { subject.account_identifier_max_length = 9 }
|
25
|
-
its(:account_identifier_max_length) { should be 9 }
|
26
|
-
end
|
27
|
-
|
28
|
-
after do
|
29
|
-
subject.code_max_length = nil
|
30
|
-
subject.scope_identifier_max_length = nil
|
31
|
-
subject.account_identifier_max_length = nil
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
describe '#define_accounts' do
|
36
|
-
it 'yields the accounts set' do
|
37
|
-
expect do |block|
|
38
|
-
subject.define_accounts(&block)
|
39
|
-
end.to yield_with_args(be_a DoubleEntry::Account::Set)
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
describe '#define_transfers' do
|
44
|
-
it 'yields the transfers set' do
|
45
|
-
expect do |block|
|
46
|
-
subject.define_transfers(&block)
|
47
|
-
end.to yield_with_args(be_a DoubleEntry::Transfer::Set)
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|
@@ -1,80 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
RSpec.describe DoubleEntry::Line do
|
3
|
-
it 'has a table name prefixed with double_entry_' do
|
4
|
-
expect(DoubleEntry::Line.table_name).to eq 'double_entry_lines'
|
5
|
-
end
|
6
|
-
|
7
|
-
describe 'persistance' do
|
8
|
-
let(:line_to_persist) do
|
9
|
-
DoubleEntry::Line.new(
|
10
|
-
:amount => Money.new(10_00),
|
11
|
-
:balance => Money.zero,
|
12
|
-
:account => account,
|
13
|
-
:partner_account => partner_account,
|
14
|
-
:code => code,
|
15
|
-
)
|
16
|
-
end
|
17
|
-
let(:account) { DoubleEntry.account(:test, :scope => '17') }
|
18
|
-
let(:partner_account) { DoubleEntry.account(:test, :scope => '72') }
|
19
|
-
let(:code) { :test_code }
|
20
|
-
|
21
|
-
subject(:persisted_line) do
|
22
|
-
line_to_persist.save!
|
23
|
-
line_to_persist.reload
|
24
|
-
end
|
25
|
-
|
26
|
-
describe 'attributes' do
|
27
|
-
context 'given code = :the_code' do
|
28
|
-
let(:code) { :the_code }
|
29
|
-
its(:code) { should eq :the_code }
|
30
|
-
end
|
31
|
-
|
32
|
-
context 'given code = nil' do
|
33
|
-
let(:code) { nil }
|
34
|
-
specify { expect { line_to_persist.save! }.to raise_error }
|
35
|
-
end
|
36
|
-
|
37
|
-
context 'given account = :test, 54 ' do
|
38
|
-
let(:account) { DoubleEntry.account(:test, :scope => '54') }
|
39
|
-
its('account.account.identifier') { should eq :test }
|
40
|
-
its('account.scope') { should eq '54' }
|
41
|
-
end
|
42
|
-
|
43
|
-
context 'given partner_account = :test, 91 ' do
|
44
|
-
let(:partner_account) { DoubleEntry.account(:test, :scope => '91') }
|
45
|
-
its('partner_account.account.identifier') { should eq :test }
|
46
|
-
its('partner_account.scope') { should eq '91' }
|
47
|
-
end
|
48
|
-
|
49
|
-
context 'currency' do
|
50
|
-
let(:account) { DoubleEntry.account(:btc_test, :scope => '17') }
|
51
|
-
let(:partner_account) { DoubleEntry.account(:btc_test, :scope => '72') }
|
52
|
-
its(:currency) { should eq 'BTC' }
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
context 'when balance is sent negative' do
|
57
|
-
before { DoubleEntry::Account.accounts.define(:identifier => :a_positive_only_acc, :positive_only => true) }
|
58
|
-
let(:account) { DoubleEntry.account(:a_positive_only_acc) }
|
59
|
-
let(:line) { DoubleEntry::Line.new(:balance => Money.new(-1), :account => account) }
|
60
|
-
|
61
|
-
it 'raises AccountWouldBeSentNegative error' do
|
62
|
-
expect { line.save }.to raise_error DoubleEntry::AccountWouldBeSentNegative
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
context 'when balance is sent positive' do
|
67
|
-
before { DoubleEntry::Account.accounts.define(:identifier => :a_negative_only_acc, :negative_only => true) }
|
68
|
-
let(:account) { DoubleEntry.account(:a_negative_only_acc) }
|
69
|
-
let(:line) { DoubleEntry::Line.new(:balance => Money.new(1), :account => account) }
|
70
|
-
|
71
|
-
it 'raises AccountWouldBeSentPositiveError' do
|
72
|
-
expect { line.save }.to raise_error DoubleEntry::AccountWouldBeSentPositiveError
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
it 'has a table name prefixed with double_entry_' do
|
77
|
-
expect(DoubleEntry::Line.table_name).to eq 'double_entry_lines'
|
78
|
-
end
|
79
|
-
end
|
80
|
-
end
|
@@ -1,214 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
RSpec.describe DoubleEntry::Locking do
|
4
|
-
before do
|
5
|
-
@config_accounts = DoubleEntry.configuration.accounts
|
6
|
-
@config_transfers = DoubleEntry.configuration.transfers
|
7
|
-
DoubleEntry.configuration.accounts = DoubleEntry::Account::Set.new
|
8
|
-
DoubleEntry.configuration.transfers = DoubleEntry::Transfer::Set.new
|
9
|
-
end
|
10
|
-
|
11
|
-
after do
|
12
|
-
DoubleEntry.configuration.accounts = @config_accounts
|
13
|
-
DoubleEntry.configuration.transfers = @config_transfers
|
14
|
-
end
|
15
|
-
|
16
|
-
before do
|
17
|
-
scope = ->(x) { x }
|
18
|
-
|
19
|
-
DoubleEntry.configure do |config|
|
20
|
-
config.define_accounts do |accounts|
|
21
|
-
accounts.define(:identifier => :account_a, :scope_identifier => scope)
|
22
|
-
accounts.define(:identifier => :account_b, :scope_identifier => scope)
|
23
|
-
accounts.define(:identifier => :account_c, :scope_identifier => scope)
|
24
|
-
accounts.define(:identifier => :account_d, :scope_identifier => scope)
|
25
|
-
accounts.define(:identifier => :account_e)
|
26
|
-
end
|
27
|
-
|
28
|
-
config.define_transfers do |transfers|
|
29
|
-
transfers.define(:from => :account_a, :to => :account_b, :code => :test)
|
30
|
-
transfers.define(:from => :account_c, :to => :account_d, :code => :test)
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
@account_a = DoubleEntry.account(:account_a, :scope => '1')
|
35
|
-
@account_b = DoubleEntry.account(:account_b, :scope => '2')
|
36
|
-
@account_c = DoubleEntry.account(:account_c, :scope => '3')
|
37
|
-
@account_d = DoubleEntry.account(:account_d, :scope => '4')
|
38
|
-
@account_e = DoubleEntry.account(:account_e)
|
39
|
-
end
|
40
|
-
|
41
|
-
it 'creates missing account balance records' do
|
42
|
-
expect do
|
43
|
-
DoubleEntry::Locking.lock_accounts(@account_a) {}
|
44
|
-
end.to change(DoubleEntry::AccountBalance, :count).by(1)
|
45
|
-
|
46
|
-
account_balance = DoubleEntry::AccountBalance.find_by_account(@account_a)
|
47
|
-
expect(account_balance).to_not be_nil
|
48
|
-
expect(account_balance.balance).to eq Money.new(0)
|
49
|
-
end
|
50
|
-
|
51
|
-
it 'takes the balance for new account balance records from the lines table' do
|
52
|
-
DoubleEntry::Line.create!(
|
53
|
-
:account => @account_a,
|
54
|
-
:partner_account => @account_b,
|
55
|
-
:amount => Money.new(3_00),
|
56
|
-
:balance => Money.new(3_00),
|
57
|
-
:code => :test,
|
58
|
-
)
|
59
|
-
DoubleEntry::Line.create!(
|
60
|
-
:account => @account_a,
|
61
|
-
:partner_account => @account_b,
|
62
|
-
:amount => Money.new(7_00),
|
63
|
-
:balance => Money.new(10_00),
|
64
|
-
:code => :test,
|
65
|
-
)
|
66
|
-
|
67
|
-
expect do
|
68
|
-
DoubleEntry::Locking.lock_accounts(@account_a) {}
|
69
|
-
end.to change(DoubleEntry::AccountBalance, :count).by(1)
|
70
|
-
|
71
|
-
account_balance = DoubleEntry::AccountBalance.find_by_account(@account_a)
|
72
|
-
expect(account_balance).to_not be_nil
|
73
|
-
expect(account_balance.balance).to eq Money.new(10_00)
|
74
|
-
end
|
75
|
-
|
76
|
-
it 'prohibits locking inside a regular transaction' do
|
77
|
-
expect do
|
78
|
-
DoubleEntry::AccountBalance.transaction do
|
79
|
-
DoubleEntry::Locking.lock_accounts(@account_a, @account_b) do
|
80
|
-
end
|
81
|
-
end
|
82
|
-
end.to raise_error(DoubleEntry::Locking::LockMustBeOutermostTransaction)
|
83
|
-
end
|
84
|
-
|
85
|
-
it 'prohibits a transfer inside a regular transaction' do
|
86
|
-
expect do
|
87
|
-
DoubleEntry::AccountBalance.transaction do
|
88
|
-
DoubleEntry.transfer(Money.new(10_00), :from => @account_a, :to => @account_b, :code => :test)
|
89
|
-
end
|
90
|
-
end.to raise_error(DoubleEntry::Locking::LockMustBeOutermostTransaction)
|
91
|
-
end
|
92
|
-
|
93
|
-
it "allows a transfer inside a lock if we've locked the transaction accounts" do
|
94
|
-
expect do
|
95
|
-
DoubleEntry::Locking.lock_accounts(@account_a, @account_b) do
|
96
|
-
DoubleEntry.transfer(Money.new(10_00), :from => @account_a, :to => @account_b, :code => :test)
|
97
|
-
end
|
98
|
-
end.to_not raise_error
|
99
|
-
end
|
100
|
-
|
101
|
-
it "does not allow a transfer inside a lock if the right locks aren't held" do
|
102
|
-
expect do
|
103
|
-
DoubleEntry::Locking.lock_accounts(@account_a, @account_c) do
|
104
|
-
DoubleEntry.transfer(Money.new(10_00), :from => @account_a, :to => @account_b, :code => :test)
|
105
|
-
end
|
106
|
-
end.to raise_error(DoubleEntry::Locking::LockNotHeld, 'No lock held for account: account_b, scope 2')
|
107
|
-
end
|
108
|
-
|
109
|
-
it 'allows nested locks if the outer lock locks all the accounts' do
|
110
|
-
expect do
|
111
|
-
DoubleEntry::Locking.lock_accounts(@account_a, @account_b) do
|
112
|
-
DoubleEntry::Locking.lock_accounts(@account_a, @account_b) {}
|
113
|
-
end
|
114
|
-
end.to_not raise_error
|
115
|
-
end
|
116
|
-
|
117
|
-
it "prohibits nested locks if the out lock doesn't lock all the accounts" do
|
118
|
-
expect do
|
119
|
-
DoubleEntry::Locking.lock_accounts(@account_a) do
|
120
|
-
DoubleEntry::Locking.lock_accounts(@account_a, @account_b) {}
|
121
|
-
end
|
122
|
-
end.to raise_error(DoubleEntry::Locking::LockNotHeld, 'No lock held for account: account_b, scope 2')
|
123
|
-
end
|
124
|
-
|
125
|
-
it 'rolls back a locking transaction' do
|
126
|
-
DoubleEntry::Locking.lock_accounts(@account_a, @account_b) do
|
127
|
-
DoubleEntry.transfer(Money.new(10_00), :from => @account_a, :to => @account_b, :code => :test)
|
128
|
-
fail ActiveRecord::Rollback
|
129
|
-
end
|
130
|
-
expect(DoubleEntry.balance(@account_a)).to eq Money.new(0)
|
131
|
-
expect(DoubleEntry.balance(@account_b)).to eq Money.new(0)
|
132
|
-
end
|
133
|
-
|
134
|
-
it "rolls back a locking transaction if there's an exception" do
|
135
|
-
expect do
|
136
|
-
DoubleEntry::Locking.lock_accounts(@account_a, @account_b) do
|
137
|
-
DoubleEntry.transfer(Money.new(10_00), :from => @account_a, :to => @account_b, :code => :test)
|
138
|
-
fail 'Yeah, right'
|
139
|
-
end
|
140
|
-
end.to raise_error('Yeah, right')
|
141
|
-
expect(DoubleEntry.balance(@account_a)).to eq Money.new(0)
|
142
|
-
expect(DoubleEntry.balance(@account_b)).to eq Money.new(0)
|
143
|
-
end
|
144
|
-
|
145
|
-
it 'allows locking a scoped account and a non scoped account' do
|
146
|
-
expect do
|
147
|
-
DoubleEntry::Locking.lock_accounts(@account_d, @account_e) {}
|
148
|
-
end.to_not raise_error
|
149
|
-
end
|
150
|
-
|
151
|
-
context 'handling ActiveRecord::StatementInvalid errors' do
|
152
|
-
context 'non lock wait timeout errors' do
|
153
|
-
let(:error) { ActiveRecord::StatementInvalid.new('some other error') }
|
154
|
-
before do
|
155
|
-
allow(DoubleEntry::AccountBalance).to receive(:with_restart_on_deadlock).
|
156
|
-
and_raise(error)
|
157
|
-
end
|
158
|
-
|
159
|
-
it 're-raises the ActiveRecord::StatementInvalid error' do
|
160
|
-
expect do
|
161
|
-
DoubleEntry::Locking.lock_accounts(@account_d, @account_e) {}
|
162
|
-
end.to raise_error(error)
|
163
|
-
end
|
164
|
-
end
|
165
|
-
|
166
|
-
context 'lock wait timeout errors' do
|
167
|
-
before do
|
168
|
-
allow(DoubleEntry::AccountBalance).to receive(:with_restart_on_deadlock).
|
169
|
-
and_raise(ActiveRecord::StatementInvalid, 'lock wait timeout')
|
170
|
-
end
|
171
|
-
|
172
|
-
it 'raises a LockWaitTimeout error' do
|
173
|
-
expect do
|
174
|
-
DoubleEntry::Locking.lock_accounts(@account_d, @account_e) {}
|
175
|
-
end.to raise_error(DoubleEntry::Locking::LockWaitTimeout)
|
176
|
-
end
|
177
|
-
end
|
178
|
-
end
|
179
|
-
|
180
|
-
# sqlite cannot handle these cases so they don't run when DB=sqlite
|
181
|
-
describe 'concurrent locking', :unless => ENV['DB'] == 'sqlite' do
|
182
|
-
it 'allows multiple threads to lock at the same time' do
|
183
|
-
expect do
|
184
|
-
threads = []
|
185
|
-
|
186
|
-
threads << Thread.new do
|
187
|
-
sleep 0.05
|
188
|
-
DoubleEntry::Locking.lock_accounts(@account_a, @account_b) do
|
189
|
-
DoubleEntry.transfer(Money.new(10_00), :from => @account_a, :to => @account_b, :code => :test)
|
190
|
-
end
|
191
|
-
end
|
192
|
-
|
193
|
-
threads << Thread.new do
|
194
|
-
DoubleEntry::Locking.lock_accounts(@account_c, @account_d) do
|
195
|
-
sleep 0.1
|
196
|
-
DoubleEntry.transfer(Money.new(10_00), :from => @account_c, :to => @account_d, :code => :test)
|
197
|
-
end
|
198
|
-
end
|
199
|
-
|
200
|
-
threads.each(&:join)
|
201
|
-
end.to_not raise_error
|
202
|
-
end
|
203
|
-
|
204
|
-
it 'allows multiple threads to lock accounts without balances at the same time' do
|
205
|
-
threads = []
|
206
|
-
expect do
|
207
|
-
threads << Thread.new { DoubleEntry::Locking.lock_accounts(@account_a, @account_b) { sleep 0.1 } }
|
208
|
-
threads << Thread.new { DoubleEntry::Locking.lock_accounts(@account_c, @account_d) { sleep 0.1 } }
|
209
|
-
|
210
|
-
threads.each(&:join)
|
211
|
-
end.to_not raise_error
|
212
|
-
end
|
213
|
-
end
|
214
|
-
end
|
@@ -1,32 +0,0 @@
|
|
1
|
-
module DoubleEntry
|
2
|
-
RSpec.describe DoubleEntry do
|
3
|
-
describe 'transfer performance' do
|
4
|
-
include PerformanceHelper
|
5
|
-
let(:user) { User.make! }
|
6
|
-
let(:amount) { Money.new(10_00) }
|
7
|
-
let(:test) { DoubleEntry.account(:test, :scope => user) }
|
8
|
-
let(:savings) { DoubleEntry.account(:savings, :scope => user) }
|
9
|
-
|
10
|
-
it 'creates a lot of transfers quickly without metadata' do
|
11
|
-
profile_transfers_with_metadata(nil)
|
12
|
-
# local results: 6.44, 5.93, 5.94
|
13
|
-
end
|
14
|
-
|
15
|
-
it 'creates a lot of transfers quickly with metadata' do
|
16
|
-
big_metadata = {}
|
17
|
-
8.times { |i| big_metadata["key#{i}".to_sym] = "value#{i}" }
|
18
|
-
profile_transfers_with_metadata(big_metadata)
|
19
|
-
# local results: 21.2, 21.6, 20.9
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
def profile_transfers_with_metadata(metadata)
|
24
|
-
start_profiling
|
25
|
-
options = { :from => test, :to => savings, :code => :bonus }
|
26
|
-
options[:metadata] = metadata if metadata
|
27
|
-
100.times { Transfer.transfer(amount, options) }
|
28
|
-
profile_name = metadata ? 'transfer-with-metadata' : 'transfer'
|
29
|
-
stop_profiling(profile_name)
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|