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,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
|