Chapter 3

What is Modularity: Rails & Modularity Basics

In this chapter, first we will see what makes Ruby on Rails such a good solution for building modular applications. We will learn about Rails engines, how they work, and how to use them as modules. We will then study different modular architectures before picking one and sticking with it for the entire book. Finally, we will talk about the modular web application that we will create in the next chapters.

3.1. Ruby on Rails Magic

In this first part, we want to discuss a few things that make it easy to create modular applications with Ruby on Rails.

3.1.1. Interpreted Language

Since Ruby is an interpreted programming language, there are a few things that we can do very easily. For example, reopening classes and extending them to add more logic.

Before we continue, we want to show you how important this is. Here is a simple example: Let’s say we have two modules (not the ruby ones), X and Y. We define a User model in X and we want to extend that model in Y. All we need to do is create a decorator for the User model inside Y and use something like class_eval to add some methods or associations to it. By doing this inside the Y module, the X module can stay independent and does not care about Y. X can run on its own but will have more features if we add Y. Obviously, since Y is extending X, it comes with a dependency on X.

This is one of the basic requirements for modularity, and this is all possible thanks to Ruby.

3.1.2. Bundler

Bundler is a great dependency management tool.

A quick bundle install will download all the dependencies required by your application and your gems’ dependencies. A good dependency resolver is required to create modular applications, since we will create our modules as libraries. We need to be able to retrieve our modules, and their dependencies, easily.

In the kind of modular applications we are going to help you create, the parent application (containing the modules) will be mostly empty. It will just be a shell containing a set of engines that contain all the logic. As we will see in a minute, we will be using Rails engines as modules. The good news is that we can package those engines as gems, and use Bundler to handle them for us. That’s why Bundler is a great companion for anyone looking to create a modular application.

Once your engines are packaged as gems (we’ll show you how to do this in the last chapter), getting your modular application together will be as simple as requiring your modules in the Gemfile, as seen in Listing 1:

Listing 0.1: Example of including your modules in your Gemfile.rb
gem 'module1', '1.0.0'
gem 'module2', '1.0.0'

3.1.3. Deface

Deface is a neat little gem that will allow us to extend Rails views. We don’t want to say too much yet, but you will soon learn how it works and how to use it. This is one of the pillars of modularity.

3.1.4. Rails engines

We will be using Rails engines as the bricks of our application. Let’s see what they are and how to use them.

3.2. Rails Engines

With Rails 3, a new feature came out: Rails Engines. They are just like regular Ruby on Rails applications but can’t actually live on their own. They need a regular Rails application to run from. Engines and regular Rails applications have a lot in common. First of all, they share the same structure. You will find the following elements in both:

  • Models
  • Controllers
  • Views
  • Assets
  • Tests

The nice thing about engines is that you can integrate them into any Ruby on Rails application. Once an engine is ready to be deployed, it can be encapsulated as a gem and pushed to RubyGems if you want to share it with the community. You can also keep it private, and we will show you how later.

3.2.1. Quick overview of Rails engines

Engines are basically miniature Ruby on Rails applications that can provide new functionalities to the parent application including them. The class defining a Ruby on Rails application is Rails::Application, which inherits a lot of its behavior from Rails::Engine, which defines an engine. A Ruby on Rails application is just a bit more than a Rails engine, and comes with the logic required to run on its own.

3.2.2. Creating an engine

Engines are closely related to plugins. As of Rails 3.0, plugins are not really used anymore, but the command to generate an engine is actually the same one used to generate a plugin. We will see how to generate an engine in the next chapter, when we start working on our modular application.

3.2.3. Mounting an engine

An engine cannot live on its own. Before you can use it, you need to integrate it inside a Rails application. There are actually two ways of doing this, but the one that interests us the most is the one in Listing 2:

Listing 0.2: Mounting an engine in a blank Rails application
Rails.application.routes.draw do
  mount MyEngine::Core::Engine, at: '/', as: 'my_engine'
end

In this example, we mounted the Core engine at the root of a Rails application. If there is nothing inside your parent application, this is the easiest way.

However, if your Rails application has some content, you probably want to do what is shown in Listing 3:

Listing 0.3: Mounting an engine in a Rails application with content
Rails.application.routes.draw do
  mount MyEngine::Forum::Engine, at: '/forum', as: 'forum'
end

Now the Forum engine can be accessed by going to /forum, and the root can be used by the parent application.

3.2.4. Namespacing

