Inheritance is often frowned upon, because “You wanted a banana but got the whole jungle…“. In some scenarios, it can be a viable alternative to modules composition for sharing behavior. In this tutorial, I will describe a practical use case where using abstract base class pattern plays well with Ruby on Rails controllers layer.
Read on if you want to find out how to “write Java in Ruby”.
Let’s have a look at a sample implementation of an abstract class using plain old Ruby objects:
Base Fruit class cannot be instantiated directly, but it (as much as Ruby allows to) declares an interface expected from its children by explicitly stating that the
description method is not implemented although it is used in an
In this theoretical example, the base
Fruit class knows how to perform the
eat action but delegates the task of describing themselves to its more specialized child classes.
Let’s move on to a more practical example to see how a similar approach can be used to work with Ruby on Rails controllers.
Rails controllers using abstract base classes
Following code samples come from Abot, a Slack plugin that allows sending anonymous feedback messages. The project has two types of HTTP interfaces. A standard one for rendering web pages in the browser, and the other for interacting with the Slack API calls.
Let’s have a look at two abstract base controller classes:
As you can see, controllers are defining shared behavior for each type of HTTP interface. Eg. Web base controller takes care of setting the correct security headers. Slack base controller verifies whether the requests are originating from the official Slack API and have not been tampered with.
Base controllers also implement the error handling. Whenever any of the child classes raises an exception it is propagated to the parent that knows how to deal with it.
I call those base classes abstract because they are never directly referenced in the
config/routes.rb file. In this context, routing to the controller class is an equivalence of directly instantiating an object. Base controllers can still implement the view (eg.
show) methods, but only if they are supposed to be inherited.
Now let’s have a look at the sample child class of a Slack base controller:
Teams using Abot can fine-tune how they want to apply anonymous communication features, and optionally disable e.g. direct messages. The logic for that differs per controller and is determined by
As you can see, the
Slack::DirectFeedbacksController child class implements the
check_permission! method that has been explicitly defined as raising
NotImplementedError in its parent. Because
Slack::BaseContoller runs the
before_action we are forced to implement its more specific version in the child class. The described approach guarantees that we always have to implement the “interface” defined by the parent class.
Different child controller e.g.,
Slack::ChannelFeedbacksController would have a separate implementation of
check_permission! method, validating that the team has enabled channel messages and that a target channel is whitelisted for anonymous communication.
I am using this approach for controllers that encapsulate the common behavior. Being more explicit and verbose by declaring the abstract methods in the parent class can help you avoid the mistake of forgetting to implement a more specialized method down the inheritance chain.
An additional advantage is that extracting the abstract base controllers can help you clearly separate different types of HTTP interfaces for your Rails app.