header image

How to Organize Large Rails Applications

   Back to list

I think a lot of you have faced a problem with your Rails application. It’s getting bigger and bigger, it’s really hard to maintain? You have too many models and they are too big? It’s really hard to find something in your code and you have duplicated it in many places? I think that it’s normal.

By default, Rails gives us only three layers – models, controllers and views. Oh, I forgot, we also have helpers!

I’ve tried to prepare some patterns and advice which might be helpful for you – maybe you’re facing this problem right now! Don’t treat it like a silver bullet, there isn’t something like that. Every project and application is different, it has different logic and parts. Some are big monoliths and other ones are still small but they’re getting harder and harder to maintain. Also, it’s really difficult to give some advice about a project, which you haven’t ever seen. Maybe you’ve selected the wrong technologies, gems – it’s really hard to guess.

I tried to describe everything by giving a lot of details and descriptions. If you don’t agree with me, I would completely understand that! Everyone has different thoughts, experience and they’ve faced different problems. In this case, please write some useful advice in the comments – I think that it can really help a lot of people in the future!

Also, remember one thing – many years ago, much more clever people than me invented something called design patterns. Thanks to them, the life of many software engineers is much easier now – maybe it’s worth to check them out and use them?

Namespaces

Let’s start with namespaces. It’s not like a pattern, it’s more like a piece of advice.

One of most the important parts of the application, to me, are namespaces. Thanks to them, it’s much easier to organize everything. I believe that every single feature, part or logic in an application should be separated in its own namespace. You may ask why – the answer is easy. It’s easier to work and maintain an application which has separated logic and files, than an app which has everything in models and controllers, without any naming convention.

Imagine that you work on a huge application – the small part of it you work on is responsible for creating, sending and generating invoices. How would you organize it? Services like GenerateInvoice, SendInvoice? Models like InvoiceLineItem, Invoice?

Maybe it’s a good idea, but when you have added more classes and got a lot of logic connected to it, you will end up with a messy application. Moreover, the application structure doesn’t look good when you open a model/services directory and there is like 300 files inside of it.

How should you separate everything? Well, let’s start with the main entity on which you work. Let’s take our invoice class. I think that we should create a namespace called invoices (the invoice namespace could cause some conflicts with other classes, like invoice.rb etc). Inside of it, create another folder, like line_items. In the line_items you should store everything which is connected with invoice line items.

Next, you should think about your entity’s logic. What does it do and for what is it responsible? If it generates files, create another folder called generate with all available options, like pdf, HTML – whatever. You can also move some common logic to a base class.

In this way, your namespace could look like:

module Invoice
  module Generate
    class Base
      ...
    end
  end
end

module Invoice
  module Generate
    class PDF < Base
      ...
    end
  end
end

module Invoice
  module Generate
    class HTML < Base
      ...
    end
  end
end

It’s much more clear, isn’t it?

Model’s namespaces

As I wrote in the previous paragraph, when you have some complex models like Invoice, InvoiceLineItem or InvoiceTemplate, you should add a namespace in order to keep these models in one place.

So creating a model like Invoice::LineItem or Invoice::Template is a right move. You may also ask what should I do with a model like Inovice – I shouldn’t probably create something like Invoice::Invoice – yes, it’s true. In this case, consider maybe something like Invoice::Model or Invoice::Entity. I don’t think that Invoice::Base is a good idea.

Controller namespaces

Another very important part of your application is controller namespacing. I think that the most important part is to create a few namespaces at the highest level – at the root of your controller namespace – web, api, web/admin, web/user, api/user, api/admin.

In the web namespace we should store everything that is connected to our webpage and that is accessible to all users, admins, and what is not api.

In the api namespace we should store everything that is connected with our api and access should be restricted by an api key or whatever. It’s really much easier to maintain, you can write some access validations on for this part of the application (base_controller.rb) and it won’t touch any other namespace. Also remember about api version namespaces (v1, v2 etc.)

In the admin namespace (api or web) we should store everything that is connected to an admin area, creating users, managing products, whatever and that should only be visible to admins. And once again, you can write any filters or access validations only for this namespace.

In the user namespace (api or web) we should store everything that is connected to user logic, signing in and some actions in a dashboard etc.

In this case, our structure could look like:

-> controllers
  -> api
    -> users
      -> v1
  -> web
    -> admins
      -> base_controller.rb
      ...
    -> users

Models

At the beginning of ourjourney with Rails, we store almost everything in our models, methods which gets some data, another one with complex logic, validations, scopes, relationships etc. But what should they really include? For me, after working on many Rails applications, I think that only relationships, gems methods, and maybe some basic validations (event them, you can move to another application layer).
If you don’t believe me or you don’t feel it, I hope that I can persuade you.

Look, scopes can be replaced by repositories, model’s logic by services, validations for some actions by form objects, formatting data (as_json method for example) by serializers etc.
In this case, we would end only with the part of the logic which is responsible for defining database relationships. Imagine a skinny and clear model, which looks like:

class User < ActiveRecord::Base
  has_many :marks
  belongs_to :classroom
end

Yeah, the rest of the logic is defined in other application layers!

I’m not a fan of callbacks so I’m not writing about them, maybe at the beginning they look awesome, but later you will spend many hours on trying to figure out why something is not working in your application – and it’ll probably turn out that one callback is triggering another one etc.

Repositories

Basically, what is a repository? For me, it’s a class which is responsible for communicating and connecting with a database. It gets data from it, inserts it there and also can delete some data. To this class, you can delegate whole logic which is connected with any ActiveRecord queries.

Imagine now, how your models or controllers would be much thinner if you move your logic to repositories? Yeah, also by adding this layer to your application, everything is much more clear and now you know, where the stuff like queries should be stored. I think that even a new developer in your project won’t be lost thanks to that.

How can we design the repository pattern? I think that the easier way would be to create a Base class, which can be inherited by other repository classes.
In the base class you should implement all core method like find, update, create – while in inherited classes, custom methods like most_recent_publishers (basically something like Rails scopes).

Here is my implementation of the Base class:

module Repositories
  class RecordNotFoundError < StandardError; end
  
  class Base   
    def all
      entity_dataset
    end
    
    def first
      entity_dataset.first
    end
    
    def last
      entity_dataset.last
    end
    
    def find(id)
      entity_dataset.find(id)
    rescue ActiveRecord::RecordNotFound => e
      raise RecordNotFoundError, e
    end
    
    def create(attributes)
      entity.create(**attributues)
    end
    
    def update(id:, attributes:)
      find(id).update(**attributes)
    end
    
    def destroy(id)
      find(id).destroy
    end
    
    private
    
    def entity
      raise NotImplementedError
    end
    
    def entity_dataset
      entity.all
    end
  end
end

And here is the example for User repository:

module Repositories
  class User < Base
    def most_recent_by_name(name:, limit:)
      entity_dataset.
        where(name: name).
        order(:created_at).
        limit(limit)
    end
    
    private
    
    def entity
      User
    end
  end
end

Of course, you can add more methods to the base class, like transactions, new record initialization etc.

There is one more thing, the naming convention – UserRepository or Repositories::Users. I think that you should decide which suits you more.

Decorators

I’m almost sure that you have ever written a method inside of a model which looks like:

def formatted_printed_on
  printed_on.strftime("Printed on %m/%d/%Y")
end

Yeah, models aren’t a good place for storing method like this (even concerns or helpers). You may think – so where should I store them, everyone needs to format data somehow.
That’s true, we need to store it somewhere and here come decorators!

Decorators are object which collect methods which can be responsible for formatting or printing data or objects.
This pattern is really easy, everything that you need to do is to create a ModelDecorator which inherits from SimpleDelegator:

class InvoiceDecorator < SimpleDelegator  
  def formatted_printed_on    
    printed_on.strftime("Printed on %m/%d/%Y")
  end
end

Now, you can use it like:

InvoiceDecorator.new(invoice).formatted_printed_on

Voila, that’s it!

FormObjects

Have you ever had a problem with validations in your models? In some parts of the application, you needed another validations than in different part. Yeah, we have a lot of forms, some are used to register a user, another to edit it – and finally, you ended with many conditions and callbacks.
Here comes FormObject – a class which is responsible for creating and validating an instance of a form, it has separate validations and it does not conflict with another one. Everything is separated and isolated, sounds perfect, isn’t it?

How can we achieve it? It’s really easy, just include in your class ActiveModel::Model module and add some validations. Thanks to ActiveModel::Model, we can call the valid? method, which checks if a form is valid, if it’s true – then you can perform further logic if not, it returns false and displays all errors.

Here is an example, how we can do it:

class RegistrationForm
  include ActiveModel::Model

  attr_accessor :email, :first_name, :last_name

  validates :email, presence: true, email: true
  validates :first_name, presence: true
  validates :last_name, presence: true
  
  def call
    if valid?
      create_user
    else
      false
    end
  end
  
  def persisted?
    false
  end

  private

  def create_user
    # put your logic here
  end
end

 

Thanks to that, our models and controllers are thinner, isn’t it beautiful?

You just need to write just a few lines inside your controller:

class MyController < BaseController
  def create
    @form = RegistrationForm.new(user_params)
    
    if @form.call
      redirect_to some_path
    else
      render :my_view
    end
  end
  
  private
  
  def user_params
  end
end

Entities

Entities are basically objects with a predefined attribute – they can be required or not. Imagine that you need an object which is built on some API response (you need to process a JSON response and build some objects for future purposes) or you just need to build an object in order to represent it somewhere. Here come entities.
They can look very simple, starting from pure Ruby, like:

module Entities
  class User
    attr_accessor :first_name, :last_name
    
    def initialize(first_name:, last_name:)
      @first_name = first_name
      @last_name = last_name
    end
  end