Engines can, and should, be namespaced. This means that two models with the same name, one in an engine and one in the host application, won’t clash. When generating an engine, everything is automatically namespaced: tables, models, controllers. If you generate an engine named Core containing a model named User, the generated table will be core_users and your model will be Core::User.

Throughout this book, we recommend going one step further and adding another level of namespacing. If you plan to create a lot of engines and want to avoid any future conflict, you can simply namespace all your engines with your project name or your company’s name.

In other words, instead of having:

Listing 0.4: Default engine namespacing
module ModuleName
  class ModelName < ActiveRecord::Base
  end
end

you would have:

Listing 0.5: Adding an extra level of namespacing
module GlobalModuleName
  module ModuleName
    Class ModelName < Activerecord::Base
    end
  end
end

With this system, you can keep your engines organized under a common namespace. You can then rest assured that you won’t have any problems when adding external engines to your application.

Box 3.1. Special Thanks!

A special thanks to James Adam, Piotr Sarnacki, the Rails Core Team, and a number of other people for their awesome work on the Rails Engines!

3.2.5. Extending an engine

We have already spoken a bit about extending an engine, and we will get to the coding part in the next chapter. There are basically three things to extend inside a Ruby on Rails application.

  • Models: Can be extended using a decorator and class_eval, or other similar methods.
  • Views: Can be extended with the Deface gem.
  • Controllers: Can be extended just like models: a decorator and class_eval.

There is actually a fourth entity that we will have to extend, but we will get to that a little later.

3.2.6. Engines in the wild!

Even if you have just discovered Rails Engines, you have probably been using them for a while. One of the most famous authentication gems is, in fact, an engine. Of course, we’re talking about Devise! Basically, any gem that will provide you with some Rails-related component, such as migrations, controllers, views or models, is not just a gem: it is actually an engine packaged as a gem.

A few examples of famous engines:

  • Devise

    Devise is a Rack Based engine that provides a complete authentication system for any Ruby on Rails application.

  • Spree Ecommerce

    Spree is a complete open source e-commerce solution built with Ruby on Rails as a set of engines. Spree consists of several different gems, each of which is maintained in a single repository and documented in a single online documentation.

  • Thredded

    Thredded is a Rails 4.2+ forum/messageboard engine. Its goal is to be as simple and feature-rich as possible.

3.3. Modular Architectures

Creating a modular application is quite different from creating a regular monolithic application. In this section, we will go over the different architectures that you can use to build a modular application with Ruby on Rails.

3.3.1. What is a monolithic application?

Regular Ruby on Rails applications are monolithic (as a reminder, we are not talking about the Rails framework itself, which is modular, but the application you are building using Ruby on Rails). They are self-contained applications that will do what you programmed them to do. Note that this is not a bad thing. Ruby on Rails is known for its fast prototyping that will let you create an MVP (Minimum Viable Product) easily.

In software engineering, a monolithic application describes a software application which is designed without modularity.

— Wikipedia

This is exactly what we are trying to avoid by building modular applications. Monolithic applications have their place… just not in this book!

Note that we are not advocating that you create only modular applications. You can still create and maintain monolithic applications when you don’t need the modular aspects. Regular applications are also easier to work with. It is all about picking the right tool for the right job.

3.3.2. What is a modular application?

Now, let’s talk about modularity.

There is no single way to organize your code. Here, we’ll cover three different ways. We are certain there are more, and if you have any suggestions we are more than happy to talk about them with you.

As we said earlier, we are going to cut up our monolithic application into small modules. But what should we put in these modules? Since we will be using Rails engines, we can put models, controllers, views, migrations, tests and so on. Basically, everything that you will find inside a Ruby on Rails application.

So how do we organize all this? Should one module have all the models, one have all the controllers, etc? Or should one module have a model-view-controller silo?

3.3.3. Core engines vs Feature engines

In this section, and in future chapters, we will refer to Core engines and Feature engines. When building a modular application, you need at least one Core engine. This engine will contain things like configuration and basic functionality. Feature engines extend the Core(s) and provide specific features.

The best way to understand the difference is by reading the following chapters, where we will build a Core engine followed by two Feature engines.

3.3.4. Approach 1 – Three Tier Modules

You should be familiar with the three-tier architecture. If not, here’s a definition.

Three-tier architecture is a client-server software architecture pattern in which the user interface (presentation), functional process logic (“business rules”), computer data storage and data access are developed and maintained as independent modules, most often on separate platforms.

— Wikipedia

