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.
In this first part, we want to discuss a few things that make it easy to create modular applications with Ruby on Rails.
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.
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:
gem 'module1', '1.0.0'
gem 'module2', '1.0.0'
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.
We will be using Rails engines as the bricks of our application. Let’s see what they are and how to use them.
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:
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.
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.
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.
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:
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:
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.
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:
module ModuleName
class ModelName < ActiveRecord::Base
end
end
you would have:
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.
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!
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.
class_eval
, or other similar methods.
Deface
gem.
class_eval
.
There is actually a fourth entity that we will have to extend, but we will get to that a little later.
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.
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.
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.
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?
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.
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:
This engine contains the data layer: models, migrations and model specs.
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.
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:
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!
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:
Now, you can start adding more modules, which will depend on this Core
.
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:
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:
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.
Figure 3 is a diagram depicting this approach:
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.
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.
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?
Our CRM will be composed of the following:
This will be an almost empty Ruby on Rails application that will contain all the engines. It will be our container application.
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.
Contacts
module
A basic CRUD for contacts that will extend the Core
module.
Tasks
module
A basic CRUD for tasks that will extend the Core
and Contacts
modules.
Let’s code!