Implementing STI for classes within a module
Single Table Inheritance (STI) as design pattern has been made popular by rails primarily because of the native support and also because it can be very helpful when used judiciously. You can read more about it over here. Now, assuming we have a fair enough idea of what STI is and how to use it, let’s try to understand a particular problem and it’s solution.
Let me explain the problem through an example. Consider the following code.
class Application < ActiveRecord::Base
end
class Server < Application
end
class Worker < Application
end
This is a very standard STI implementation. We will have a single Application
table with type
column and we can have custom APIs exposed on Server
and Worker
models. Easy enough.
There is a little caveat to it though. What if Server
and Worker
could also mean something else in our domain and not just an application. In such a case, modules
come in handy wherein we wrap all such related entities into one namespace. This is how our implementation may change.
module Application
class Base < ActiveRecord::Base
# common code goes here
end
class Server < Base
# Server specific implementation goes here
end
class Worker < Base
# Worker specific implementation goes here
end
end
Now, we just don’t refer to Server
but we explicity call it out, saying, Application::Server
. Much cleaner.
But, how does the STI implementation change in such a case? Unless specified otherwise, any model that inherits from ActiveRecord
should have a corresponding table in the database with the same(class) name.
It would not really be wise to have a table with the name as base
as it does not really map to any domain entity. What we can do instead is use one of ActiveRecord
functionality of specifying the table name in the parent class to refer for STI. Checkout the code below.
module Application
class Base < ActiveRecord::Base
self.table_name = 'applications'
# common code goes here
end
class Server < Base
# Server specific implementation goes here
end
class Worker < Base
# Worker specific implementation goes here
end
end
Let’s move ahead.
Now, when we create a new Server
entity, let’s say by doing, Server.create
, it creates an entry in the applications
table with the type Application::Server
. Rails, by default stores the fully qualified class name as type
in the STI table. We might not want this behaviour for a couple of reasons:
- This is redundant information. We are already in the
application
table and hence apppending Application to the type doesn’t really makes sense. - Querying the table becomes difficult. Each of our queries will have to append
Application::
to query on type from theapplication
table
How can we avoid this? Rails has a pretty neat API to overcome this. You can override the store_full_sti_class
method in the parent class(the class inheriting from ActiveRecord
) and make it return false
. This is how our final implementation looks like.
module Application
class Base < ActiveRecord::Base
self.table_name = 'applications'
def self.store_full_sti_class
false
end
# common code goes here
end
class Server < Base
# Server specific implementation goes here
end
class Worker < Base
# Worker specific implementation goes here
end
end
Voila! With these two simple overrides, we can implement STI for classes wrapped in a module.
Thanks for reading!