Inheritance and Abstract Class Pattern for Ruby on Rails Controllers Updated Jan 7, 2020

4 minute read

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”.

Theoretical example

Let’s have a look at a sample implementation of an abstract class using plain old Ruby objects:

class Fruit def initialize raise "Cannot initialize an abstract Fruit class" end def tasty? true end def eat puts "I'm eating a #{ description } ." end private def description raise NotImplementedError end end class Apple < Fruit attr_reader :color def initialize ( color ) @color = color end private def description " #{ color } apple" end end Fruit . new => RuntimeError ( "Cannot initialize..." ) apple = Apple . new ( "red" ) apple . tasty? => true apple . eat => "I'm eating a red apple."

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 eat method.

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:

app/controllers/web/base_controller.rb

class Web :: BaseController < ApplicationController before_action :set_headers layout "web" rescue_from AuthenticationError do | e | ExceptionNotifier . notify_exception ( e ) flash [ :error ] = "Access denied." redirect_to root_path end private def set_headers response . headers [ "Content-Security-Policy" ] = "..." ... end end

app/controllers/slack/base_controller.rb

class Slack :: BaseController < ApplicationController before_action :check_slack_signature! , :check_permission! layout "slack" rescue_from PermissionError do | e | render "slack/base/permission_error" , locales: { error: e . message } end private def check_slack_signature! # Verify API request origin ... end def check_permission! raise NotImplementedError end end

Source code has been simplified for brevity.





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. index 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:

app/controllers/slack/direct_feedbacks_controller.rb

class Slack :: DirectFeedbacksController < Slack :: BaseController def create # Logic for rendering the feedback dialog using Slack API ... render :create end private def check_permission! return if current_team . permit_direct? raise PermissionError , "Direct feedback messages are disabled for your team." end end

Part of the implementation has been skipped because it's not relevant for this blog post.





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 check_permission! method.

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 check_permission! in 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.

Summary

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.