Since the application’s views are so closely linked to the rest of the application in Ruby on Rails, a good way to follow this pattern is by creating an API and a Javascript frontend application.

The idea is to separate the application into the following modules/engines:

  • Engine 1 - Data Engine

    This engine contains the data layer: models, migrations and model specs.

  • Engine 2 - API

    This engine contains an API, allowing external applications to connect to it through RESTful URLs. This engine depends on the Data Engine (Engine 1) above.

  • Engine 3 - Javascript application

    The Javascript application should be built with the help of a front-end Javascript framework, such as React or Angular.js. The code can be contained inside a Rails engine and included inside the parent application. Another way is to create the Javascript application as a standalone application, using tools like Yo, Grunt and Bower.

Figure 1 is a diagram representing this architecture:

https://s3.amazonaws.com/devblast-modr-book/images/figures/01_03/three_tier_modules
Figure 1: Three Tier Modules

This architecture can be optimized based on your needs. For example, you could put Engine 2′s logic directly inside the parent application. You could also separate Engine 1 into smaller engines. Keep reading to learn more about other architectures so you can find the perfect one for your needs!

3.3.5. Approach 2 – Hybrid Component-Based

This approach is the first one we tried when we first started working on modular applications, however, with time we realized that it’s not the best solution. The idea is to have a couple of engines representing the minimal logic of your application. You can later add new features encapsulated inside new engines.

With this architecture, the Core of your application will be composed of at least two engines:

  • Core Engine 1: Models & Migrations engine
  • Core Engine 2: Controllers & Views engine

Now, you can start adding more modules, which will depend on this Core.

  • Feature 1 engine (models + controllers + views)
  • Feature 2 engine (models + controllers + views)
  • Feature 3 engine (models + controllers + views)

This might look a bit abstract, so to give you something more concrete here’s what you might include in your Gemfile.rb with this architecture:

Listing 0.6: An example of including your engines in your Gemfile.rb
gem 'core_data', '1.0'
gem 'core_view', '1.0'
gem 'feature_1', '1.0'
gem 'feature_2', '1.0'
gem 'feature_3', '1.0'

Figure 2 shows you the complete architecture:

https://s3.amazonaws.com/devblast-modr-book/images/figures/01_03/hybrid_component_based
Figure 2: Hybrid Component Based

3.3.6. Approach 3 – Full Component-Based

The Full Component-Based architecture is a refined version of the previous example. In this one, we remove the Core engines and the separation of the models, views and controllers into different engines. Instead, each engine encapsulates all the components required to make it work. In other words, each engine is a micro Rails application containing models, controllers, views and specs.

  • Core (models + controllers + views)
  • Feature 1 (models + controllers + views)
  • Feature 2 (models + controllers + views)
  • Feature 3 (models + controllers + views)

Figure 3 is a diagram depicting this approach:

https://s3.amazonaws.com/devblast-modr-book/images/figures/01_03/full_component_based
Figure 3: Full Component Based

In this case, the Core could be run alone, inside the containing application, but would offer only limited features. For example, we could simply include the minimum code for users to be able to log in and access an empty dashboard. We can then add completely encapsulated features to the core to improve our application.

This is a very good solution to build modular applications with Ruby on Rails, which is why we will pick this architecture for our example. By using layers like this, we can easily and completely encapsulate one specific feature inside one engine. Each engine can also be independent from the others, except for their dependency to the Core engine.

3.4. Build a modular CRM

The best way to learn something is by doing it.

As French people like to say:

It is by forging that one becomes a blacksmith.

— French people

So we’re going to build a little modular application. We want to show you how to do this by using the various techniques that we have learned the hard way.

3.4.1. The Idea

We’re going to build a CRM (Customer Relationship Management), and we’re going to call it BlastCRM (because that’s an amazing name). CRMs are usually used by salespeople in companies to keep track of their contacts, meetings, calls and so on. So what should we put inside BlastCRM?

3.4.2. BlastCRM

Our CRM will be composed of the following:

  • The Parent application

    This will be an almost empty Ruby on Rails application that will contain all the engines. It will be our container application.

  • The Core module

    This engine will put the foundations of our CRM in place. We will include everything related to users: login, authentication, authorization, and administration. We will also define the style for the application and create the dashboards that will be extended by the modules at a later stage.

  • The Contacts module

    A basic CRUD for contacts that will extend the Core module.

  • The Tasks module

    A basic CRUD for tasks that will extend the Core and Contacts modules.

Let’s code!