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
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ .DS_Store
2
+ .rbx
3
+ .bundle/
4
+ coverage/
5
+ tmp/
6
+ public
7
+ vendor/
8
+ Gemfile.lock
9
+ subledger-*.gem
data/LICENSE.txt ADDED
@@ -0,0 +1,12 @@
1
+ Copyright (c) 2014, Subledger, Inc.
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
5
+
6
+ 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
7
+
8
+ 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
9
+
10
+ 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
11
+
12
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/README.md ADDED
@@ -0,0 +1,136 @@
1
+ [![Build Status](https://magnum.travis-ci.com/boocx2/subledger.png?token=xpzDphMqDV2co7CrgGkL&branch=reporting)](https://magnum.travis-ci.com/boocx2/subledger)
2
+
3
+ Signup:
4
+
5
+ gem install subledger
6
+
7
+ subledger
8
+
9
+ Welcome to Subledger, the world's first in-app accounting API!
10
+
11
+ To stop this script at any time simply press control-C.
12
+
13
+ We require your email address for verification.
14
+
15
+ Please enter your email address:
16
+ > your@email.com
17
+
18
+ creating key...created!
19
+
20
+ Your key: Gid1vE0lStRnPNCx9GFmL5
21
+
22
+ You *must* keep it 100% secure, or your data may be accessed by others.
23
+
24
+ Would you like your key written into ~/.subledger for you?
25
+ This prevents the need for you to enter it or put it in source code.
26
+ > n
27
+
28
+ OK, please store your key and secret securely.
29
+
30
+ > y
31
+
32
+ Your key has been written to ~/.subledger
33
+
34
+ Your key is not yet ready to use, it must be verified via email.
35
+
36
+ Please enter the verification code we sent you via email:
37
+ > V6XxRwP5j9ldaeDjOpbkv7
38
+
39
+ Verifying key...verified!
40
+
41
+ Sorry, that verification code does not match your key. Please try again.
42
+
43
+ Next, we'll create an organization.
44
+
45
+ The organization name will be present in URLs, and must be unique.
46
+
47
+ What organization name would you like to use?
48
+ > subledger
49
+
50
+ creating organization "subledger"...created!
51
+
52
+ Your key has been granted access to your new organization.
53
+
54
+ Next we'll create a book in the subledger organization to create accounts in.
55
+
56
+ A book tracks the state of one dimension of your business.
57
+
58
+ The most common book tracks the state of currency in your business.
59
+
60
+ What would you like to name your book? We'd suggest Dollars, Euros, Yen, etc.
61
+ > USD
62
+
63
+ creating book "USD"...created!
64
+
65
+ Would you like to create a very basic set of accounting accounts?
66
+ > n
67
+
68
+ > y
69
+
70
+ creating account "1000 Assets"...created!
71
+ creating account "2000 Liabilities"...created!
72
+ creating account "3000 Equity"...created!
73
+ creating account "4000 Income"...created!
74
+ creating account "5000 Cost of goods sold"...created!
75
+ creating account "6000 Expenses"...created!
76
+
77
+ Would you like to become virtually wealthy?
78
+ > n
79
+
80
+ Ok, just remember: early to bed, early to rise, makes a man healthy, wealthy and wise! :-)
81
+
82
+ > y
83
+
84
+ posting opening balances...posted!
85
+
86
+ Congratulations, you've successfully setup Subledger!
87
+
88
+ Your private and secure API explorer now exists at:
89
+
90
+ https://api.subledger.com/
91
+
92
+ You'll need to enter your key_id and secret where indicated.
93
+
94
+ Post Signup:
95
+
96
+ Gemfile:
97
+
98
+ gem install subledger
99
+
100
+ Client setup:
101
+
102
+ subledger = Subledger.new :key_id => '',
103
+ :secret => '',
104
+ :org_id => '',
105
+ :book_id => ''
106
+
107
+ Basic accounting objects:
108
+
109
+ debit = subledger.debit '1'
110
+ credit = subledger.credit '1'
111
+ zero = subledger.zero '0'
112
+
113
+ debit + credit == zero
114
+ debit + debit + credit == debit
115
+ debit + credit + credit == credit
116
+
117
+ book = subject.books.create :description => 'description of book', # required
118
+ :reference => 'http://url.of.choice' # optional, must be a URL if present
119
+
120
+ book = subledger.books.read :id => book.id
121
+
122
+ book.update :description => '',
123
+ :reference => ''
124
+
125
+ book.archive
126
+ book.activate
127
+
128
+ subledger.books.collect :state => :active|:archived,
129
+ :action => :before|:ending|:starting|:after,
130
+ :description => 'description',
131
+ :limit => 25
132
+
133
+ subledger.books.collect :state => :active|:archived,
134
+ :action => :preceding|:following,
135
+ :id => another_book_in_this_org.id,
136
+ :limit => 25
data/Rakefile ADDED
@@ -0,0 +1,5 @@
1
+ # DO NOT EDIT THIS FILE! (Create new task(s) using lib/tasks/*.rake convention
2
+
3
+ Dir[ File.expand_path( __FILE__ + '/..' ) + '/lib/tasks/*.rake' ].each do |f|
4
+ load f
5
+ end
data/bin/subledger ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env bash
2
+
3
+ echo "Welcome to the Subledger CLI!"
4
+ echo
5
+
6
+ irb -Ilib -rsubledger
@@ -0,0 +1,32 @@
1
+ require 'celluloid/autostart'
2
+
3
+ Celluloid.logger = Subledger::LOG
4
+
5
+ module Subledger
6
+ class Actor
7
+ include Celluloid
8
+
9
+ private
10
+
11
+ def worker type
12
+ worker_actor = nil
13
+
14
+ while ( worker_actor = Celluloid::Actor[ type ] ).nil?
15
+ sleep 0.1
16
+ end
17
+
18
+ worker_actor
19
+ end
20
+
21
+ CLIENT_HASH = 'client_hash'
22
+ ORG_ID = 'org_id'
23
+ BOOK_ID = 'book_id'
24
+
25
+ def client_for args
26
+ client_hash = args[CLIENT_HASH]
27
+
28
+ Subledger.new :org_id => client_hash[ORG_ID],
29
+ :book_id => client_hash[BOOK_ID]
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,25 @@
1
+ module Subledger
2
+ module CollectionName
3
+ def self.without_state args
4
+ anchor = args[:anchor]
5
+ klass = anchor.class
6
+
7
+ if klass == Domain::PostedLine and not anchor.account.nil?
8
+ :account_lines
9
+ else
10
+ klass.root_klass.collection_name
11
+ end
12
+ end
13
+
14
+ def self.with_state args
15
+ klass = args[:klass]
16
+ state = args[:state]
17
+
18
+ if klass == Domain::PostedLine and not args[:account].nil?
19
+ :account_lines
20
+ else
21
+ "#{state}_#{klass.root_klass.collection_name}".to_sym
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,168 @@
1
+ module Subledger
2
+ module Domain
3
+ class AccountError < Error; end
4
+
5
+ class Account
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
+ Account
39
+ end
40
+
41
+ def self.sub_klasses
42
+ [ active_klass, archived_klass ]
43
+ end
44
+
45
+ def self.active_klass
46
+ ActiveAccount
47
+ end
48
+
49
+ def self.archived_klass
50
+ ArchivedAccount
51
+ end
52
+
53
+ def self.first_and_last_line args
54
+ store = args[:store]
55
+ client = args[:client]
56
+
57
+ id = args[:id]
58
+
59
+ if id.nil?
60
+ raise AccountError, 'cannot get first and last lines without an :id'
61
+ end
62
+
63
+ args.merge! :account => client.accounts( :id => id )
64
+
65
+ store.first_and_last_line args
66
+ end
67
+
68
+ def initialize args
69
+ describable args
70
+ identifiable args
71
+ storable args
72
+ versionable args
73
+
74
+ @book = args[:book]
75
+ @normal_balance = args[:normal_balance]
76
+ end
77
+
78
+ def line args
79
+ client.posted_lines( args.merge! :account => self )
80
+ end
81
+
82
+ def lines args={ }, &block
83
+ args.merge! :action => args[:action] || :ending,
84
+ :state => args[:state] || :posted,
85
+ :effective_at => args[:effective_at] || Time.now.utc,
86
+ :account => self
87
+
88
+ client.account_lines.collect args, &block
89
+ end
90
+
91
+ def balance args
92
+ at = args[:at]
93
+
94
+ unless at.kind_of? Time
95
+ raise AccountError, ':at must be a Time'
96
+ end
97
+
98
+ store.account_balance :store => store,
99
+ :client => client,
100
+ :account => self,
101
+ :at => at.utc
102
+ end
103
+
104
+ def first_and_last_line
105
+ client.accounts.first_and_last_line :id => self.id
106
+ end
107
+
108
+ class Entity < Grape::Entity
109
+ root 'accounts', 'account'
110
+
111
+ expose :id, :documentation => { :type => 'string', :desc => 'account ID' }
112
+
113
+ expose :book, :format_with => :id,
114
+ :documentation => { :type => 'string', :desc => 'book ID' }
115
+
116
+ expose :description, :documentation => { :type => 'string', :desc => 'description' }
117
+
118
+ expose :reference, :documentation => { :type => 'string', :desc => 'reference URI' }
119
+
120
+ expose :normal_balance, :format_with => :normal_balance,
121
+ :documentation => { :type => 'string', :desc => 'normal balance type' }
122
+
123
+ expose :version, :documentation => { :type => 'integer', :desc => 'version' }
124
+ end
125
+
126
+ private
127
+
128
+ ACCEPTABLE_NORMAL_BALANCES = [ Debit, Credit ]
129
+
130
+ def self.raise_unless_creatable args
131
+
132
+ book = args[:book]
133
+
134
+ if book.nil? or not book.kind_of? Book
135
+ raise AccountError, ':book is required and must be a Book'
136
+ elsif UUID.invalid? book.id
137
+ raise AccountError, ':book must have a valid :id'
138
+ end
139
+
140
+ normal_balance = args[:normal_balance]
141
+
142
+ if normal_balance.nil? or not ACCEPTABLE_NORMAL_BALANCES.include? normal_balance
143
+ raise AccountError, ":normal_balance is required and must be one of #{ACCEPTABLE_NORMAL_BALANCES.join(', ')}"
144
+ end
145
+ end
146
+ end
147
+
148
+ class ArchivedAccount < Account
149
+ class Entity < Account::Entity
150
+ root 'archived_accounts', 'archived_account'
151
+ end
152
+
153
+ def self.sub_klasses
154
+ [ archived_klass ]
155
+ end
156
+ end
157
+
158
+ class ActiveAccount < Account
159
+ class Entity < Account::Entity
160
+ root 'active_accounts', 'active_account'
161
+ end
162
+
163
+ def self.sub_klasses
164
+ [ active_klass ]
165
+ end
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,102 @@
1
+ module Subledger
2
+ module Domain
3
+ class BalanceError < Error; end
4
+
5
+ class Balance
6
+
7
+ # Implements Valuable directly: #value, #amount, #+ and #==
8
+ # Implements Attributable directly: #attributes
9
+
10
+ include Domain
11
+
12
+ include Roles::Restable
13
+
14
+ def initialize args={ :debit_value => Zero.new, :credit_value => Zero.new }
15
+ if args[:debit_value].nil? or args[:credit_value].nil?
16
+ raise BalanceError, ':debit_value and :credit_value required'
17
+ end
18
+
19
+ @debit_amount = args[:debit_value].amount
20
+ @credit_amount = args[:credit_value].amount
21
+ end
22
+
23
+ def debit_value
24
+ debit_amount.zero? ? Zero.new : Debit.new( debit_amount )
25
+ end
26
+
27
+ def credit_value
28
+ credit_amount.zero? ? Zero.new : Credit.new( credit_amount )
29
+ end
30
+
31
+ def balanced?
32
+ value == Zero.new
33
+ end
34
+
35
+ def value
36
+ if debit_amount == credit_amount
37
+ Zero.new
38
+ elsif debit_amount > credit_amount
39
+ Debit.new( debit_amount - credit_amount )
40
+ else
41
+ Credit.new( credit_amount - debit_amount )
42
+ end
43
+ end
44
+
45
+ def amount
46
+ value.amount
47
+ end
48
+
49
+ def + other
50
+ klass = other.class
51
+
52
+ debit_amount = @debit_amount
53
+ credit_amount = @credit_amount
54
+
55
+ if klass == Balance
56
+ debit_amount += other.debit_value.amount
57
+ credit_amount += other.credit_value.amount
58
+ elsif klass == Debit
59
+ debit_amount += other.amount
60
+ elsif klass == Credit
61
+ credit_amount += other.amount
62
+ elsif klass == Zero
63
+ return self.dup
64
+ elsif other.kind_of? Line
65
+ return self + other.value
66
+ else
67
+ raise BalanceError, "cannot add #{other.class.name} to a Balance"
68
+ end
69
+
70
+ self.class.new :debit_value => debit_amount.zero? ? Zero.new : Debit.new(debit_amount),
71
+ :credit_value => credit_amount.zero? ? Zero.new : Credit.new(credit_amount)
72
+ end
73
+
74
+ def == other
75
+ debit_value == other.debit_value and credit_value == other.credit_value
76
+ end
77
+
78
+ def attributes
79
+ { :debit_value => debit_value,
80
+ :credit_value => credit_value,
81
+ :value => value }
82
+ end
83
+
84
+ class Entity < Grape::Entity
85
+ root 'balances', 'balance'
86
+
87
+ expose :debit_value, :format_with => :value,
88
+ :documentation => { :type => 'string', :desc => 'Total debits' }
89
+
90
+ expose :credit_value, :format_with => :value,
91
+ :documentation => { :type => 'string', :desc => 'Total credits' }
92
+
93
+ expose :value, :format_with => :value,
94
+ :documentation => { :type => 'string', :desc => 'Total combined debits and credits' }
95
+ end
96
+
97
+ private
98
+
99
+ attr_reader :debit_amount, :credit_amount
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,111 @@
1
+ module Subledger
2
+ module Domain
3
+ class BookError < Error; end
4
+
5
+ class Book
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 :org
27
+
28
+ def self.post_keys
29
+ [ :org, :description, :reference ]
30
+ end
31
+
32
+ def self.patch_keys
33
+ # TODO should :id be in patch_keys?
34
+
35
+ [ :id, :description, :reference, :version ]
36
+ end
37
+
38
+ def self.root_klass
39
+ Book
40
+ end
41
+
42
+ def self.sub_klasses
43
+ [ active_klass, archived_klass ]
44
+ end
45
+
46
+ def self.active_klass
47
+ ActiveBook
48
+ end
49
+
50
+ def self.archived_klass
51
+ ArchivedBook
52
+ end
53
+
54
+ def initialize args
55
+ describable args
56
+ identifiable args
57
+ storable args
58
+ versionable args
59
+
60
+ @org = args[:org]
61
+ end
62
+
63
+ class Entity < Grape::Entity
64
+ root 'books', 'book'
65
+
66
+ expose :id, :documentation => { :type => 'string', :desc => 'book ID' }
67
+
68
+ expose :org, :format_with => :id,
69
+ :documentation => { :type => 'string', :desc => 'org ID' }
70
+
71
+ expose :description, :documentation => { :type => 'string', :desc => 'description' }
72
+
73
+ expose :reference, :documentation => { :type => 'string', :desc => 'reference URI' }
74
+
75
+ expose :version, :documentation => { :type => 'integer', :desc => 'version' }
76
+ end
77
+
78
+ private
79
+
80
+ def self.raise_unless_creatable args
81
+ org = args[:org]
82
+
83
+ if org.nil? or not org.kind_of? Org
84
+ raise BookError, ':org is required and must be an Org'
85
+ elsif UUID.invalid? org.id
86
+ raise BookError, ':org must have a valid :id'
87
+ end
88
+ end
89
+ end
90
+
91
+ class ArchivedBook < Book
92
+ class Entity < Book::Entity
93
+ root 'archived_books', 'archived_book'
94
+ end
95
+
96
+ def self.sub_klasses
97
+ [ archived_klass ]
98
+ end
99
+ end
100
+
101
+ class ActiveBook < Book
102
+ class Entity < Book::Entity
103
+ root 'active_books', 'active_book'
104
+ end
105
+
106
+ def self.sub_klasses
107
+ [ active_klass ]
108
+ end
109
+ end
110
+ end
111
+ end