subledger 0.7.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (85) hide show
  1. data/.gitignore +9 -0
  2. data/LICENSE.txt +12 -0
  3. data/README.md +136 -0
  4. data/Rakefile +5 -0
  5. data/bin/subledger +6 -0
  6. data/lib/subledger/actor.rb +32 -0
  7. data/lib/subledger/collection_name.rb +25 -0
  8. data/lib/subledger/domain/account.rb +168 -0
  9. data/lib/subledger/domain/balance.rb +102 -0
  10. data/lib/subledger/domain/book.rb +111 -0
  11. data/lib/subledger/domain/category.rb +157 -0
  12. data/lib/subledger/domain/control.rb +180 -0
  13. data/lib/subledger/domain/formatters.rb +31 -0
  14. data/lib/subledger/domain/identity.rb +159 -0
  15. data/lib/subledger/domain/journal_entry.rb +293 -0
  16. data/lib/subledger/domain/key.rb +113 -0
  17. data/lib/subledger/domain/line.rb +272 -0
  18. data/lib/subledger/domain/org.rb +110 -0
  19. data/lib/subledger/domain/report.rb +247 -0
  20. data/lib/subledger/domain/report_rendering.rb +233 -0
  21. data/lib/subledger/domain/roles/activatable.rb +11 -0
  22. data/lib/subledger/domain/roles/archivable.rb +11 -0
  23. data/lib/subledger/domain/roles/attributable.rb +14 -0
  24. data/lib/subledger/domain/roles/collectable.rb +175 -0
  25. data/lib/subledger/domain/roles/creatable.rb +58 -0
  26. data/lib/subledger/domain/roles/describable.rb +33 -0
  27. data/lib/subledger/domain/roles/describable_report_rendering.rb +50 -0
  28. data/lib/subledger/domain/roles/identifiable.rb +15 -0
  29. data/lib/subledger/domain/roles/postable.rb +54 -0
  30. data/lib/subledger/domain/roles/progressable.rb +11 -0
  31. data/lib/subledger/domain/roles/readable.rb +34 -0
  32. data/lib/subledger/domain/roles/restable.rb +69 -0
  33. data/lib/subledger/domain/roles/storable.rb +30 -0
  34. data/lib/subledger/domain/roles/timeable.rb +18 -0
  35. data/lib/subledger/domain/roles/updatable.rb +35 -0
  36. data/lib/subledger/domain/roles/versionable.rb +35 -0
  37. data/lib/subledger/domain/roles.rb +16 -0
  38. data/lib/subledger/domain/value/credit.rb +16 -0
  39. data/lib/subledger/domain/value/debit.rb +16 -0
  40. data/lib/subledger/domain/value/zero.rb +24 -0
  41. data/lib/subledger/domain/value.rb +111 -0
  42. data/lib/subledger/domain.rb +95 -0
  43. data/lib/subledger/exception_handler.rb +65 -0
  44. data/lib/subledger/interface/client.rb +295 -0
  45. data/lib/subledger/interface/dispatcher.rb +20 -0
  46. data/lib/subledger/interface.rb +2 -0
  47. data/lib/subledger/path.rb +106 -0
  48. data/lib/subledger/rest.rb +128 -0
  49. data/lib/subledger/server.rb +3 -0
  50. data/lib/subledger/store/api/errors.rb +95 -0
  51. data/lib/subledger/store/api/roles/activate.rb +21 -0
  52. data/lib/subledger/store/api/roles/archive.rb +21 -0
  53. data/lib/subledger/store/api/roles/balance.rb +39 -0
  54. data/lib/subledger/store/api/roles/categories.rb +51 -0
  55. data/lib/subledger/store/api/roles/collect.rb +58 -0
  56. data/lib/subledger/store/api/roles/create.rb +26 -0
  57. data/lib/subledger/store/api/roles/create_and_post.rb +35 -0
  58. data/lib/subledger/store/api/roles/create_identity.rb +39 -0
  59. data/lib/subledger/store/api/roles/create_line.rb +24 -0
  60. data/lib/subledger/store/api/roles/first_and_last_line.rb +31 -0
  61. data/lib/subledger/store/api/roles/post.rb +25 -0
  62. data/lib/subledger/store/api/roles/progress.rb +21 -0
  63. data/lib/subledger/store/api/roles/read.rb +19 -0
  64. data/lib/subledger/store/api/roles/reports.rb +77 -0
  65. data/lib/subledger/store/api/roles/update.rb +24 -0
  66. data/lib/subledger/store/api/store.rb +103 -0
  67. data/lib/subledger/store/api.rb +20 -0
  68. data/lib/subledger/store.rb +236 -0
  69. data/lib/subledger/supervisor.rb +21 -0
  70. data/lib/subledger/uuid.rb +52 -0
  71. data/lib/subledger/version.rb +4 -0
  72. data/lib/subledger.rb +234 -0
  73. data/spec/spec_helper.rb +77 -0
  74. data/spec/subledger_account_spec.rb +354 -0
  75. data/spec/subledger_book_spec.rb +130 -0
  76. data/spec/subledger_category_spec.rb +203 -0
  77. data/spec/subledger_control_spec.rb +43 -0
  78. data/spec/subledger_identity_spec.rb +47 -0
  79. data/spec/subledger_journal_entry_spec.rb +417 -0
  80. data/spec/subledger_key_spec.rb +43 -0
  81. data/spec/subledger_org_spec.rb +68 -0
  82. data/spec/subledger_report_spec.rb +295 -0
  83. data/spec/subledger_spec.rb +101 -0
  84. data/subledger.gemspec +52 -0
  85. metadata +205 -0
