14/04/2022  |   Software Development

Adding a business logic layer to your Ruby on Rails application

The ActiveManageable gem provides a framework from which to create business logic manager classes

The development team at Circle Software & Design have worked together on numerous software projects over the course of nearly two decades. During that time we’ve become strong advocates for having a dedicated business logic layer within our application code.

The ActiveManageable gem provides a framework from which to create business logic manager classes.

Our experience has taught us that the business logic should be decoupled from & function independently of other application layers; should provide a clear & consistent interface; and follow conventions that are readily understood & maintained.

When we started developing with Ruby on Rails over 10 years ago we transported this pattern into our projects from the outset, extending the MVC pattern to incorporate a business logic layer that sits between the controllers and models. We called these business logic classes “managers” and created a “manager” class for each of our models. At their core they exposed methods for the seven standard CRUD actions: index, show, new, create, edit, update, and destroy. As well as responsibility for the business rules of the application, the classes also performed authorisation and included additional logic to facilitate the advanced searching of records and pagination etc.

In our Ruby on Rails applications, use of this pattern meant we were able to keep our controllers and models skinny. With the addition of managers, we had a clear separation of concerns with the controllers responsible for managing requests, the views dealing with presentation and the models handling attribute level validation and persistence. The manager classes enabled us to create extensive isolated unit tests for the business logic that covered both typical usage and edge cases; allowing the system & integration tests to remain true to their purpose of testing user interaction and the workflow of the application. This pattern was especially beneficial when implementing a public API as we were able to use the same manager classes in the new API controllers without the need to duplicate any logic from our application controllers.

The pattern was also helpful in a development team where at times one developer would be working on back-end logic while another worked on the front-end. The consistent interface and common conventions of the business logic layer also made the onboarding of new developers and maintenance of the application easier.

We have now created the ActiveManageable gem which provides a framework from which to create business logic manager classes in your Ruby on Rails application.

The ActiveManageable manager classes

  1. include methods for the seven standard CRUD actions: index, show, new, create, edit, update, and destroy
  2. can be configured to incorporate authentication, search and pagination logic
  3. enable specification of the associations to eager load, default attribute values, scopes & order when retrieving records and more
  4. perform advanced parsing of parameter values for date/datetime/numeric attributes

To show how ActiveManageable manager classes can be used to create DRY code in skinny controllers, we’ll refactor the following controller index method that retrieves records with an eager loaded association using Pundit, Ransack & Kaminari.

def index
  search = policy_scope(User).ransack(params[:q])
  search.sorts = "name asc" if q.sorts.empty?
  @users = search.result.includes(:address).page(params[:page])

With ActiveManageable configured to use the Pundit, Ransack & Kaminari libraries, the following manager class includes the standard CRUD methods and sets the default order and association to eager load in the index method.

class UserManager < ActiveManageable::Base
  manageable ActiveManageable::ALL_METHODS
  default_order :name
  default_includes :address, methods: :index

Using the manager class, the controller index method can now be rewritten to only include a single call to the index method.

def index
  @users = UserManager.new.index(options: {search: params[:q], page: {number: params[:page]}})

The manager classes provide standard implementations of the seven core CRUD methods. These can be overwritten to perform custom business logic and the classes can also be extended to include the business logic for additional actions, both making use of the internal ActiveManageable methods and variables.

With an Activity model in a CRM application to manage meetings & tasks, a complete action may be required. This could be implemented as follows:

class ActivityManager < ActiveManageable::Base
  manageable ActiveManageable::ALL_METHODS
  def complete(id:)
    @target = model_class.find(id)
    authorize(record: @target, action: :complete?)
    @target.update(completed_by: current_user.id, completed_at: Time.zone.now)

This method makes use of ActiveManageable methods and variables (described in the gem README). In summary, the initialize_state method sets the initial values of the internal variables; the model_class method returns the ActiveRecord class; assigning the record to the target variable makes it accessible both internally and externally via the object method; and the authorize method performs authorization for the current user, record and action using the configuration library.

The controller method can then call the manager method, retrieve the activity that was completed and act on the result.

def complete
  result = manager.complete(id: params[:id])
  @activity = manager.object
  # now redirect based on the result
icon image

Get in touch

Are you looking for advice and direction on creating business logic layers within your application? We’re experts in developing and improving Ruby on Rails applications so if you want to outsource some of your backend or frontend project requirements, get in touch today on hello@circle-sd.com or fill in our contact form to find out how we can help. No project is too small, we’ve been in your shoes and understand the technical complexities involved - we’re on hand to help you in any stage of your product life cycle.