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