@@ -0,0 +1,157 @@
1
+ module Subledger
2
+ module Domain
3
+ class CategoryError < Error; end
4
+
5
+ class Category
6
+
7
+ include Domain
8
+
9
+ include Roles::Attributable
10
+ include Roles::Describable
11
+ include Roles::Identifiable
12
+ include Roles::Storable
13
+ include Roles::Versionable
14
+
15
+ include Roles::Creatable
16
+ include Roles::Readable
17
+ include Roles::Updatable
18
+
19
+ include Roles::Collectable
20
+
21
+ include Roles::Activatable
22
+ include Roles::Archivable
23
+
24
+ include Roles::Restable
25
+
26
+ attr_reader :book
27
+ attr_accessor :normal_balance
28
+
29
+ def self.post_keys
30
+ [ :description, :reference, :normal_balance ]
31
+ end
32
+
33
+ def self.patch_keys
34
+ [ :id, :description, :reference, :normal_balance, :version ]
35
+ end
36
+
37
+ def self.root_klass
38
+ Category
39
+ end
40
+
41
+ def self.sub_klasses
42
+ [ active_klass, archived_klass ]
43
+ end
44
+
45
+ def self.active_klass
46
+ ActiveCategory
47
+ end
48
+
49
+ def self.archived_klass
50
+ ArchivedCategory
51
+ end
52
+
53
+ def initialize args
54
+ describable args
55
+ identifiable args
56
+ storable args
57
+ versionable args
58
+
59
+ @book = args[:book]
60
+ @normal_balance = args[:normal_balance]
61
+ end
62
+
63
+ def accounts &block
64
+ begin
65
+ store.collect_accounts_for_category self, &block
66
+ rescue Store::CollectError => e
67
+ raise CategoryError, e
68
+ end
69
+ end
70
+
71
+ class Entity < Grape::Entity
72
+ root 'categories', 'category'
73
+
74
+ expose :id, :documentation => { :type => 'string', :desc => 'category ID' }
75
+
76
+ expose :book, :format_with => :id,
77
+ :documentation => { :type => 'string', :desc => 'book ID' }
78
+
79
+ expose :description, :documentation => { :type => 'string', :desc => 'description' }
80
+
81
+ expose :reference, :documentation => { :type => 'string', :desc => 'reference URI' }
82
+
83
+ expose :normal_balance, :format_with => :normal_balance,
84
+ :documentation => { :type => 'string', :desc => 'normal balance type' }
85
+
86
+ expose :version, :documentation => { :type => 'integer', :desc => 'version' }
87
+ end
88
+
89
+ private
90
+
91
+ ACCEPTABLE_NORMAL_BALANCES = [ Debit, Credit ]
92
+
93
+ def self.raise_unless_creatable args
94
+
95
+ book = args[:book]
96
+
97
+ if book.nil? or not book.kind_of? Book
98
+ raise CategoryError, ':book is required and must be a Book'
99
+ elsif UUID.invalid? book.id
100
+ raise CategoryError, ':book must have a valid :id'
101
+ end
102
+
103
+ normal_balance = args[:normal_balance]
104
+
105
+ if normal_balance.nil? or not ACCEPTABLE_NORMAL_BALANCES.include? normal_balance
106
+ raise CategoryError, ":normal_balance is required and must be one of #{ACCEPTABLE_NORMAL_BALANCES.join(', ')}"
107
+ end
108
+ end
109
+ end
110
+
111
+ class ArchivedCategory < Category
112
+ class Entity < Category::Entity
113
+ root 'archived_categories', 'archived_category'
114
+ end
115
+
116
+ def self.sub_klasses
117
+ [ archived_klass ]
118
+ end
119
+ end
120
+
121
+ class ActiveCategory < Category
122
+ class Entity < Category::Entity
123
+ root 'active_categories', 'active_category'
124
+ end
125
+
126
+ def self.sub_klasses
127
+ [ active_klass ]
128
+ end
129
+
130
+ def attach args
131
+ account = args[:account]
132
+
133
+ begin
134
+ store.attach_account_to_category :category => self,
135
+ :account => account
136
+ rescue Store::AttachError => e
137
+ raise CategoryError, e
138
+ end
139
+
140
+ account
141
+ end
142
+
143
+ def detach args
144
+ account = args[:account]
145
+
146
+ begin
147
+ store.detach_account_from_category :category => self,
148
+ :account => account
149
+ rescue Store::DetachError => e
150
+ raise CategoryError, e
151
+ end
152
+
153
+ account
154
+ end
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,180 @@
1
+ module Subledger
2
+ module Domain
3
+ class ControlError < Error; end
4
+
5
+ class Control
6
+
7
+ include Domain
8
+
9
+ include Roles::Attributable
10
+ include Roles::Identifiable
11
+ include Roles::Storable
12
+ include Roles::Creatable
13
+ include Roles::Readable
14
+ include Roles::Collectable
15
+ include Roles::Activatable
16
+ include Roles::Archivable
17
+ include Roles::Restable
18
+
19
+ attr_reader :identity, :verbs, :path, :mode
20
+
21
+ def self.root_klass
22
+ Control
23
+ end
24
+
25
+ def self.post_keys
26
+ [ :id, :identity, :verbs, :path, :mode ]
27
+ end
28
+
29
+ def self.patch_keys
30
+ [ ]
31
+ end
32
+
33
+ def self.sub_klasses
34
+ [ active_klass, archived_klass ]
35
+ end
36
+
37
+ def self.active_klass
38
+ ActiveControl
39
+ end
40
+
41
+ def self.archived_klass
42
+ ArchivedControl
43
+ end
44
+
45
+ def initialize args
46
+ identifiable args
47
+ storable args
48
+
49
+ @identity = args[:identity]
50
+ @verbs = args[:verbs]
51
+ @path = args[:path]
52
+ @mode = args[:mode]
53
+ end
54
+
55
+ # def revoke
56
+ # @revoked = true
57
+ #
58
+ # self.update
59
+ #
60
+ # self
61
+ # end
62
+
63
+ # def revoked?
64
+ # @revoked == true
65
+ # end
66
+
67
+ # def is verb, url
68
+ # verb == @verbs and url == @path
69
+ # end
70
+
71
+ def match? verb, url
72
+ @verbs.split('|').include?(verb) and match_wildcard(url, @path)
73
+ end
74
+
75
+ class Entity < Grape::Entity
76
+ root 'controls', 'control'
77
+
78
+ expose :id, :documentation => { :type => 'string', :desc => 'control ID' }
79
+
80
+ expose :identity, :format_with => :id,
81
+ :documentation => { :type => 'string', :desc => 'identity ID' }
82
+
83
+ expose :verbs, :documentation => { :type => 'string', :desc => 'HTTP verbs' }
84
+
85
+ expose :path, :documentation => { :type => 'string', :desc => 'HTTP path' }
86
+
87
+ expose :mode, :documentation => { :type => 'string', :desc => 'allow or deny' }
88
+ end
89
+
90
+ private
91
+
92
+ def match_wildcard actual, authorized
93
+ actual = strip_version actual
94
+ authorized = strip_version authorized
95
+
96
+ regx = authorized.gsub(/\*(.)/, '[^\1]*\1').gsub(/\*$/, '.*')
97
+
98
+ actual == authorized or ( actual =~ /^#{regx}$/ ) == 0
99
+ end
100
+
101
+ def strip_version path
102
+ path[3..-1]
103
+ end
104
+
105
+ ACCEPTABLE_VERBS = [ 'POST', 'GET', 'PATCH' ]
106
+ ACCEPTABLE_MODES = [ :allow, :deny ]
107
+
108
+ def self.raise_unless_creatable args
109
+
110
+ identity = args[:identity]
111
+
112
+ if identity.nil? or not identity.kind_of? Identity
113
+ raise ControlError, ':identity is required and must be an Identity'
114
+ elsif UUID.invalid? identity.id
115
+ raise ControlError, ':identity must have a valid :id'
116
+ end
117
+
118
+ # TODO :verb(s) should be a domain object
119
+
120
+ verbs = args[:verbs]
121
+
122
+ if verbs.nil?
123
+ raise ControlError, ':verbs is required'
124
+ end
125
+
126
+ verbs.split('|').each do |verb|
127
+ unless ACCEPTABLE_VERBS.include? verb
128
+ raise ControlError, ":verbs must contain one or more of #{ACCEPTABLE_VERBS.join(', ')} separated by |"
129
+ end
130
+ end
131
+
132
+ # TODO :path should be a domain object
133
+
134
+ path = args[:path]
135
+
136
+ if path.nil?
137
+ raise ControlError, ':path is required'
138
+ end
139
+
140
+ api_version = path[1,2]
141
+
142
+ unless api_version == API_VERSION
143
+ raise ControlError, ":path must begin with /#{API_VERSION}/"
144
+ end
145
+
146
+ path[4,].split( '/' ).each do |part|
147
+ unless part =~ /[*_0-9A-Za-z]+/
148
+ raise ControlError, 'invalid :path'
149
+ end
150
+ end
151
+
152
+ mode = args[:mode]
153
+
154
+ if mode.nil? or not ACCEPTABLE_MODES.include? mode
155
+ raise ControlError, ":mode must contain one or more of :allow, :deny}"
156
+ end
157
+ end
158
+ end
159
+
160
+ class ArchivedControl < Control
161
+ class Entity < Control::Entity
162
+ root 'archived_controls', 'archived_control'
163
+ end
164
+
165
+ def self.sub_klasses
166
+ [ archived_klass ]
167
+ end
168
+ end
169
+
170
+ class ActiveControl < Control
171
+ class Entity < Control::Entity
172
+ root 'active_controls', 'active_control'
173
+ end
174
+
175
+ def self.sub_klasses
176
+ [ active_klass ]
177
+ end
178
+ end
179
+ end
180
+ end
@@ -0,0 +1,31 @@
1
+ Grape::Entity.format_with :id do |entity|
2
+ entity.id unless entity.nil?
3
+ end
4
+
5
+ Grape::Entity.format_with :normal_balance do |value_klass|
6
+ value_klass.type unless value_klass.nil?
7
+ end
8
+
9
+ Grape::Entity.format_with :time do |time|
10
+ time.iso8601(3) unless time.nil?
11
+ end
12
+
13
+ Grape::Entity.format_with :time_full_second do |time|
14
+ time.iso8601 unless time.nil?
15
+ end
16
+
17
+ Grape::Entity.format_with :value do |value|
18
+ value.rest_hash unless value.nil?
19
+ end
20
+
21
+ Grape::Entity.format_with :progress do |value|
22
+ { :percentage => value }
23
+ end
24
+
25
+ Grape::Entity.format_with :integer do |value|
26
+ value.to_i unless value.nil?
27
+ end
28
+
29
+ Grape::Entity.format_with :balance do |value|
30
+ value.serializable_hash unless value.nil?
31
+ end
@@ -0,0 +1,159 @@
1
+ module Subledger
2
+ module Domain
3
+ class IdentityError < Error; end
4
+
5
+ class Identity
6
+
7
+ include Domain
8
+
9
+ include Roles::Attributable
10
+ include Roles::Describable
11
+ include Roles::Identifiable
12
+ include Roles::Storable
13
+ include Roles::Versionable
14
+
15
+ include Roles::Creatable
16
+ include Roles::Readable
17
+ include Roles::Updatable
18
+
19
+ include Roles::Activatable
20
+ include Roles::Archivable
21
+
22
+ include Roles::Restable
23
+
24
+ attr_accessor :email
25
+
26
+ def self.post_keys
27
+ [ :id, :email, :description, :reference ]
28
+ end
29
+
30
+ def self.patch_keys
31
+ [ :id, :email, :description, :reference, :version ]
32
+ end
33
+
34
+ def self.root_klass
35
+ Identity
36
+ end
37
+
38
+ def self.sub_klasses
39
+ [ archived_klass, active_klass ]
40
+ end
41
+
42
+ def self.archived_klass
43
+ ArchivedIdentity
44
+ end
45
+
46
+ def self.active_klass
47
+ ActiveIdentity
48
+ end
49
+
50
+ def self.create args
51
+ validate_creatability args
52
+
53
+ identity = args[:client].send active_klass.collection_name, args
54
+
55
+ args[:store].create_identity identity
56
+ end
57
+
58
+ def create
59
+ identity, key = self.class.create self.attributes
60
+
61
+ initialize identity.attributes
62
+
63
+ return self, key
64
+ end
65
+
66
+ def initialize args
67
+ describable args
68
+ identifiable args
69
+ storable args
70
+ versionable args
71
+
72
+ @email = args[:email]
73
+ end
74
+
75
+ # def by_email email
76
+ # # TODO
77
+ # end
78
+
79
+ # def by_org org
80
+ # # TODO look in OrgIdentities table
81
+ # end
82
+
83
+ def authorize verb, url
84
+ raise KeyError if @retired
85
+
86
+ controls.each do |control|
87
+ return true if control.match(verb, url) unless control.revoked?
88
+ end
89
+
90
+ false
91
+ end
92
+
93
+ # def grant args
94
+ # args[:client].controls.create args
95
+ # end
96
+
97
+ # def revoke args
98
+ # org_id = args[:org_id]
99
+ # verb = args[:verb]
100
+ # url = args[:url]
101
+ #
102
+ # revoked = false
103
+ #
104
+ # controls.each do |control|
105
+ # if control.is org_id, verb, url
106
+ # control.revoke
107
+ # revoked = true
108
+ # end
109
+ # end
110
+ #
111
+ # raise IdentityError unless revoked
112
+ #
113
+ # self
114
+ # end
115
+
116
+ class Entity < Grape::Entity
117
+ root 'identities', 'identity'
118
+
119
+ expose :id, :documentation => { :type => 'string', :desc => 'identity ID' }
120
+
121
+ expose :email, :documentation => { :type => 'string', :desc => 'identity email' }
122
+
123
+ expose :description, :documentation => { :type => 'string', :desc => 'description' }
124
+
125
+ expose :reference, :documentation => { :type => 'string', :desc => 'reference URI' }
126
+
127
+ expose :version, :documentation => { :type => 'integer', :desc => 'version' }
128
+ end
129
+
130
+ private
131
+
132
+ def self.raise_unless_creatable args
133
+ unless ValidateEmail.mx_valid? args[:email]
134
+ raise IdentityError, ':email is required and must have an MX record for the domain'
135
+ end
136
+ end
137
+ end
138
+
139
+ class ArchivedIdentity < Identity
140
+ class Entity < Identity::Entity
141
+ root 'archived_identities', 'archived_identity'
142
+ end
143
+
144
+ def self.sub_klasses
145
+ [ archived_klass ]
146
+ end
147
+ end
148
+
149
+ class ActiveIdentity < Identity
150
+ class Entity < Identity::Entity
151
+ root 'active_identities', 'active_identity'
152
+ end
153
+
154
+ def self.sub_klasses
155
+ [ active_klass ]
156
+ end
157
+ end
158
+ end
159
+ end