double_entry 0.10.3 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +3 -0
- data/.rubocop.yml +5 -1
- data/README.md +16 -0
- data/double_entry.gemspec +35 -0
- data/lib/double_entry.rb +1 -0
- data/lib/double_entry/line.rb +1 -0
- data/lib/double_entry/line_metadata.rb +18 -0
- data/lib/double_entry/locking.rb +11 -0
- data/lib/double_entry/reporting.rb +26 -14
- data/lib/double_entry/reporting/aggregate.rb +11 -15
- data/lib/double_entry/reporting/aggregate_array.rb +1 -1
- data/lib/double_entry/reporting/line_aggregate.rb +2 -23
- data/lib/double_entry/reporting/line_aggregate_filter.rb +81 -0
- data/lib/double_entry/transfer.rb +42 -25
- data/lib/double_entry/version.rb +1 -1
- data/lib/generators/double_entry/install/templates/migration.rb +10 -0
- data/spec/double_entry/locking_spec.rb +29 -0
- data/spec/double_entry/performance/double_entry_performance_spec.rb +32 -0
- data/spec/double_entry/performance/reporting/aggregate_performance_spec.rb +50 -0
- data/spec/double_entry/reporting/aggregate_array_spec.rb +10 -10
- data/spec/double_entry/reporting/aggregate_spec.rb +57 -114
- data/spec/double_entry/reporting/line_aggregate_filter_spec.rb +90 -0
- data/spec/double_entry/reporting/line_aggregate_spec.rb +35 -7
- data/spec/double_entry/reporting_spec.rb +136 -0
- data/spec/double_entry/transfer_spec.rb +58 -1
- data/spec/support/performance_helper.rb +26 -0
- data/spec/support/schema.rb +9 -0
- metadata +124 -4
data/lib/double_entry/version.rb
CHANGED
@@ -54,9 +54,19 @@ class CreateDoubleEntryTables < ActiveRecord::Migration
|
|
54
54
|
t.text "log"
|
55
55
|
t.timestamps :null => false
|
56
56
|
end
|
57
|
+
|
58
|
+
create_table "double_entry_line_metadata", :force => true do |t|
|
59
|
+
t.integer "line_id", :null => false
|
60
|
+
t.string "key", :limit => 48, :null => false
|
61
|
+
t.string "value", :limit => 64, :null => false
|
62
|
+
t.timestamps :null => false
|
63
|
+
end
|
64
|
+
|
65
|
+
add_index "double_entry_line_metadata", ["line_id", "key", "value"], :name => "lines_meta_line_id_key_value_idx"
|
57
66
|
end
|
58
67
|
|
59
68
|
def self.down
|
69
|
+
drop_table "double_entry_line_metadata"
|
60
70
|
drop_table "double_entry_line_checks"
|
61
71
|
drop_table "double_entry_line_aggregates"
|
62
72
|
drop_table "double_entry_lines"
|
@@ -148,6 +148,35 @@ RSpec.describe DoubleEntry::Locking do
|
|
148
148
|
end.to_not raise_error
|
149
149
|
end
|
150
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
|
+
|
151
180
|
# sqlite cannot handle these cases so they don't run when DB=sqlite
|
152
181
|
describe 'concurrent locking', :unless => ENV['DB'] == 'sqlite' do
|
153
182
|
it 'allows multiple threads to lock at the same time' do
|
@@ -0,0 +1,32 @@
|
|
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
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module DoubleEntry
|
2
|
+
module Reporting
|
3
|
+
RSpec.describe Aggregate 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
|
+
subject(:transfer) { Transfer.transfer(amount, options) }
|
11
|
+
|
12
|
+
context '200 transfers in a single day, half with metadata' do
|
13
|
+
# Surprisingly, the number of transfers makes no difference to the time taken to aggregate them. Some sample results:
|
14
|
+
# 20,000 => 524ms
|
15
|
+
# 10,000 => 573ms
|
16
|
+
# 1,000 => 486ms
|
17
|
+
# 100 => 608ms
|
18
|
+
# 10 => 509ms
|
19
|
+
# 1 => 473ms
|
20
|
+
before do
|
21
|
+
Timecop.freeze Time.local(2015, 06, 30) do
|
22
|
+
100.times { Transfer.transfer(amount, :from => test, :to => savings, :code => :bonus) }
|
23
|
+
100.times { Transfer.transfer(amount, :from => test, :to => savings, :code => :bonus, :metadata => { :country => 'AU', :tax => 'GST' }) }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'calculates monthly all_time ranges quickly without a filter' do
|
28
|
+
profile_aggregation_with_filter(nil)
|
29
|
+
# local results: 517ms, 484ms, 505ms, 482ms, 525ms
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'calculates monthly all_time ranges quickly with a filter' do
|
33
|
+
profile_aggregation_with_filter([:metadata => { :country => 'AU' }])
|
34
|
+
# local results when run independently (caching improves performance when run consecutively):
|
35
|
+
# 655ms, 613ms, 597ms, 607ms, 627ms
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def profile_aggregation_with_filter(filter)
|
40
|
+
start_profiling
|
41
|
+
range = TimeRange.make(:year => 2015, :month => 06, :range_type => :all_time)
|
42
|
+
options = {}
|
43
|
+
options[:filter] = filter if filter
|
44
|
+
Reporting.aggregate(:sum, :savings, :bonus, range, options)
|
45
|
+
profile_name = filter ? 'aggregate-with-metadata' : 'aggregate'
|
46
|
+
stop_profiling(profile_name)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -5,11 +5,11 @@ module DoubleEntry
|
|
5
5
|
let(:start) { nil }
|
6
6
|
let(:finish) { nil }
|
7
7
|
let(:range_type) { 'year' }
|
8
|
-
let(:function) {
|
8
|
+
let(:function) { 'sum' }
|
9
9
|
let(:account) { :savings }
|
10
10
|
let(:transfer_code) { :bonus }
|
11
11
|
subject(:aggregate_array) do
|
12
|
-
|
12
|
+
AggregateArray.new(
|
13
13
|
function,
|
14
14
|
account,
|
15
15
|
transfer_code,
|
@@ -42,17 +42,17 @@ module DoubleEntry
|
|
42
42
|
|
43
43
|
context 'and some aggregates were created previously' do
|
44
44
|
before do
|
45
|
-
|
46
|
-
|
47
|
-
allow(
|
45
|
+
Aggregate.formatted_amount(function, account, transfer_code, years[0])
|
46
|
+
Aggregate.formatted_amount(function, account, transfer_code, years[1])
|
47
|
+
allow(Aggregate).to receive(:formatted_amount)
|
48
48
|
end
|
49
49
|
|
50
|
-
it 'only asks
|
51
|
-
expect(
|
52
|
-
expect(
|
50
|
+
it 'only asks Aggregate for the non-existent ones' do
|
51
|
+
expect(Aggregate).not_to receive(:formatted_amount).with(function, account, transfer_code, years[0], :filter => nil)
|
52
|
+
expect(Aggregate).not_to receive(:formatted_amount).with(function, account, transfer_code, years[1], :filter => nil)
|
53
53
|
|
54
|
-
expect(
|
55
|
-
expect(
|
54
|
+
expect(Aggregate).to receive(:formatted_amount).with(function, account, transfer_code, years[2], :filter => nil)
|
55
|
+
expect(Aggregate).to receive(:formatted_amount).with(function, account, transfer_code, years[3], :filter => nil)
|
56
56
|
aggregate_array
|
57
57
|
end
|
58
58
|
end
|
@@ -33,166 +33,113 @@ module DoubleEntry
|
|
33
33
|
end
|
34
34
|
|
35
35
|
it 'should store the aggregate for quick retrieval' do
|
36
|
-
Aggregate.new(:sum, :savings, :bonus,
|
36
|
+
Aggregate.new(:sum, :savings, :bonus, TimeRange.make(:year => 2009, :month => 10)).amount
|
37
37
|
expect(LineAggregate.count).to eq 1
|
38
38
|
end
|
39
39
|
|
40
40
|
it 'should only store the aggregate once if it is requested more than once' do
|
41
|
-
Aggregate.new(:sum, :savings, :bonus,
|
42
|
-
Aggregate.new(:sum, :savings, :bonus,
|
43
|
-
Aggregate.new(:sum, :savings, :bonus,
|
41
|
+
Aggregate.new(:sum, :savings, :bonus, TimeRange.make(:year => 2009, :month => 9)).amount
|
42
|
+
Aggregate.new(:sum, :savings, :bonus, TimeRange.make(:year => 2009, :month => 9)).amount
|
43
|
+
Aggregate.new(:sum, :savings, :bonus, TimeRange.make(:year => 2009, :month => 10)).amount
|
44
44
|
expect(LineAggregate.count).to eq 2
|
45
45
|
end
|
46
46
|
|
47
47
|
it 'should calculate the complete year correctly' do
|
48
|
-
|
49
|
-
|
50
|
-
:sum, :savings, :bonus,
|
51
|
-
:range => TimeRange.make(:year => 2009)
|
52
|
-
).formatted_amount,
|
53
|
-
).to eq Money.new(200_00)
|
48
|
+
amount = Aggregate.new(:sum, :savings, :bonus, TimeRange.make(:year => 2009)).formatted_amount
|
49
|
+
expect(amount).to eq Money.new(200_00)
|
54
50
|
end
|
55
51
|
|
56
52
|
it 'should calculate seperate months correctly' do
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
).to eq Money.new(110_00)
|
63
|
-
expect(
|
64
|
-
Aggregate.new(
|
65
|
-
:sum, :savings, :bonus,
|
66
|
-
:range => TimeRange.make(:year => 2009, :month => 11)
|
67
|
-
).formatted_amount,
|
68
|
-
).to eq Money.new(90_00)
|
53
|
+
amount = Aggregate.new(:sum, :savings, :bonus, TimeRange.make(:year => 2009, :month => 10)).formatted_amount
|
54
|
+
expect(amount).to eq Money.new(110_00)
|
55
|
+
|
56
|
+
amount = Aggregate.new(:sum, :savings, :bonus, TimeRange.make(:year => 2009, :month => 11)).formatted_amount
|
57
|
+
expect(amount).to eq Money.new(90_00)
|
69
58
|
end
|
70
59
|
|
71
60
|
it 'should calculate seperate weeks correctly' do
|
72
61
|
# Week 40 - Mon Sep 28, 2009 to Sun Oct 4 2009
|
73
|
-
|
74
|
-
|
75
|
-
:sum, :savings, :bonus,
|
76
|
-
:range => TimeRange.make(:year => 2009, :week => 40)
|
77
|
-
).formatted_amount,
|
78
|
-
).to eq Money.new(60_00)
|
62
|
+
amount = Aggregate.new(:sum, :savings, :bonus, TimeRange.make(:year => 2009, :week => 40)).formatted_amount
|
63
|
+
expect(amount).to eq Money.new(60_00)
|
79
64
|
end
|
80
65
|
|
81
66
|
it 'should calculate seperate days correctly' do
|
82
67
|
# 1 Nov 2009
|
83
|
-
|
84
|
-
|
85
|
-
:sum, :savings, :bonus,
|
86
|
-
:range => TimeRange.make(:year => 2009, :week => 44, :day => 7)
|
87
|
-
).formatted_amount,
|
88
|
-
).to eq Money.new(90_00)
|
68
|
+
amount = Aggregate.new(:sum, :savings, :bonus, TimeRange.make(:year => 2009, :week => 44, :day => 7)).formatted_amount
|
69
|
+
expect(amount).to eq Money.new(90_00)
|
89
70
|
end
|
90
71
|
|
91
72
|
it 'should calculate seperate hours correctly' do
|
92
73
|
# 1 Nov 2009
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
).formatted_amount,
|
98
|
-
).to eq Money.new(40_00)
|
99
|
-
expect(
|
100
|
-
Aggregate.new(
|
101
|
-
:sum, :savings, :bonus,
|
102
|
-
:range => TimeRange.make(:year => 2009, :week => 44, :day => 7, :hour => 1)
|
103
|
-
).formatted_amount,
|
104
|
-
).to eq Money.new(50_00)
|
74
|
+
amount = Aggregate.new(:sum, :savings, :bonus, TimeRange.make(:year => 2009, :week => 44, :day => 7, :hour => 0)).formatted_amount
|
75
|
+
expect(amount).to eq Money.new(40_00)
|
76
|
+
amount = Aggregate.new(:sum, :savings, :bonus, TimeRange.make(:year => 2009, :week => 44, :day => 7, :hour => 1)).formatted_amount
|
77
|
+
expect(amount).to eq Money.new(50_00)
|
105
78
|
end
|
106
79
|
|
107
80
|
it 'should calculate, but not store aggregates when the time range is still current' do
|
108
81
|
Timecop.freeze Time.local(2009, 11, 21) do
|
109
|
-
|
110
|
-
|
111
|
-
:sum, :savings, :bonus,
|
112
|
-
:range => TimeRange.make(:year => 2009, :month => 11)
|
113
|
-
).formatted_amount,
|
114
|
-
).to eq Money.new(90_00)
|
82
|
+
amount = Aggregate.new(:sum, :savings, :bonus, TimeRange.make(:year => 2009, :month => 11)).formatted_amount
|
83
|
+
expect(amount).to eq Money.new(90_00)
|
115
84
|
expect(LineAggregate.count).to eq 0
|
116
85
|
end
|
117
86
|
end
|
118
87
|
|
119
88
|
it 'should calculate, but not store aggregates when the time range is in the future' do
|
120
89
|
Timecop.freeze Time.local(2009, 11, 21) do
|
121
|
-
|
122
|
-
|
123
|
-
:sum, :savings, :bonus,
|
124
|
-
:range => TimeRange.make(:year => 2009, :month => 12)
|
125
|
-
).formatted_amount,
|
126
|
-
).to eq Money.new(0)
|
90
|
+
amount = Aggregate.new(:sum, :savings, :bonus, TimeRange.make(:year => 2009, :month => 12)).formatted_amount
|
91
|
+
expect(amount).to eq Money.new(0)
|
127
92
|
expect(LineAggregate.count).to eq 0
|
128
93
|
end
|
129
94
|
end
|
130
95
|
|
131
96
|
it 'should calculate monthly all_time ranges correctly' do
|
132
|
-
|
133
|
-
|
134
|
-
:sum, :savings, :bonus,
|
135
|
-
:range => TimeRange.make(:year => 2009, :month => 12, :range_type => :all_time)
|
136
|
-
).formatted_amount,
|
137
|
-
).to eq Money.new(200_00)
|
97
|
+
amount = Aggregate.new(:sum, :savings, :bonus, TimeRange.make(:year => 2009, :month => 12, :range_type => :all_time)).formatted_amount
|
98
|
+
expect(amount).to eq Money.new(200_00)
|
138
99
|
end
|
139
100
|
|
140
101
|
it 'calculates the average monthly all_time ranges correctly' do
|
141
|
-
|
142
|
-
|
143
|
-
:average, :savings, :bonus,
|
144
|
-
:range => TimeRange.make(:year => 2009, :month => 12, :range_type => :all_time)
|
145
|
-
).formatted_amount,
|
146
|
-
).to eq expected_monthly_average
|
102
|
+
amount = Aggregate.new(:average, :savings, :bonus, TimeRange.make(:year => 2009, :month => 12, :range_type => :all_time)).formatted_amount
|
103
|
+
expect(amount).to eq expected_monthly_average
|
147
104
|
end
|
148
105
|
|
149
106
|
it 'returns the correct count for weekly all_time ranges correctly' do
|
150
|
-
|
151
|
-
|
152
|
-
:count, :savings, :bonus,
|
153
|
-
:range => TimeRange.make(:year => 2009, :month => 12, :range_type => :all_time)
|
154
|
-
).formatted_amount,
|
155
|
-
).to eq 5
|
107
|
+
amount = Aggregate.new(:count, :savings, :bonus, TimeRange.make(:year => 2009, :month => 12, :range_type => :all_time)).formatted_amount
|
108
|
+
expect(amount).to eq 5
|
156
109
|
end
|
157
110
|
|
158
111
|
it 'should calculate weekly all_time ranges correctly' do
|
159
|
-
|
160
|
-
|
161
|
-
:sum, :savings, :bonus,
|
162
|
-
:range => TimeRange.make(:year => 2009, :week => 43, :range_type => :all_time)
|
163
|
-
).formatted_amount,
|
164
|
-
).to eq Money.new(110_00)
|
112
|
+
amount = Aggregate.new(:sum, :savings, :bonus, TimeRange.make(:year => 2009, :week => 43, :range_type => :all_time)).formatted_amount
|
113
|
+
expect(amount).to eq Money.new(110_00)
|
165
114
|
end
|
166
115
|
|
167
116
|
it 'calculates the average weekly all_time ranges correctly' do
|
168
|
-
|
169
|
-
|
170
|
-
:average, :savings, :bonus,
|
171
|
-
:range => TimeRange.make(:year => 2009, :week => 43, :range_type => :all_time)
|
172
|
-
).formatted_amount,
|
173
|
-
).to eq expected_weekly_average
|
117
|
+
amount = Aggregate.new(:average, :savings, :bonus, TimeRange.make(:year => 2009, :week => 43, :range_type => :all_time)).formatted_amount
|
118
|
+
expect(amount).to eq expected_weekly_average
|
174
119
|
end
|
175
120
|
|
176
121
|
it 'returns the correct count for weekly all_time ranges correctly' do
|
177
|
-
|
178
|
-
|
179
|
-
:count, :savings, :bonus,
|
180
|
-
:range => TimeRange.make(:year => 2009, :week => 43, :range_type => :all_time)
|
181
|
-
).formatted_amount,
|
182
|
-
).to eq 3
|
122
|
+
amount = Aggregate.new(:count, :savings, :bonus, TimeRange.make(:year => 2009, :week => 43, :range_type => :all_time)).formatted_amount
|
123
|
+
expect(amount).to eq 3
|
183
124
|
end
|
184
125
|
|
185
126
|
it 'raises an AggregateFunctionNotSupported exception' do
|
186
127
|
expect do
|
187
128
|
Aggregate.new(
|
188
|
-
:not_supported_calculation, :savings, :bonus,
|
189
|
-
:range => TimeRange.make(:year => 2009, :week => 43, :range_type => :all_time)
|
129
|
+
:not_supported_calculation, :savings, :bonus, TimeRange.make(:year => 2009, :week => 43, :range_type => :all_time)
|
190
130
|
).amount
|
191
131
|
end.to raise_error(AggregateFunctionNotSupported)
|
192
132
|
end
|
193
133
|
|
194
134
|
context 'filters' do
|
195
135
|
let(:range) { TimeRange.make(:year => 2011, :month => 10) }
|
136
|
+
let(:filter) do
|
137
|
+
[
|
138
|
+
:scope => {
|
139
|
+
:name => :test_filter,
|
140
|
+
},
|
141
|
+
]
|
142
|
+
end
|
196
143
|
|
197
144
|
DoubleEntry::Line.class_eval do
|
198
145
|
scope :test_filter, -> { where(:amount => 10_00) }
|
@@ -210,36 +157,35 @@ module DoubleEntry
|
|
210
157
|
|
211
158
|
it 'saves filtered aggregations' do
|
212
159
|
expect do
|
213
|
-
Aggregate.new(:sum, :savings, :bonus,
|
160
|
+
Aggregate.new(:sum, :savings, :bonus, range, :filter => filter).amount
|
214
161
|
end.to change { LineAggregate.count }.by 1
|
215
162
|
end
|
216
163
|
|
217
164
|
it 'saves filtered aggregation only once for a range' do
|
218
165
|
expect do
|
219
|
-
Aggregate.new(:sum, :savings, :bonus,
|
220
|
-
Aggregate.new(:sum, :savings, :bonus,
|
166
|
+
Aggregate.new(:sum, :savings, :bonus, range, :filter => filter).amount
|
167
|
+
Aggregate.new(:sum, :savings, :bonus, range, :filter => filter).amount
|
221
168
|
end.to change { LineAggregate.count }.by 1
|
222
169
|
end
|
223
170
|
|
224
171
|
it 'saves filtered aggregations and non filtered aggregations separately' do
|
225
172
|
expect do
|
226
|
-
Aggregate.new(:sum, :savings, :bonus,
|
227
|
-
Aggregate.new(:sum, :savings, :bonus,
|
173
|
+
Aggregate.new(:sum, :savings, :bonus, range, :filter => filter).amount
|
174
|
+
Aggregate.new(:sum, :savings, :bonus, range).amount
|
228
175
|
end.to change { LineAggregate.count }.by 2
|
229
176
|
end
|
230
177
|
|
231
178
|
it 'loads the correct saved aggregation' do
|
232
179
|
# cache the results for filtered and unfiltered aggregations
|
233
|
-
Aggregate.new(:sum, :savings, :bonus,
|
234
|
-
Aggregate.new(:sum, :savings, :bonus,
|
180
|
+
Aggregate.new(:sum, :savings, :bonus, range, :filter => filter).amount
|
181
|
+
Aggregate.new(:sum, :savings, :bonus, range).amount
|
235
182
|
|
236
183
|
# ensure a second call loads the correct cached value
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
).to eq Money.new(19_00)
|
184
|
+
amount = Aggregate.new(:sum, :savings, :bonus, range, :filter => filter).formatted_amount
|
185
|
+
expect(amount).to eq Money.new(10_00)
|
186
|
+
|
187
|
+
amount = Aggregate.new(:sum, :savings, :bonus, range).formatted_amount
|
188
|
+
expect(amount).to eq Money.new(19_00)
|
243
189
|
end
|
244
190
|
end
|
245
191
|
end
|
@@ -251,11 +197,8 @@ module DoubleEntry
|
|
251
197
|
end
|
252
198
|
|
253
199
|
it 'should calculate the sum in the correct currency' do
|
254
|
-
|
255
|
-
|
256
|
-
:sum, :btc_savings, :btc_test_transfer, :range => TimeRange.make(:year => Time.now.year)
|
257
|
-
).formatted_amount,
|
258
|
-
).to eq(Money.new(300_000_000, :btc))
|
200
|
+
amount = Aggregate.new(:sum, :btc_savings, :btc_test_transfer, TimeRange.make(:year => Time.now.year)).formatted_amount
|
201
|
+
expect(amount).to eq(Money.new(300_000_000, :btc))
|
259
202
|
end
|
260
203
|
end
|
261
204
|
end
|