In working on Regdel, an open source bookkeeping system I am writing with Ruby on Rails, I pondered the question about whether to use Single Table Inheritance (STI), an abstract base class, a model namespace, and/or polymorphic associations for the top-level account types: assets, liabilities, equity, revenue, and expenses.
Its really quite a tough call! I want all the accounts to be in one table, i.e. the chart of accounts, and I also want scoped routes to select accounts of each type. For example, a request to “/accounts” would return all the accounts, and a request to “/assets” would only return assets.
I have it working, and I want to review the configuration so that I am certain it is right for this problem domain.
Remember, this is Rails 3.1!
First, the Account class:
class Account < ActiveRecord::Base
include AccountMethods
ACCOUNT_TYPES = ["Asset", "Liability", "Equity", "Revenue", "Expense"]
serialize :attrs
validates :name,
:presence => true,
:uniqueness => true
has_many :entries
has_many :entry_amounts, :through => :entries
acts_as_nested_set
state_machine :initial => :active do
end
end
Now here’s the Asset class:
class Asset < Account end
Basic, right? So this is using STI just fine - the table has a type column which sets Asset when a new Asset is created.
So how about the routes and views? Can we inherit there? Yes, with some help from InheritedResources!
Here is the AccountsController:
class AccountsController < InheritedResources::Base
defaults :resource_class => Account, :collection_name => ‘accounts’, :instance_name => ‘account’
def show
@account = Account.find(params[:id])
@sub_accounts = Account.find_all_by_parent_id(params[:id])
end
end
and the AssetsController:
class AssetsController < AccountsController defaults :resource_class => Asset, :collection_name => 'accounts', :instance_name => 'account' end
Since these are separate controllers, routes.rb needs resources for each:
resources :accounts do
resources :entries
end
resources :bank_accounts
resources :assets
At this point, we might expect that assets have their own views, and usually they need their own, but thanks to the fact that Rails 3.1 has inherited views built in, the controllers that inherit from the accounts controller share the same views!
Thanks to Inherited Resources, the views use resource and resources to access the instance objects, so it greatly simplifies the controllers, too.
For entries, I did something a little different, mainly because I only want new objects to have specific routes, but the rest of the restful actions to share the same controller actions. More specifically, although checks, transfers, invoices, and deposits are all subclasses of entries, they need to be typed upon instantiation for the new action form.
Here’s the routes I have for this:
resources :entries do
collection do
get 'write_check'
get 'transfer_funds'
end
end
Links
- http://stackoverflow.com/questions/598177/layer-supertype-in-activerecord-rails
- http://www.ruby-forum.com/topic/159894
- http://api.rubyonrails.org/classes/ActiveRecord/Base.html
- http://code.alexreisner.com/articles/single-table-inheritance-in-rails.html
- https://gist.github.com/44525
- http://www.strictlyuntyped.com/2008/06/rails-where-to-put-other-files.html