end

Or be simpler (thanks to attr_extras gem):

module Entities
  class User
    pattr_initialize [:first_name!, :last_name!]
  end
end

Or be even complex – type validation (thanks to dry-rb):

module Types
  include Dry::Types.module
end

class User < Dry::Struct
  attribute :first_name, Types::String
  attribute :last_name, Types::String
  attribute :age,  Types::Int
end

You should decide, how what they should look like.

Services

I think that this part is one of the most common patterns in Rails.
What are services – service objects? Basically, they are classes which keep a logic responsible for one thing – they are something like a lib folder. It would be much easier to describe them on an example.
Let’s say that you need a class, which generates a model with some attributes and after that, it creates a Sidekiq job. How would you normally do that? I guess, that probably by adding another method to your model – and yeah, your model is getting fatter and fatter. Or maybe by adding a concern? At the beginning maybe it looks ok, but after a while, you’ll notice that concerns just keep your model’s code splitten in smaller files. And you will end with something like:

class Model < ActiveRecord::Base
  include MyConcern
  include MyOtherConcern
  ...
end

Yeah, in practise inside one class, you have a lot of methods. By using concerns, you just keep them in another place, so it’s like one big class!
Here comes service objects – you can create a class which is responsible for one thing and just does it – everything is isolated and can be reused in different place!

There is one really important thing, try to design them to be reusable by different classes. Trust me, it’s much easier when you have already a service which sends some notification and you can reuse it, instead of writing the same code with another parameter or something like this for next time.
Another thing is to select a right name, it should be precise and describe what a certain service does. And also namespaces! Remember about isolation!

For example, you can do something like this:

module MyNamespace
  class MyCrazyClassWhichDoesSomething
    def call
      create_user
      send_notification
      do_another_thing
      ...
    end

    private

    def create_user
      Users::Create.new(attributes).call
    end

    def send_notification
      Notifications::Send.new(user).call
    end

    def do_another_thing
    end  
  end
end

A lot of people are arguing, what services should return? A result, true/false value etc. I think that you should decide with your team, what solution would be the best for you and for your problem – there is no silver bullet!

As you can see, inside our models, we don’t have this ugly code anymore – every responsibility is separated by a service and you can reuse your service where ever you want!

Workers/Jobs
It’s not about building something new – adding another layer to our application (basically, jobs are already added to Rails 5). I just want to mention workers, because they are really important. Maybe not workers but the background processing in your application.

You should remember, that anything complex and that needs more time to process, should be done in your workers. Don’t try to run some complex tasks by calling an action from a link! It’ll block your web resources! Try to process actions like: bulk mailing, generating reports and reindexing your Elasticsearch core in background – it won’t hurt your application.

Remember to use Sidekiq, ActiveJob or Resque, it’s really important and it’ll save a lot of your time, resources and your money!

Serializers

In many Rails applications, I see that people are using a lot of different things, serializers, calling render json method directly from controllers, creating to_json methods or using rabl/jbuilder views. Sometimes it’s really hard to decide, which solution would be the best for our needs.

I think that serializers are really awesome, they help to build JSONs without any pain, moreover, you can create various serializers for the same resources – for example, for an admin you need a different object than for a user – and you’re defining two, different serializers.
I think that the most important thing is not to mix everything in your project – select one approach and try to use it. I know that in some cases you’ll end with calling from your controller render json: { … }, you can’t avoid it – but you can avoid rendering your models/objects from views, instead of using serializers.

To be clear – I’m not saying that jbuilder views are bad – they are really awesome and powerful – you should select a technology/solution to your problem, not in an opposite way.
In many cases, serializers are helpful and will help to maintain your project.

There are some ready made solutions – ActiveModel Serializers (from Rails) and Roar representers (from Trailblazer) but you can also build something from the scratch!

The final structure and thoughts

Finally, you should check your application’s structure. How does it look? Do you feel that it takes too long to scroll down when you click on the model folder? Probably.
By adding some extra layers to your application and extending the default ones provided by Rails, your application could look like:

-> app
-> assets
-> controllers
-> decorators
-> entities
-> forms
-> helpers
-> mailers
-> models
-> repositories
-> serializers
-> services
-> views
-> workers

Isn’t it much clearer and easier to maintain? Now you know where everything is placed!

Also, there are different methods and patterns which help you improve your application or simplify your logic, like event sourcing, CQRS, microservices and stuff like that! I tried to cover the most common and simple things, which would be easy to introduce inside of your application.

As I said before, everything depends on the situation and problem – you should select and decide which solution could best solve your problem! If you feel that I wrote something wrong or something is not clear for you – feel free to leave a comment, I would love to talk with you about it!

Maybe in another article, I’ll try to cover some tools which help you to deal with and maintain your application as well as deploy it and plan it! Thanks for reading this, I’m really glad that you stayed with me till the end of this article!

Send this to a friend