subledger 0.7.7
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +9 -0
- data/LICENSE.txt +12 -0
- data/README.md +136 -0
- data/Rakefile +5 -0
- data/bin/subledger +6 -0
- data/lib/subledger/actor.rb +32 -0
- data/lib/subledger/collection_name.rb +25 -0
- data/lib/subledger/domain/account.rb +168 -0
- data/lib/subledger/domain/balance.rb +102 -0
- data/lib/subledger/domain/book.rb +111 -0
- data/lib/subledger/domain/category.rb +157 -0
- data/lib/subledger/domain/control.rb +180 -0
- data/lib/subledger/domain/formatters.rb +31 -0
- data/lib/subledger/domain/identity.rb +159 -0
- data/lib/subledger/domain/journal_entry.rb +293 -0
- data/lib/subledger/domain/key.rb +113 -0
- data/lib/subledger/domain/line.rb +272 -0
- data/lib/subledger/domain/org.rb +110 -0
- data/lib/subledger/domain/report.rb +247 -0
- data/lib/subledger/domain/report_rendering.rb +233 -0
- data/lib/subledger/domain/roles/activatable.rb +11 -0
- data/lib/subledger/domain/roles/archivable.rb +11 -0
- data/lib/subledger/domain/roles/attributable.rb +14 -0
- data/lib/subledger/domain/roles/collectable.rb +175 -0
- data/lib/subledger/domain/roles/creatable.rb +58 -0
- data/lib/subledger/domain/roles/describable.rb +33 -0
- data/lib/subledger/domain/roles/describable_report_rendering.rb +50 -0
- data/lib/subledger/domain/roles/identifiable.rb +15 -0
- data/lib/subledger/domain/roles/postable.rb +54 -0
- data/lib/subledger/domain/roles/progressable.rb +11 -0
- data/lib/subledger/domain/roles/readable.rb +34 -0
- data/lib/subledger/domain/roles/restable.rb +69 -0
- data/lib/subledger/domain/roles/storable.rb +30 -0
- data/lib/subledger/domain/roles/timeable.rb +18 -0
- data/lib/subledger/domain/roles/updatable.rb +35 -0
- data/lib/subledger/domain/roles/versionable.rb +35 -0
- data/lib/subledger/domain/roles.rb +16 -0
- data/lib/subledger/domain/value/credit.rb +16 -0
- data/lib/subledger/domain/value/debit.rb +16 -0
- data/lib/subledger/domain/value/zero.rb +24 -0
- data/lib/subledger/domain/value.rb +111 -0
- data/lib/subledger/domain.rb +95 -0
- data/lib/subledger/exception_handler.rb +65 -0
- data/lib/subledger/interface/client.rb +295 -0
- data/lib/subledger/interface/dispatcher.rb +20 -0
- data/lib/subledger/interface.rb +2 -0
- data/lib/subledger/path.rb +106 -0
- data/lib/subledger/rest.rb +128 -0
- data/lib/subledger/server.rb +3 -0
- data/lib/subledger/store/api/errors.rb +95 -0
- data/lib/subledger/store/api/roles/activate.rb +21 -0
- data/lib/subledger/store/api/roles/archive.rb +21 -0
- data/lib/subledger/store/api/roles/balance.rb +39 -0
- data/lib/subledger/store/api/roles/categories.rb +51 -0
- data/lib/subledger/store/api/roles/collect.rb +58 -0
- data/lib/subledger/store/api/roles/create.rb +26 -0
- data/lib/subledger/store/api/roles/create_and_post.rb +35 -0
- data/lib/subledger/store/api/roles/create_identity.rb +39 -0
- data/lib/subledger/store/api/roles/create_line.rb +24 -0
- data/lib/subledger/store/api/roles/first_and_last_line.rb +31 -0
- data/lib/subledger/store/api/roles/post.rb +25 -0
- data/lib/subledger/store/api/roles/progress.rb +21 -0
- data/lib/subledger/store/api/roles/read.rb +19 -0
- data/lib/subledger/store/api/roles/reports.rb +77 -0
- data/lib/subledger/store/api/roles/update.rb +24 -0
- data/lib/subledger/store/api/store.rb +103 -0
- data/lib/subledger/store/api.rb +20 -0
- data/lib/subledger/store.rb +236 -0
- data/lib/subledger/supervisor.rb +21 -0
- data/lib/subledger/uuid.rb +52 -0
- data/lib/subledger/version.rb +4 -0
- data/lib/subledger.rb +234 -0
- data/spec/spec_helper.rb +77 -0
- data/spec/subledger_account_spec.rb +354 -0
- data/spec/subledger_book_spec.rb +130 -0
- data/spec/subledger_category_spec.rb +203 -0
- data/spec/subledger_control_spec.rb +43 -0
- data/spec/subledger_identity_spec.rb +47 -0
- data/spec/subledger_journal_entry_spec.rb +417 -0
- data/spec/subledger_key_spec.rb +43 -0
- data/spec/subledger_org_spec.rb +68 -0
- data/spec/subledger_report_spec.rb +295 -0
- data/spec/subledger_spec.rb +101 -0
- data/subledger.gemspec +52 -0
- metadata +205 -0
@@ -0,0 +1,25 @@
|
|
1
|
+
module Subledger
|
2
|
+
module Store
|
3
|
+
module Api
|
4
|
+
module Post
|
5
|
+
def post postable_journal_entry
|
6
|
+
path = Path.for_entity :anchor => postable_journal_entry
|
7
|
+
|
8
|
+
begin
|
9
|
+
json_body = http.post do |req|
|
10
|
+
req.url path
|
11
|
+
|
12
|
+
unless postable_journal_entry.post_delay.zero?
|
13
|
+
req.headers['X-Subledger-Post-Slowly'] = 'true'
|
14
|
+
end
|
15
|
+
end.body
|
16
|
+
rescue Exception => e
|
17
|
+
raise PostError, "Cannot post #{postable_journal_entry}: #{e}"
|
18
|
+
end
|
19
|
+
|
20
|
+
new_or_initialize json_body, postable_journal_entry
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Subledger
|
2
|
+
module Store
|
3
|
+
module Api
|
4
|
+
module Progress
|
5
|
+
def progress progressable
|
6
|
+
|
7
|
+
path = Path.for_entity( :anchor => progressable ) + '/progress'
|
8
|
+
|
9
|
+
begin
|
10
|
+
response_hash = parse_json(
|
11
|
+
http.get( path ).body )
|
12
|
+
rescue Exception => e
|
13
|
+
raise ProgressError, "Cannot progress #{progressable}: #{e}"
|
14
|
+
end
|
15
|
+
|
16
|
+
response_hash['progress']['percentage'].to_i
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Subledger
|
2
|
+
module Store
|
3
|
+
module Api
|
4
|
+
module Read
|
5
|
+
def read readable
|
6
|
+
path = Path.for_entity :anchor => readable
|
7
|
+
|
8
|
+
begin
|
9
|
+
json_body = http.get( path ).body
|
10
|
+
rescue Exception => e
|
11
|
+
raise ReadError, "Cannot read #{readable}: #{e}"
|
12
|
+
end
|
13
|
+
|
14
|
+
new_or_initialize json_body, readable
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module Subledger
|
2
|
+
module Store
|
3
|
+
module Api
|
4
|
+
module Reports
|
5
|
+
def attach_category_to_report args
|
6
|
+
report = args[:report]
|
7
|
+
category = args[:category]
|
8
|
+
parent = args[:parent]
|
9
|
+
|
10
|
+
path = Path.for_entity( :anchor => report ) + '/attach'
|
11
|
+
|
12
|
+
attach_post_hash = { 'category' => category.id }
|
13
|
+
|
14
|
+
attach_post_hash['parent'] = parent.id unless parent.nil?
|
15
|
+
|
16
|
+
begin
|
17
|
+
json_body = http.post do |req|
|
18
|
+
req.url path
|
19
|
+
req.body = attach_post_hash
|
20
|
+
end.body
|
21
|
+
rescue Exception => e
|
22
|
+
raise AttachError, "Cannot attach #{category}: #{e}"
|
23
|
+
end
|
24
|
+
|
25
|
+
category
|
26
|
+
end
|
27
|
+
|
28
|
+
def detach_category_from_report args
|
29
|
+
category = args[:category]
|
30
|
+
report = args[:report]
|
31
|
+
|
32
|
+
path = Path.for_entity( :anchor => report ) + '/detach'
|
33
|
+
|
34
|
+
detach_post_hash = { 'category' => category.id }
|
35
|
+
|
36
|
+
begin
|
37
|
+
json_body = http.post do |req|
|
38
|
+
req.url path
|
39
|
+
req.body = detach_post_hash
|
40
|
+
end.body
|
41
|
+
rescue Exception => e
|
42
|
+
raise DetachError, "Cannot detach #{category}: #{e}"
|
43
|
+
end
|
44
|
+
|
45
|
+
category
|
46
|
+
end
|
47
|
+
|
48
|
+
def render args
|
49
|
+
at = args[:at].iso8601
|
50
|
+
|
51
|
+
building_report_rendering = args[:building_report_rendering]
|
52
|
+
report = building_report_rendering.report
|
53
|
+
|
54
|
+
path = Path.for_entity( :anchor => report ) + "/render?at=#{at}"
|
55
|
+
|
56
|
+
begin
|
57
|
+
json_body = http.post do |req|
|
58
|
+
req.url path
|
59
|
+
end.body
|
60
|
+
rescue Exception => e
|
61
|
+
raise ReportError, "Cannot render #{report}: #{e}"
|
62
|
+
end
|
63
|
+
|
64
|
+
new_or_initialize json_body, building_report_rendering
|
65
|
+
end
|
66
|
+
|
67
|
+
def collect_categories_for_report report, &block
|
68
|
+
raise ReportError, 'report#categories is not yet implemented'
|
69
|
+
end
|
70
|
+
|
71
|
+
def report_structure_for report, &block
|
72
|
+
raise ReportError, 'report#structure is not yet implemented'
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Subledger
|
2
|
+
module Store
|
3
|
+
module Api
|
4
|
+
module Update
|
5
|
+
def update updatable
|
6
|
+
path = Path.for_entity :anchor => updatable
|
7
|
+
|
8
|
+
begin
|
9
|
+
json_body = http.patch do |req|
|
10
|
+
req.url path
|
11
|
+
req.body = updatable.patch_hash
|
12
|
+
end.body
|
13
|
+
rescue UpdateConflictError => e
|
14
|
+
raise e
|
15
|
+
rescue Exception => e
|
16
|
+
raise UpdateError, "Cannot update #{updatable}: #{e}"
|
17
|
+
end
|
18
|
+
|
19
|
+
new_or_initialize json_body, updatable
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
module Subledger
|
2
|
+
module Store
|
3
|
+
module Api
|
4
|
+
class Store
|
5
|
+
|
6
|
+
include Subledger::Store
|
7
|
+
|
8
|
+
include Balance
|
9
|
+
include Categories
|
10
|
+
include Collect
|
11
|
+
include Create
|
12
|
+
include CreateAndPost
|
13
|
+
include CreateLine
|
14
|
+
include FirstAndLastLine
|
15
|
+
include Post
|
16
|
+
include Progress
|
17
|
+
include Read
|
18
|
+
include Reports
|
19
|
+
include Update
|
20
|
+
include Activate
|
21
|
+
include Archive
|
22
|
+
include CreateIdentity
|
23
|
+
|
24
|
+
def add_initial_controls_for org
|
25
|
+
# Server handles this functionality
|
26
|
+
end
|
27
|
+
|
28
|
+
def add_initial_controls_to identity
|
29
|
+
# Server handles this functionality
|
30
|
+
end
|
31
|
+
|
32
|
+
def raise_unless_bucket_name_valid args
|
33
|
+
# No endpoints for this
|
34
|
+
end
|
35
|
+
|
36
|
+
def create_backup anchor
|
37
|
+
# No endpoints for this
|
38
|
+
end
|
39
|
+
|
40
|
+
def backup_exists? anchor
|
41
|
+
# No endpoints for this
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
attr_reader :http
|
47
|
+
|
48
|
+
def base_url
|
49
|
+
"#{SCHEME}#{DOMAIN}"
|
50
|
+
end
|
51
|
+
|
52
|
+
def setup
|
53
|
+
@http = Faraday.new :url => base_url do |conn|
|
54
|
+
unless auth_key.nil?
|
55
|
+
conn.basic_auth auth_key.id, auth_key.secret
|
56
|
+
end
|
57
|
+
|
58
|
+
conn.request :json
|
59
|
+
conn.response :json, :content_type => /^json$/
|
60
|
+
conn.use Subledger::Store::Api::Errors::ResponseError
|
61
|
+
conn.adapter Faraday.default_adapter
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def parse_json json_body
|
66
|
+
MultiJson.load json_body
|
67
|
+
end
|
68
|
+
|
69
|
+
def klass_method_from parsed_json
|
70
|
+
parsed_json.keys.first
|
71
|
+
end
|
72
|
+
|
73
|
+
def new_or_initialize json_body, initializable
|
74
|
+
client = initializable.client
|
75
|
+
|
76
|
+
parsed_json = parse_json json_body
|
77
|
+
|
78
|
+
klass_method = klass_method_from parsed_json
|
79
|
+
|
80
|
+
response_hash = parsed_json[klass_method]
|
81
|
+
|
82
|
+
args = Rest.to_args response_hash, client
|
83
|
+
|
84
|
+
args.merge! :json => MultiJson.dump( response_hash )
|
85
|
+
|
86
|
+
if initializable.respond_to? :post_delay
|
87
|
+
args.merge! :post_delay => initializable.post_delay
|
88
|
+
end
|
89
|
+
|
90
|
+
new_item = client.send klass_method, args
|
91
|
+
|
92
|
+
if initializable.entity_name == klass_method.to_sym
|
93
|
+
initializable.send :initialize, new_item.attributes
|
94
|
+
else
|
95
|
+
initializable = new_item
|
96
|
+
end
|
97
|
+
|
98
|
+
initializable
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'faraday_middleware'
|
2
|
+
|
3
|
+
require 'subledger/store/api/roles/activate'
|
4
|
+
require 'subledger/store/api/roles/archive'
|
5
|
+
require 'subledger/store/api/roles/balance'
|
6
|
+
require 'subledger/store/api/roles/categories'
|
7
|
+
require 'subledger/store/api/roles/collect'
|
8
|
+
require 'subledger/store/api/roles/create'
|
9
|
+
require 'subledger/store/api/roles/create_and_post'
|
10
|
+
require 'subledger/store/api/roles/create_line'
|
11
|
+
require 'subledger/store/api/roles/first_and_last_line'
|
12
|
+
require 'subledger/store/api/roles/post'
|
13
|
+
require 'subledger/store/api/roles/progress'
|
14
|
+
require 'subledger/store/api/roles/read'
|
15
|
+
require 'subledger/store/api/roles/reports'
|
16
|
+
require 'subledger/store/api/roles/update'
|
17
|
+
require 'subledger/store/api/roles/create_identity'
|
18
|
+
|
19
|
+
require 'subledger/store/api/errors'
|
20
|
+
require 'subledger/store/api/store'
|
@@ -0,0 +1,236 @@
|
|
1
|
+
module Subledger
|
2
|
+
module Store
|
3
|
+
|
4
|
+
class ActivateError < Error; end
|
5
|
+
class ArchiveError < Error; end
|
6
|
+
class AttachError < Error; end
|
7
|
+
class BalanceError < Error; end
|
8
|
+
class CategoryError < Error; end
|
9
|
+
class CollectError < Error; end
|
10
|
+
class CreateError < Error; end
|
11
|
+
class CreateLineError < Error; end
|
12
|
+
class DeleteError < Error; end
|
13
|
+
class DetachError < Error; end
|
14
|
+
class FirstAndLastLineError < Error; end
|
15
|
+
class PostError < Error; end
|
16
|
+
class ProgressError < Error; end
|
17
|
+
class ReadError < Error; end
|
18
|
+
class ReportError < Error; end
|
19
|
+
class UpdateError < Error; end
|
20
|
+
class UpdateNotFoundError < Error; end
|
21
|
+
class UpdateConflictError < Error; end
|
22
|
+
class BucketValidatorError < Error; end
|
23
|
+
|
24
|
+
attr_reader :auth_key
|
25
|
+
|
26
|
+
def initialize args
|
27
|
+
@auth_key = args[:auth_key]
|
28
|
+
|
29
|
+
setup
|
30
|
+
end
|
31
|
+
|
32
|
+
def attributes
|
33
|
+
{ :auth_key => auth_key }
|
34
|
+
end
|
35
|
+
|
36
|
+
def collect args
|
37
|
+
collection_name = args[:collection_name]
|
38
|
+
action = args[:action]
|
39
|
+
limit = args[:limit]
|
40
|
+
no_balances = args[:no_balances]
|
41
|
+
|
42
|
+
begin
|
43
|
+
if limit.nil? or limit < 1 or limit > 100
|
44
|
+
raise CollectError, ':limit must be 1-100'
|
45
|
+
end
|
46
|
+
|
47
|
+
collected = send action, args
|
48
|
+
|
49
|
+
if collection_name == :account_lines and not collected.empty?
|
50
|
+
if no_balances
|
51
|
+
collected.each do |line|
|
52
|
+
line.send :balance=, nil
|
53
|
+
end
|
54
|
+
else
|
55
|
+
args.merge! :account_lines => collected
|
56
|
+
|
57
|
+
set_account_line_balances args
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
collected
|
62
|
+
rescue Exception => e
|
63
|
+
raise CollectError, "Unable to collect #{collection_name}: #{e}"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def raise_unless_bucket_name_valid args
|
68
|
+
bucket_name = args[:bucket_name]
|
69
|
+
|
70
|
+
unless bucket_name.nil?
|
71
|
+
Celluloid::Actor[:backup_bucket_validators].validate args
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def create_backup anchor
|
76
|
+
Celluloid::Actor[:backup_creators].async.create :anchor => anchor
|
77
|
+
|
78
|
+
anchor
|
79
|
+
end
|
80
|
+
|
81
|
+
def backup_exists? anchor
|
82
|
+
Celluloid::Actor[:backup_creators].exists? :anchor => anchor
|
83
|
+
end
|
84
|
+
|
85
|
+
def create_identity identity
|
86
|
+
create identity
|
87
|
+
|
88
|
+
key = add_key_to identity
|
89
|
+
|
90
|
+
add_initial_controls_to identity
|
91
|
+
|
92
|
+
return identity, key
|
93
|
+
end
|
94
|
+
|
95
|
+
def add_initial_controls_for org
|
96
|
+
client = org.client
|
97
|
+
|
98
|
+
key = read client.keys :id => auth_key.id
|
99
|
+
|
100
|
+
identity = key.identity
|
101
|
+
|
102
|
+
# TODO OrgIdentities table is not being updated yet
|
103
|
+
|
104
|
+
create client.active_controls :org => org,
|
105
|
+
:identity => identity,
|
106
|
+
:verbs => 'GET|PATCH',
|
107
|
+
:path => "/#{API_VERSION}/orgs/#{org.id}",
|
108
|
+
:mode => :allow
|
109
|
+
|
110
|
+
create client.active_controls :org => org,
|
111
|
+
:identity => identity,
|
112
|
+
:verbs => 'POST|GET|PATCH',
|
113
|
+
:path => "/#{API_VERSION}/orgs/#{org.id}/*",
|
114
|
+
:mode => :allow
|
115
|
+
end
|
116
|
+
|
117
|
+
def add_key_to identity
|
118
|
+
key = identity.client.active_keys :identity => identity
|
119
|
+
|
120
|
+
create key
|
121
|
+
end
|
122
|
+
|
123
|
+
def add_initial_controls_to identity
|
124
|
+
client = identity.client
|
125
|
+
|
126
|
+
client.active_controls.create :identity => identity,
|
127
|
+
:verbs => 'GET|PATCH',
|
128
|
+
:path => "/#{API_VERSION}/identities/#{identity.id}",
|
129
|
+
:mode => :allow
|
130
|
+
|
131
|
+
client.active_controls.create :identity => identity,
|
132
|
+
:verbs => 'GET|POST',
|
133
|
+
:path => "/#{API_VERSION}/identities/#{identity.id}/*",
|
134
|
+
:mode => :allow
|
135
|
+
|
136
|
+
client.active_controls.create :identity => identity,
|
137
|
+
:verbs => 'POST',
|
138
|
+
:path => "/#{API_VERSION}/orgs",
|
139
|
+
:mode => :allow
|
140
|
+
end
|
141
|
+
|
142
|
+
def first_and_last_line args
|
143
|
+
account = args[:account]
|
144
|
+
|
145
|
+
# eventually consistent means we could get one, but not two...
|
146
|
+
# zero is fine, it means the account has no lines!
|
147
|
+
|
148
|
+
begin
|
149
|
+
lines = [ ]
|
150
|
+
|
151
|
+
lines += first_or_last_line( args.merge! :effective_at => first_second,
|
152
|
+
:action => :starting )
|
153
|
+
|
154
|
+
lines += first_or_last_line( args.merge! :effective_at => last_second,
|
155
|
+
:action => :ending )
|
156
|
+
|
157
|
+
end while lines.length == 1 and sleep 0.025
|
158
|
+
|
159
|
+
lines
|
160
|
+
end
|
161
|
+
|
162
|
+
private
|
163
|
+
|
164
|
+
def first_or_last_line args
|
165
|
+
account = args[:account]
|
166
|
+
client = args[:client] ||= account.client
|
167
|
+
|
168
|
+
anchor = account.line :effective_at => args[:effective_at]
|
169
|
+
|
170
|
+
collect args.merge! :collection_name => :account_lines,
|
171
|
+
:anchor => anchor,
|
172
|
+
:limit => 1
|
173
|
+
end
|
174
|
+
|
175
|
+
def set_account_line_balances args
|
176
|
+
if [ :before, :ending, :preceding ].include? args[:action]
|
177
|
+
account_lines = args[:account_lines].reverse
|
178
|
+
else
|
179
|
+
account_lines = args[:account_lines]
|
180
|
+
end
|
181
|
+
|
182
|
+
first_line = account_lines.first
|
183
|
+
|
184
|
+
balance = balance_for first_line
|
185
|
+
|
186
|
+
first_line.send :balance=, balance
|
187
|
+
|
188
|
+
# Beware insane Ruby array indexing on line below
|
189
|
+
|
190
|
+
account_lines[1..-1].each do |account_line|
|
191
|
+
balance += account_line
|
192
|
+
account_line.send :balance=, balance
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
def set_account_line_balance account_line
|
197
|
+
account_line.send :balance=, balance_for( account_line )
|
198
|
+
end
|
199
|
+
|
200
|
+
def balance_for account_line
|
201
|
+
client = account_line.client
|
202
|
+
account = account_line.account
|
203
|
+
effective_at = account_line.effective_at
|
204
|
+
|
205
|
+
previous_ms = previous_ms_for effective_at
|
206
|
+
|
207
|
+
balance = account_balance :store => self,
|
208
|
+
:client => client,
|
209
|
+
:account => account,
|
210
|
+
:at => previous_ms
|
211
|
+
|
212
|
+
account.lines( :action => :after,
|
213
|
+
:effective_at => previous_ms,
|
214
|
+
:no_balances => true ) do |line|
|
215
|
+
|
216
|
+
balance += line
|
217
|
+
|
218
|
+
break if line == account_line
|
219
|
+
end
|
220
|
+
|
221
|
+
balance
|
222
|
+
end
|
223
|
+
|
224
|
+
def previous_ms_for effective_at
|
225
|
+
effective_at_sec = BigDecimal effective_at.tv_sec
|
226
|
+
effective_at_usec = BigDecimal effective_at.tv_usec
|
227
|
+
effective_at_frac = effective_at_usec / 1_000_000
|
228
|
+
|
229
|
+
effective_at_bd = effective_at_sec + effective_at_frac
|
230
|
+
|
231
|
+
previous_ms_bd = effective_at_bd - BigDecimal( '.001' )
|
232
|
+
|
233
|
+
Time.at( previous_ms_bd ).utc
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Subledger
|
2
|
+
class Supervisor < Celluloid::SupervisionGroup
|
3
|
+
|
4
|
+
DEFAULT_SIZE = 1
|
5
|
+
|
6
|
+
SMALL = 16
|
7
|
+
MEDIUM = 32
|
8
|
+
LARGE = 48
|
9
|
+
GRANDE = 80
|
10
|
+
|
11
|
+
def self.manage klass, args
|
12
|
+
args[:size] ||= DEFAULT_SIZE
|
13
|
+
|
14
|
+
if args[:size] == 1
|
15
|
+
supervise klass, args
|
16
|
+
else
|
17
|
+
pool klass, args
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
|
3
|
+
module Subledger
|
4
|
+
module UUID
|
5
|
+
|
6
|
+
SPACE = 62 ** 22
|
7
|
+
|
8
|
+
CHARS = ('0'..'9').to_a + ('a'..'z').to_a + ('A'..'Z').to_a
|
9
|
+
|
10
|
+
VALID = /^[0-9A-Za-z]{22}$/
|
11
|
+
|
12
|
+
def self.valid? uuid
|
13
|
+
!uuid.nil? and uuid =~ VALID
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.invalid? uuid
|
17
|
+
uuid.nil? or uuid !~ VALID
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.as_string
|
21
|
+
to_string as_bignum
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.as_bignum
|
25
|
+
SecureRandom.random_number SPACE
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.to_string bignum
|
29
|
+
base_62 = ''
|
30
|
+
|
31
|
+
22.times do
|
32
|
+
base_62 << CHARS[ bignum.modulo 62 ]
|
33
|
+
bignum /= 62
|
34
|
+
end
|
35
|
+
|
36
|
+
base_62.reverse
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.to_bignum uuid
|
40
|
+
bignum = 0
|
41
|
+
|
42
|
+
power = 0
|
43
|
+
|
44
|
+
uuid.split('').reverse.each do |character|
|
45
|
+
bignum += CHARS.index( character ) * ( 62 ** power )
|
46
|
+
power += 1
|
47
|
+
end
|
48
|
+
|
49
|
+
bignum
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|