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.
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,24 @@
1
+ module Subledger
2
+ module Domain
3
+ class Zero
4
+
5
+ include Value
6
+
7
+ def self.type
8
+ 'zero'
9
+ end
10
+
11
+ def rest_hash
12
+ { 'type' => 'zero', 'amount' => amount_to_s(@amount) }
13
+ end
14
+
15
+ private
16
+
17
+ def validate_sign arg_amount
18
+ unless arg_amount.zero?
19
+ raise ValueError, 'Amount must be zero'
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,111 @@
1
+ # additional requires at bottom
2
+
3
+ module Subledger
4
+ module Domain
5
+ class ValueError < Error; end
6
+
7
+ module Value
8
+
9
+ def self.included base
10
+ base.extend ValueClass
11
+ end
12
+
13
+ module ValueClass
14
+ def raise_unless_creatable args
15
+ value = args[:value]
16
+
17
+ if value.nil? or not value.kind_of? Value
18
+ raise ValueError, ':value is required and must be a Value'
19
+ end
20
+ end
21
+ end
22
+
23
+ attr_reader :amount
24
+
25
+ def initialize arg_amount=0
26
+ if arg_amount.nil?
27
+ raise ValueError, 'amount may not be nil'
28
+ end
29
+
30
+ validate_amount(arg_amount) if arg_amount.respond_to?(:match)
31
+
32
+ big_amount = BigDecimal arg_amount
33
+
34
+ validate_sign big_amount
35
+
36
+ validate_amount( amount_to_s big_amount )
37
+
38
+ @amount = big_amount
39
+ end
40
+
41
+ def + other
42
+ (Balance.new + value + other.value).value
43
+ end
44
+
45
+ def == other
46
+ self.value.class == other.value.class and amount == other.amount
47
+ end
48
+
49
+ def value
50
+ self
51
+ end
52
+
53
+ def to_s
54
+ "#{self.class.type}: #{amount_to_s @amount}"
55
+ end
56
+
57
+ alias :inspect :to_s
58
+
59
+ private
60
+
61
+ def amount_to_s arg_amount
62
+ if arg_amount.zero?
63
+ '0'
64
+ else
65
+ arg_amount.to_s( 'F' ).chomp( '.0' ).sub( /^0+/, '' )
66
+ end
67
+ end
68
+
69
+ def validate_sign arg_amount
70
+ unless arg_amount > 0
71
+ raise ValueError, 'amount must be positive'
72
+ end
73
+ end
74
+
75
+ ERROR_TEXT = 'amount is limited to 15 whole digits and 12 fractional digits'
76
+
77
+ def validate_amount arg_amount
78
+
79
+ unless arg_amount.match /^\d*[.]?\d*$/
80
+ raise ValueError, "amount (#{arg_amount}) must be numeric"
81
+ end
82
+
83
+ return if BigDecimal(arg_amount).zero?
84
+
85
+ arg_amount.sub! /^0+/, ''
86
+ arg_amount.sub! /[.](\d)*0+$/, '.\\1'
87
+
88
+ if arg_amount.include? '.'
89
+ whole, fraction = arg_amount.split '.'
90
+
91
+ whole ||= ''
92
+ fraction ||= ''
93
+
94
+ if whole.length > 15 or fraction.length > 12
95
+ raise ValueError, ERROR_TEXT
96
+ end
97
+ else
98
+ if arg_amount.length > 15
99
+ raise ValueError, ERROR_TEXT
100
+ end
101
+ end
102
+
103
+ end
104
+
105
+ end
106
+ end
107
+ end
108
+
109
+ require 'subledger/domain/value/credit'
110
+ require 'subledger/domain/value/debit'
111
+ require 'subledger/domain/value/zero'
@@ -0,0 +1,95 @@
1
+ require 'bcrypt'
2
+ require 'grape-entity'
3
+
4
+ # additional requires at bottom
5
+
6
+ module Subledger
7
+ module Domain
8
+ class ActivatableError < Error; end
9
+ class ArchivableError < Error; end
10
+ class AttributableError < Error; end
11
+ class CollectableError < Error; end
12
+ class CreatableError < Error; end
13
+ class DescribableError < Error; end
14
+ class DescribableReportRenderingError < Error; end
15
+ class IdentifiableError < Error; end
16
+ class PostableError < Error; end
17
+ class ProgressableError < Error; end
18
+ class ReadableError < Error; end
19
+ class RestableError < Error; end
20
+ class StorableError < Error; end
21
+ class TimeableError < Error; end
22
+ class UpdatableError < Error; end
23
+ class VersionableError < Error; end
24
+
25
+ def self.included base
26
+ base.extend DomainClass
27
+ end
28
+
29
+ module DomainClass
30
+ def keys
31
+ self::Entity.exposures.keys
32
+ end
33
+
34
+ def entity_name
35
+ self::Entity.instance_variable_get( :@root ).to_sym
36
+ end
37
+
38
+ def collection_name
39
+ self::Entity.instance_variable_get( :@collection_root ).to_sym
40
+ end
41
+
42
+ def raise_unless_creatable args
43
+ raise NoMethodError, 'subclasses must provide self.raise_unless_creatable'
44
+ end
45
+ end
46
+
47
+ def == other
48
+ if respond_to?( :id ) and
49
+ other.respond_to?( :id ) and
50
+ ( ( not id.nil? ) and ( not other.id.nil? ) )
51
+ id == other.id
52
+ else
53
+ super
54
+ end
55
+ end
56
+
57
+ def collection_name
58
+ self.class.collection_name
59
+ end
60
+
61
+ def entity_name
62
+ self.class.entity_name
63
+ end
64
+
65
+ def to_s
66
+ "#{entity_name}: #{ MultiJson.dump serializable_hash }"
67
+ end
68
+
69
+ alias :inspect :to_s
70
+
71
+ private
72
+
73
+ def short_klass_name
74
+ self.class.name.match( /[^:]+$/ )[0] + ': '
75
+ end
76
+ end
77
+ end
78
+
79
+ require 'subledger/domain/formatters'
80
+ require 'subledger/domain/roles'
81
+
82
+ require 'subledger/domain/value'
83
+
84
+ require 'subledger/domain/account'
85
+ require 'subledger/domain/balance'
86
+ require 'subledger/domain/book'
87
+ require 'subledger/domain/category'
88
+ require 'subledger/domain/control'
89
+ require 'subledger/domain/identity'
90
+ require 'subledger/domain/journal_entry'
91
+ require 'subledger/domain/key'
92
+ require 'subledger/domain/line'
93
+ require 'subledger/domain/org'
94
+ require 'subledger/domain/report'
95
+ require 'subledger/domain/report_rendering'
@@ -0,0 +1,65 @@
1
+ module Subledger
2
+ class ExceptionHandler
3
+
4
+ # attempts seconds
5
+ # 1 no point!
6
+ # 2 0.15+
7
+ # 3 0.35+
8
+ # 4 0.75+
9
+ # 5 1.55+
10
+ # 6 3.15+
11
+ # 7 6.35+
12
+ # 8 12.75+
13
+ # 9 25.55+ default max_attempts
14
+ # 10 51.15
15
+ #
16
+ # attempts 11 and up are 2 seconds apart
17
+
18
+ def initialize args
19
+ @name = args[:name]
20
+ @exception = args[:exception] || Exception
21
+ @max_attempts = args[:max_attempts] || 10
22
+ @initial_delay = args[:initial_delay] || 0
23
+ end
24
+
25
+ def with_retry
26
+
27
+ attempts = 1
28
+ total = 0
29
+
30
+ sleep @initial_delay
31
+
32
+ begin
33
+ yield
34
+ rescue @exception => e
35
+ if is_internal?( e ) or ( attempts >= @max_attempts )
36
+ raise e, "FAILURE: #{@name}, attempts: #{attempts}, total: #{total}, #{e}"
37
+ elsif attempts < @max_attempts
38
+
39
+ if attempts < 11
40
+ delay = 2 ** attempts * 0.05
41
+ else
42
+ delay += 2
43
+ end
44
+
45
+ if attempts > 8
46
+ LOG.warn "RETRY: #{@name}, attempts: #{attempts}, delay: #{delay}, #{e}"
47
+ end
48
+
49
+ sleep delay
50
+
51
+ total += delay
52
+ attempts += 1
53
+
54
+ retry
55
+ end
56
+ end
57
+ end
58
+
59
+ private
60
+
61
+ def is_internal? e
62
+ e.class.name.to_s =~ /^Subledger::/
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,295 @@
1
+ module Subledger
2
+ module Interface
3
+ class ClientError < Error; end
4
+
5
+ class Client
6
+
7
+ private
8
+
9
+ attr_reader :client_args
10
+
11
+ COLLECTIONS = { :identity => { :klass => Domain::Identity,
12
+ :root_collection => :identities,
13
+ :state => :active },
14
+ :identities => { :klass => Domain::Identity,
15
+ :root_collection => :identities,
16
+ :state => :active },
17
+ :archived_identity => { :klass => Domain::ArchivedIdentity,
18
+ :root_collection => :identities,
19
+ :state => :archived },
20
+ :archived_identities => { :klass => Domain::ArchivedIdentity,
21
+ :root_collection => :identities,
22
+ :state => :archived },
23
+ :active_identity => { :klass => Domain::ActiveIdentity,
24
+ :root_collection => :identities,
25
+ :state => :active },
26
+ :active_identities => { :klass => Domain::ActiveIdentity,
27
+ :root_collection => :identities,
28
+ :state => :active },
29
+ :key => { :klass => Domain::Key,
30
+ :root_collection => :keys,
31
+ :state => :active },
32
+ :keys => { :klass => Domain::Key,
33
+ :root_collection => :keys,
34
+ :state => :active },
35
+ :archived_key => { :klass => Domain::ArchivedKey,
36
+ :root_collection => :keys,
37
+ :state => :archived },
38
+ :archived_keys => { :klass => Domain::ArchivedKey,
39
+ :root_collection => :keys,
40
+ :state => :archived },
41
+ :active_key => { :klass => Domain::ActiveKey,
42
+ :root_collection => :keys,
43
+ :state => :active },
44
+ :active_keys => { :klass => Domain::ActiveKey,
45
+ :root_collection => :keys,
46
+ :state => :active },
47
+ :control => { :klass => Domain::Control,
48
+ :root_collection => :controls,
49
+ :state => :active },
50
+ :controls => { :klass => Domain::Control,
51
+ :root_collection => :controls,
52
+ :state => :active },
53
+ :archived_control => { :klass => Domain::ArchivedControl,
54
+ :root_collection => :controls,
55
+ :state => :archived },
56
+ :archived_controls => { :klass => Domain::ArchivedControl,
57
+ :root_collection => :controls,
58
+ :state => :archived },
59
+ :active_control => { :klass => Domain::ActiveControl,
60
+ :root_collection => :controls,
61
+ :state => :active },
62
+ :active_controls => { :klass => Domain::ActiveControl,
63
+ :root_collection => :controls,
64
+ :state => :active },
65
+ :org => { :klass => Domain::Org,
66
+ :root_collection => :orgs,
67
+ :state => :active },
68
+ :orgs => { :klass => Domain::Org,
69
+ :root_collection => :orgs,
70
+ :state => :active },
71
+ :archived_org => { :klass => Domain::ArchivedOrg,
72
+ :root_collection => :orgs,
73
+ :state => :archived },
74
+ :archived_orgs => { :klass => Domain::ArchivedOrg,
75
+ :root_collection => :orgs,
76
+ :state => :archived },
77
+ :active_org => { :klass => Domain::ActiveOrg,
78
+ :root_collection => :orgs,
79
+ :state => :active },
80
+ :active_orgs => { :klass => Domain::ActiveOrg,
81
+ :root_collection => :orgs,
82
+ :state => :active },
83
+ :book => { :klass => Domain::Book,
84
+ :root_collection => :books,
85
+ :state => :active },
86
+ :books => { :klass => Domain::Book,
87
+ :root_collection => :books,
88
+ :state => :active },
89
+ :archived_book => { :klass => Domain::ArchivedBook,
90
+ :root_collection => :books,
91
+ :state => :archived },
92
+ :archived_books => { :klass => Domain::ArchivedBook,
93
+ :root_collection => :books,
94
+ :state => :archived },
95
+ :active_book => { :klass => Domain::ActiveBook,
96
+ :root_collection => :books,
97
+ :state => :active },
98
+ :active_books => { :klass => Domain::ActiveBook,
99
+ :root_collection => :books,
100
+ :state => :active },
101
+ :account => { :klass => Domain::Account,
102
+ :root_collection => :accounts,
103
+ :state => :active },
104
+ :accounts => { :klass => Domain::Account,
105
+ :root_collection => :accounts,
106
+ :state => :active },
107
+ :archived_account => { :klass => Domain::ArchivedAccount,
108
+ :root_collection => :accounts,
109
+ :state => :archived },
110
+ :archived_accounts => { :klass => Domain::ArchivedAccount,
111
+ :root_collection => :accounts,
112
+ :state => :archived },
113
+ :active_account => { :klass => Domain::ActiveAccount,
114
+ :root_collection => :accounts,
115
+ :state => :active },
116
+ :active_accounts => { :klass => Domain::ActiveAccount,
117
+ :root_collection => :accounts,
118
+ :state => :active },
119
+ :category_accounts => { :klass => Domain::Account,
120
+ :root_collection => :category_accounts,
121
+ :state => :nil },
122
+ :journal_entry => { :klass => Domain::JournalEntry,
123
+ :root_collection => :journal_entries,
124
+ :state => :active },
125
+ :journal_entries => { :klass => Domain::JournalEntry,
126
+ :root_collection => :journal_entries,
127
+ :state => :active },
128
+ :archived_journal_entry => { :klass => Domain::ArchivedJournalEntry,
129
+ :root_collection => :journal_entries,
130
+ :state => :archived },
131
+ :archived_journal_entries => { :klass => Domain::ArchivedJournalEntry,
132
+ :root_collection => :journal_entries,
133
+ :state => :archived },
134
+ :active_journal_entry => { :klass => Domain::ActiveJournalEntry,
135
+ :root_collection => :journal_entries,
136
+ :state => :active },
137
+ :active_journal_entries => { :klass => Domain::ActiveJournalEntry,
138
+ :root_collection => :journal_entries,
139
+ :state => :active },
140
+ :posting_journal_entry => { :klass => Domain::PostingJournalEntry,
141
+ :root_collection => :journal_entries,
142
+ :state => :posting },
143
+ :posting_journal_entries => { :klass => Domain::PostingJournalEntry,
144
+ :root_collection => :journal_entries,
145
+ :state => :posting },
146
+ :posted_journal_entry => { :klass => Domain::PostedJournalEntry,
147
+ :root_collection => :journal_entries,
148
+ :state => :posted },
149
+ :posted_journal_entries => { :klass => Domain::PostedJournalEntry,
150
+ :root_collection => :journal_entries,
151
+ :state => :posted },
152
+ :line => { :klass => Domain::Line,
153
+ :root_collection => :lines,
154
+ :state => :active },
155
+ :lines => { :klass => Domain::Line,
156
+ :root_collection => :lines,
157
+ :state => :active },
158
+ :archived_line => { :klass => Domain::ArchivedLine,
159
+ :root_collection => :lines,
160
+ :state => :archived },
161
+ :archived_lines => { :klass => Domain::ArchivedLine,
162
+ :root_collection => :lines,
163
+ :state => :archived },
164
+ :active_line => { :klass => Domain::ActiveLine,
165
+ :root_collection => :lines,
166
+ :state => :active },
167
+ :active_lines => { :klass => Domain::ActiveLine,
168
+ :root_collection => :lines,
169
+ :state => :active },
170
+ :posted_line => { :klass => Domain::PostedLine,
171
+ :root_collection => :lines,
172
+ :state => :posted },
173
+ :posted_lines => { :klass => Domain::PostedLine,
174
+ :root_collection => :lines,
175
+ :state => :posted },
176
+ :account_line => { :klass => Domain::PostedLine,
177
+ :root_collection => :account_lines,
178
+ :state => :nil },
179
+ :account_lines => { :klass => Domain::PostedLine,
180
+ :root_collection => :account_lines,
181
+ :state => :nil },
182
+ :category => { :klass => Domain::Category,
183
+ :root_collection => :categories,
184
+ :state => :active },
185
+ :categories => { :klass => Domain::Category,
186
+ :root_collection => :categories,
187
+ :state => :active },
188
+ :archived_category => { :klass => Domain::ArchivedCategory,
189
+ :root_collection => :categories,
190
+ :state => :archived },
191
+ :archived_categories => { :klass => Domain::ArchivedCategory,
192
+ :root_collection => :categories,
193
+ :state => :archived },
194
+ :active_category => { :klass => Domain::ActiveCategory,
195
+ :root_collection => :categories,
196
+ :state => :active },
197
+ :active_categories => { :klass => Domain::ActiveCategory,
198
+ :root_collection => :categories,
199
+ :state => :active },
200
+ :report_categories => { :klass => Domain::Category,
201
+ :root_collection => :report_categories,
202
+ :state => :nil },
203
+ :report => { :klass => Domain::Report,
204
+ :root_collection => :reports,
205
+ :state => :active },
206
+ :reports => { :klass => Domain::Report,
207
+ :root_collection => :reports,
208
+ :state => :active },
209
+ :archived_report => { :klass => Domain::ArchivedReport,
210
+ :root_collection => :reports,
211
+ :state => :archived },
212
+ :archived_reports => { :klass => Domain::ArchivedReport,
213
+ :root_collection => :reports,
214
+ :state => :archived },
215
+ :active_report => { :klass => Domain::ActiveReport,
216
+ :root_collection => :reports,
217
+ :state => :active },
218
+ :active_reports => { :klass => Domain::ActiveReport,
219
+ :root_collection => :reports,
220
+ :state => :active },
221
+ :report_rendering => { :klass => Domain::ReportRendering,
222
+ :root_collection => :report_renderings,
223
+ :state => :building },
224
+ :report_renderings => { :klass => Domain::ReportRendering,
225
+ :root_collection => :report_renderings,
226
+ :state => :building },
227
+ :building_report_rendering => { :klass => Domain::BuildingReportRendering,
228
+ :root_collection => :report_renderings,
229
+ :state => :building },
230
+ :building_report_renderings => { :klass => Domain::BuildingReportRendering,
231
+ :root_collection => :report_renderings,
232
+ :state => :building },
233
+ :completed_report_rendering => { :klass => Domain::CompletedReportRendering,
234
+ :root_collection => :report_renderings,
235
+ :state => :completed },
236
+ :completed_report_renderings => { :klass => Domain::CompletedReportRendering,
237
+ :root_collection => :report_renderings,
238
+ :state => :completed }
239
+ }
240
+
241
+ public
242
+
243
+ attr_reader :store
244
+
245
+ def initialize args
246
+ @client_args = args
247
+
248
+ # TODO client should not have @store and #store
249
+
250
+ @store = args[:store]
251
+ end
252
+
253
+ COLLECTIONS.each do |name, attributes|
254
+ klass = attributes[:klass]
255
+ state = attributes[:state] == :nil ? 'nil' : attributes[:state].inspect
256
+ root_collection = attributes[:root_collection]
257
+
258
+ method_text = <<-"EOM"
259
+ def #{name} *args
260
+ @#{name}_dispatcher ||= Dispatcher.new @client_args.
261
+ merge(
262
+ { :klass => #{klass},
263
+ :client => self,
264
+ :state => #{state},
265
+ :root_collection => :#{root_collection} } )
266
+
267
+ if args.empty?
268
+ @#{name}_dispatcher
269
+ else
270
+ @#{name}_dispatcher.new args.first.dup
271
+ end
272
+ end
273
+ EOM
274
+
275
+ class_eval method_text
276
+ end
277
+
278
+ def debit *args
279
+ args.empty? ? Domain::Debit : Domain::Debit.new( *args )
280
+ end
281
+
282
+ def credit *args
283
+ args.empty? ? Domain::Credit : Domain::Credit.new( *args )
284
+ end
285
+
286
+ def zero *args
287
+ Domain::Zero.new
288
+ end
289
+
290
+ def balance *args
291
+ Domain::Balance.new *args
292
+ end
293
+ end
294
+ end
295
+ end
@@ -0,0 +1,20 @@
1
+ module Subledger
2
+ module Interface
3
+ class Dispatcher
4
+ def initialize args
5
+ @klass = args[:klass]
6
+
7
+ @dispatch_args = args
8
+ end
9
+
10
+ def method_missing method, args, &block
11
+ args.delete :store
12
+ args.delete :client
13
+
14
+ merged_args = @dispatch_args.merge args
15
+
16
+ @klass.send method, merged_args, &block
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,2 @@
1
+ require 'subledger/interface/client'
2
+ require 'subledger/interface/dispatcher'