subledger 0.7.7
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.
- 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
|