Modular Rails The Complete Guide To Modular Rails Applications - Thibault Denizet
Modular Rails The Complete Guide To Modular Rails Applications - Thibault Denizet
Thibault Denizet
Contents
Acknowledgements
Foreword
Chapter 1 Introduction
1.1 Who Am I ?
1.2 Who is this book for?
1.3 How is this book organized?
1.4 How to read this book?
1.5 Errors, bugs, problems?
1.5.1 Typos & bugs
1.5.2 About the code in this book
1.5.3 Module & Engine
1.5.4 Requirements
Chapter 2 The Modular Way
2.1 What is modularity
2.2 Modularity in programming
2.3 Advantages and Drawbacks
2.3.1 Advantages
2.3.2 Drawbacks
2.4 When should you build modular web applications?
2.5 There’s no need to go All-In
Chapter 3 Modular Rails: The Basics
3.1 Ruby on Rails Magic
3.1.1 Interpreted Language
3.1.2 Bundler
3.1.3 Deface
3.1.4 Rails engines
3.2 Rails Engines
3.2.1 Quick overview of Rails engines
3.2.2 Creating an engine
3.2.3 Mounting an engine
3.2.4 Namespacing
3.2.5 Extending an engine
3.2.6 Engines in the wild!
3.3 Modular Architectures
3.3.1 What is a monolithic application?
3.3.2 What’s a modular application?
3.3.3 Understand the difference between Core and Feature engines
3.3.4 Approach 1 – Three Tier Modules
3.3.5 Approach 2 – Hybrid Component Based
3.3.6 Approach 3 – Full Component Based
3.4 Build a modular CRM
3.4.1 The Idea
3.4.2 SamuraiCRM
Chapter 4 Building SamuraiCRM : the core
4.1 What’s the core module ?
4.2 Build it
4.2.1 Part 1: Setup the Rails app
4.2.2 Part 2: The first controller
4.2.3 Part 3: Style our app
4.2.4 Part 4: Users & Authentication
4.2.5 Part 5: The admin panel
4.2.6 Part 6: Authorization
4.2.7 Part 7: Testing
4.3 Round Up
4.3.1 What did we learn?
4.3.2 Main points to remember
4.3.3 Next Step
Chapter 5 SamuraiCRM: The Contacts module
5.1 What’s the Contacts module
5.2 Build it
5.2.1 Part 1: Generate the Contacts engine
5.2.2 Part 2: Contact Model
5.2.3 Part 3: Contacts Controller
5.2.4 Part 4: Extending the Core views
5.2.5 Part 5: Extending the Core models
5.2.6 Part 6: Extending the Dashboard
5.2.7 Part 7: Extending the Core controllers
5.2.8 Part 8: Extending the abilities
5.2.9 Part 9: Do It Yourself! Extend the admin Panel
5.3 Round Up
5.3.1 What did we learn?
5.3.2 Main points to remember
5.3.3 Next Step
Chapter 6 SamuraiCRM: The Tasks module
6.1 What’s the Tasks module
6.2 Build it
6.2.1 Part 1: Generate the Tasks engine
6.2.2 Part 2: Task Model
6.2.3 Part 3: Tasks Controller & Views
6.2.4 Part 4: Adding tasks to the navigation menu
6.2.5 Part 5: Adding a list of tasks to Contacts
6.2.6 Part 6: Extend the dashboard
6.2.7 Part 7: Extending the abilities
6.2.8 Part 8: Do It Yourself! Extend the admin panel
6.3 Round Up
6.3.1 What did we learn?
6.3.2 Main points to remember
6.3.3 Next Step
Chapter 7 From development to production
7.1 Packaging your engines
7.1.1 What’s an engine?
7.1.2 Why package your engines as gems
7.1.3 Make your first module gem
7.1.4 Adding the source
7.2 Managing your engines
7.2.1 Development vs Production application
7.2.2 Working with Git
7.2.3 Where should you push your source code
7.2.4 How to use branches
7.2.5 Versioning your modules
7.2.6 Eating your own dog food
7.3 Publishing your engines
7.3.1 Public engines vs Private engines
7.3.2 Public? Rubygems!
7.3.3 Private? Pick one
7.4 Build a private home for your engines
7.4.1 Setting up Geminabox
7.4.2 Pushing gems to your private gem server
7.4.3 Bundle install your gems
7.4.4 Pushing all our modules
7.5 Push your application
7.6 The modular workflow
7.7 Round Up
7.7.1 What did we learn?
7.7.2 Main points to remember
7.7.3 Next Step
Chapter 8 The End
Acknowledgements
I’d like to thank Philippe D’Acquet who indoctrinated me to modular applications. He
probably had no idea I would write a book about it!
I also want to thank Jean Pierre Dumas and Mike Darnell for allowing me to learn so
much while building my first modular application.
Finally, thank you Pin and Tar, my modularity bros. You guys are awesome!
And of course, thanks Hongyok for your support and your understanding!
Médéric Petit: For checking that everything made at least some sense.
Nicholas Baker: For fixing my French style.
Simon Bonnard: For cheering me up when I wanted to give up.
Cheers!
Foreword
As a Ruby on Rails developer, have you ever encountered problems like slow tests, no re-
usability, and some messes in codes that are not-so-easy to clean up? If you answer “Yes”
to this question, this book is definitely for you. On the contrary, if your answer is “No”,
you could also benefit from this book in plenty of ways such as learning a modular
approach to Ruby on Rails application development, making your application more
configurable, and applying techniques presented in the book to avoid the problems
mentioned above altogether in the future.
The writer of this book, Thibault Denizet, is a colleague of mine at AppyHotel. We are the
only two Ruby on Rails developers in the company, and responsible for AppyHotel’s
backend applications. One day, the business team came and asked us to make the backend
modular. Knowing absolutely nothing about modularity at the time, we went into several
stressful meetings with the business team, did a lot of research on the Internet, and
experimented with suggestions here and there. At last, we have got it, a modular
AppyHotel backend. Developing a Ruby on Rails application with a modular approach
surely has pros and cons like everything else in this world. It is a decision you have to
make whether or not a modular application works for your task in hand.
I have known Thibault for almost 2 years. Keen, confident, and full of energy, he is not
the kind of person you find every day. When something really gets his attention, he will
keep doing it. The word “addicted” could be used here appropriately in my opinion.
These days, Thibault has much interest in LEGO®. The way he builds them is like a Lego
geek would do: building with his mind — building without any instruction provided in
Lego boxes. Building Lego is not far from building modular applications. You start with
the very first brick, then add another, and on and on and on. Do not stop and eventually
you will have a giant robot, or a town, or even the whole world if you may.
In this book, Modular Rails, Thibault shows you how to build a Ruby on Rails application
in a modular way using a mock-up of a CRM system as an example to guide you step-by-
step. He also discusses about the term “modularity” and how Ruby and Rails are suitable
for creating modular applications. With his beautifully creative mind, countless trials and
errors, and hand-on experience in shaping AppyHotel’s modular applications, the
knowledge and effort he puts into his first book here are exceptional. Entry level Ruby on
Rails developers alongside with those who master Ruby on Rails would surely enjoy the
book content and find it valuable. I shall certainly be recommending the book to any
Ruby on Rails developers but particularly to those who suffer from monolithic application
nightmare.
Panitan P.
Chapter 1
Introduction
It’s funny. I had to rewrite this introduction after receiving the foreword you just read.
Initially, I spoke about LEGO® but I think my friend Pin covered it already, so here’s a
short introduction before we get started.
So you know how modular applications work now. Brick by brick, module by module,
but you don’t know yet how you can build a modular application with Ruby on Rails and
that’s why you are reading this book. Building modular Rails application means that you
will build a set of reusable modules, a bit like libraries, some with very specific
functionalities. Then you will just assemble them, just like bricks, and build your
application.
Building modular applications is hard. You won’t be building your web application like
everyone else and finding help is not easy. There’s not much documentation either, but
with this book you will be able to learn a lot. I will also help you if you still have
questions after reading it!
1.1 Who Am I ?
I’m Thibault Denizet. It’s me in the picture. I’m a young and dynamic Ruby on Rails
developer living in Bangkok. I’m actually French but I hate the cold weather. I also
dislike cooking. So I came to a country where it’s always (too) hot and where I don’t have
to cook. Thailand is a great country to live in. If you ever come by, let me know so we
can grab a beer!
My story with modularity started over two years ago when I was charged with the
rewriting of an existing web application into something more configurable and modular.
I’ve been working on a few modular applications since then while still creating regular
apps when modularity was overkill.
I wrote this book because I couldn’t find any documentation when I created my first
modular application. I was studying open source apps and even though reading code is
awesome to learn something, you don’t always understand what’s going on. This is the
book I wish I had at that time. It’s not a bible, far from it, but it will teach you everything
you need to know. I hope you will enjoy it! (If not, please tell me what’s wrong so I can
improve it.)
1.2 Who is this book for?
I think every Ruby on Rails developer should read this book or at least give it a try. It’s
not super long but comes with a lot of ‘underground’ information about engines. If you’re
about to write a modular application, then you should definitely read it.
The goal is to teach you how to build your own modules and at the end of the road, create
complete modular applications.
This book will probably be more useful to web agencies and freelancers, or anyone who
ends up building the same thing over and over again.
1.3 How is this book organized?
This book only has 7 chapters but don’t let that fool you. They are huge chapters with a
lot of sub-chapters!
Chapter 1 - Introduction
This chapter is about the features of Ruby on Rails that allows us to create modular
applications.
First practice chapter. We’ll create a Rails application (SamuraiCRM) and our first
module: the Core! This chapter focuses on configuring a modular application and
integrating modules.
In this chapter, we’ll create a second module (Contacts) to add a new feature to
SamuraiCRM. This chapter will focus on extending models, controllers and views.
Chapter 6 – Practice: SamuraiCRM: The Tasks module
Here we will add another feature to SamuraiCRM: Tasks. We’ll see how we can
extend our previous modules and how to make two modules work together while
keeping them independent.
Chapter 7 – Theory & Practice: From development to production
After building a complete modular application, we will see how to maintain it and
push it to production.
1.4 How to read this book?
This book is meant to be read in the prescribed order: from chapter 1 to chapter 7, but
you’re free, so just read it the way you want. Chapters 4 to 6 contain most of the source
code and that’s probably where you will learn the most, especially if you code along.
However I tried to keep writing code not mandatory by providing explanations and
screenshots so you should be able to just read through and start coding later if you want to.
1.5 Errors, bugs, problems?
I tried my best to provide the best content possible. Unfortunately, you will probably find
typos and bugs. If you do, please report them by sending an email to [email protected].
I will be eternally grateful and will add your name to the thanks page!
You will find a lot of source code in this book because I think the best way to learn more
about coding is by actually seeing code. The code is contained in colored boxes to be
easily noticed. In some cases, the code span more than one page. I did my best to keep
the same code snippet on one page but sometimes I simply could not.
If you’re not sure where some code is supposed to go, just check the first line of the
snippet. You will find a path indicating the file. Most of the time, it will start at the top
level Ruby on Rails application (SamuraiCRM) but sometimes it was too long to stay on
one line. In these cases, you will see the path starting at the engine level. Keep in mind
that engines will be stored in SamuraiCRM/engines/.
Throughout this book I will be using the terms modular and engine a lot. Like a hell of a
lot. I use these two terms interchangeably and when I say module, I’m talking about the
modular module which is an engine and not Ruby modules.
1.5.4 Requirements
To follow the tutorials in this book, you will need some kind of Unix or Linux system.
Indeed, I use the command line intensively to work on modular applications. If you’re on
Windows, you can easily setup a virtual machine with VirtualBox and Ubuntu.
Chapter 2
The Modular Way
Let’s start by defining the concept.
Maybe you don’t know what modularity means or what is a modular architecture. Or
maybe you have a vague idea of what it represents, but no concrete example in mind.
That’s okay, I’m here to help.
This first chapter will take you from knowing nothing about modularity to having a clear
idea of what it is and how it can serve you.
2.1 What is modularity
I believe that to understand something you need to understand its origin. Module find its
source in Latin as ‘modulus’ meaning ‘a small measure’. By extension, modular means
‘composed of interchangeable units’. The antonym of modular is monolithic which
originally was a ‘column consisting of a single large block of stone’. We will see both of
these terms throughout the book so it is important that you remember them.
As you’ve probably already understood it, something modular is built from a set of
smaller, interchangeable pieces. On the other side, we have monolithic things which are
only composed of one element.
Now that you know that modularity is all around us, let’s see how it fits in the
programming world.
2.2 Modularity in programming
Let’s focus on programming now since that’s why you’re here.
So modularity is all about isolating the functionalities of your application into independent
and interchangeable components.
But what does that means exactly? I think the best way to understand this is with an
example, and what better example than the Ruby on Rails framework itself! Ruby on
Rails used to be monolithic but that all changed when Rails 3 was published, after merging
with Merb. Indeed, Rails became modular.
Active Record
Active Model
Active Support
Action View
Action Pack
Action Mailer
And more recently: Active Job
You will usually use all of them in a standard Ruby on Rails application but you could
also build a Ruby program with just Active Record. That would allow you to easily build
a Ruby application that can talk with all kinds of databases. As you can guess from the
names, each module in the Rails framework encapsulates a specific feature. Active
Record contains everything you need to map Ruby classes to a database tables.
Rails modules are obviously highly reusable since they lay the groundwork for any web
application you’d like to build. What we will see in this book is a way to build Ruby on
Rails applications on top of these modules by using the same principles: Re-usability and
functionality encapsulation.
2.3 Advantages and Drawbacks
As we already said, modularity comes with 2 major advantages, the re-usability of the
components and the encapsulation of features into independent chunks of code called
modules. There are actually more advantages!
2.3.1 Advantages
The fact that each module is independent (but a module can be built on top of another one)
makes the coordination and cooperation of different teams much easier. If two teams are
building 2 completely different features on top of a shared Core, even without
communication, there won’t be a lot of conflicts. Why? Simply because they are working
on the equivalent of 2 distinct projects.
Let’s say now that you’re working for a company building complex web applications.
These applications will always require users, authentication and authorization. So why not
build it one time and re-use it everywhere? We can build a Core module which will handle
the users’ CRUD, the way they can login and what they can do. Once it’s done, you can
simply reuse it in every future web application produced by your company, simply by
adapting the style.
Finally, I’d like to talk about tests. Automated testing is an important part of software
creation, be it for web, desktop or mobile. Since features are encapsulated, you can easily
write tests that run in isolation. You can also add tests at the top level of your application
to ensure that everything is working well together. It’s just like the way libraries are tested
to be sure that what they provide is working. Then, you test the code where you use those
libraries to be sure you are getting the behavior you want.
Just like everything, modularity comes with some drawbacks too.
2.3.2 Drawbacks
One of the main drawbacks of modular applications is that it can get quite time-
consuming. Indeed, you are not dealing with one application and one repository. Instead,
you will work with a set of repositories, each containing the source code for a specific
functionality. Depending on the technology used, you will have to update the module, test
it, push the source code, then link it to the main application, re-test it and then push it.
Now, imagine if you have 3, 4 or more modules to update.
That’s a longer process than simply making the change and pushing it live but it’s totally
worth it! Finally, since we have a lot of different mini-projects, we need to keep track of
their version. What is currently used in production, in staging, which versions are ready
for testing, etc. The best way I’ve found to deal with that is simply a Google Doc
Spreadsheet. It allows you to see what is where and is easy enough to update.
Now you know that modularity comes with some stuff that you will need to deal with if
you want to use it in the best way. Before we see more about modular web applications
built with Ruby on Rails, I want to tell you about a few case scenarios where you might
want to build a modular application and when you should simply avoid it.
2.4 When should you build modular
web applications?
A good example of when to create modular applications is the one I talked about above. If
you are a web agency, or a freelancer, and you’d like to reuse some code between clients,
then go for modular applications. As a web agency, you probably build a lot of web
applications, a lot have commons features. A way for people to create accounts, login,
access some parts of the application and so on. You will also often want an Admin panel
to administrate the application. Then you’ll probably have some specific code to add for
each client, depending on their needs. But still, you just saved a lot of time reusing an
existing module. Imagine all the time you can save through 10 or 100 projects!
Another good case scenario for modularity is a startup creating some kind of generic
application with a layer of configuration. That might sounds abstract so let me give you
an example of such an application. Take the Stack Exchange network. If you’re looking
to build something like that, then modularity is your best friend. You would probably
have a set of modules present in every application in your network. Then, you’d have
some specific modules added on top of them to customize the application to a specific
niche. Maybe for one application, you want a rating system, for another one you want to
have commentable questions and answers. All that can be achieved by building modular
applications.
You may have heard about Spree Commerce. Spree is a complete open source e-
commerce solution built with Ruby on Rails. Spree is completely modular and is
composed of 6 modules. Spree was one of my main example when I first started to create
modular applications and I’d like to thank them for the great work they did. If you’re
looking for an ecommerce solution, take a look at what they offer, it’s very nice!
2.5 There’s no need to go All-In
With this book, you will learn how to build modular web applications with Ruby on Rails.
I will present you fully modular application but you don’t have to go to that extend if you
don’t want. Hybrid applications are also great. The idea is to use a few modules and build
a standard Rails application with them. I will talk more about this approach in the next
chapter, after we’ve talked about architecture.
Chapter 3
Modular Rails: The Basics
In this chapter we’ll first see what makes Ruby on Rails such a good solution to build
modular applications. We will learn about Rails engines, how they work and how we can
use them as modules. Then we’ll study different modular architectures before picking one
and sticking with it for the entire book. Finally, I 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, I want to share with you a few of the things that makes it easy to create
modular application with Ruby on Rails.
Since Ruby is an interpreted programming language, there are a few things that we can do
super easily. For example, re-opening classes and extending them to add more logic.
Before we continue, I want to show you how important this is, so 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 that 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 will 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
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 easily get
them and their dependencies.
In the kind of modular applications I am going to show you, the parent application
(containing the modules) is mostly empty. It’s just a shell containing a set of engines
which contains 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 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 see that in the last chapter), getting your
modular application together will be as simple as requiring your modules in the Gemfile.
3.1.3 Deface
Deface is a neat little gem that will allow us to extend Rails views. I don’t want to say too
much yet, but you will learn very soon 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 are they
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 actually a lot in
common. First of all, they share the same structure.
Models
Controllers
Views
Assets
Tests
The nice thing about engines is that you can integrate them to 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 see 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, coming with the logic required to run on its own.
Engines are also closely related to plugins. Plugins are not really used anymore since
Rails 3.0 but the command to generate an engine is actually the same 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 to do this but the one that interests us is
this one:
Rails.application.routes.draw do
mount Samurails::Core::Engine, at: '/', as: 'samurails'
end
In this example, we mounted the Core engine at the root of a Rails application. If there is
nothing inside your parent application, that’s the easiest way.
However, if your Rails application has some content you probably want to do the
following:
Rails.application.routes.draw do
mount Samurails::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. That 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 and a User model, the generated table will be core_users and your
model will be Core::User.
Throughout this book, I 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 just namespace all your engines with your project or with your company name.
Instead of having:
module ModuleName
class ModelName < ActiveRecord::Base
end
end
With this system, you can keep your engines organized under a common namespace. You
can rest assured that you won’t get 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 already talked a bit about extending an engine and we will get into the coding part in
the fourth chapter. There are basically three things to extend inside a Ruby on Rails
application.
Models
Views
Controllers
There is actually a fourth entity that we will have to extend and we will see how later.
Even if you’ve just discovered Rails Engines, you’ve probably been using them for a
while. One of the most famous authentication gems is actually an engine.
Of course, I’m 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’s actually an engine packaged as a gem.
Forem
Forem is an engine for Rails that aims to be the best little forum system ever. The
end goal is to have an engine that can be dropped into an application that provides the
basic functionality of forums, topics and posts.
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 set
of engines. Spree actually consists of several different gems, each of which are
maintained in a single repository and documented in a single set of online
documentation.
3.3 Modular Architectures
Creating a modular application is quite different from creating a regular monolithic
application. In this section, I want to 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 applications. They are self-contained
applications that will do what you programmed them for. 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.
Note that I don’t recommend creating only modular applications. I still create and
maintain monolithic applications because I don’t always need the modular side. Regular
applications are also easier to work with. It’s all about picking the right tool for the right
job.
As we said earlier, we are going to cut our monolithic application in 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 that? Should one module have all the models? Or should one
module have a model-view-controller silo?
In the following section and in the future chapters, I will sometimes talk about Core
engines and Feature engine. When building a modular application, you need at least one
Core engine. This engine will contains things like the configuration and the basic
functionalities. 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 Features engines.
You should be familiar with the three tier architecture. If not, here’s a definition.
Since the views are so tied 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 models specs.
Engine 2 - API
This architecture can be optimized based on your needs. For example, you could put the
Engine 2′s logic directly inside the parent application. You could also separate the Engine
1 into smaller engines. Keep reading to learn more about other architectures to find the
perfect one for your needs!
This approach is the first one I tried when I started to work on modular applications. With
time, I realized that it’s not the best solution. The idea is to have a couple of engines
representing the minimal logic of your application. On this set of engine, 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:
That might look abstract so to give you something more concrete here’s how your Gemfile
might look like with this architecture:
And here is a diagram showing you the complete architecture. Figure 3.2
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. That means each engine is a micro Rails application containing
models, controllers, views and specs.
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 login and access an empty dashboard. We can then add competently
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 to build our modular application. By using layers like
this, we can easily and completely encapsulate one specific feature inside one engine.
Each engine can also be independant from the others, except for their dependency on the
Core.
3.4 Build a modular CRM
The best way to learn something is by doing it.
So we’re going to build a little modular application. I want to show you how to create
your own modular application by using the various techniques I learned the hard way.
We’re going to make a CRM, or Customer Relationship Management, and we’re going to
call it SamuraiCRM because that’s an amazing name. CRM are usually used by
salespeople in companies to keep track of their contacts, meetings, calls and so on. So
what should we put inside SamuraiCRM?
3.4.2 SamuraiCRM
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!
Chapter 4
Building SamuraiCRM : the core
That’s it, you’re about to create your first module. Feeling excited? You should!
4.1 What’s the core module ?
Before we dive in the code, let’s define what is the Core module. This module sets the
foundations of our application. The other engines will be built on top of it by extending its
very basic functionalities.
The dashboard
The dashboard is the first thing a user sees after logging in. In the Core, it will be
pretty much a blank screen. But in the next chapters we will add hooks to it to allow
other modules to extend it. This way, we will be able to fill the dashboard a little bit
in each module.
Authentication
Since we are lazy developers, we will be using the great Devise gem to handle
Authentication. Devise being an engine, we will meet a few problems while using it
inside another engine. Do not worry, I will show you how to make everything work
together!
Authorization
Authorization will be achieved by using CanCan. In future chapters, we will see how
to extend CanCan abilities in other modules.
Specs
The Core module will come with a set of tests to ensure that it’s working correctly. I
will keep the code and tests simple so you can focus your attention on the modular
side. We’ll be using Rspec and Factory Girl.
Modularity
Modularity principles will be explained while building the application and summed
up at the end of the chapter.
Before we actually work on the Core engine, we need a parent application. Let’s generate
one and create our first engine!
Note that this book was written with Ruby version 2.2 and Rails version 4.2.
Fire up a terminal and navigate to your workspace before running the following:
Step 2: Go inside your new application folder and generate a mountable engine
By default, everything inside an engine will be namespaced based on the name you gave
it. Since we are building a modular application and a set of engines that are supposed to
work together, I recommend adding one more namespace. For this application, I will use
the namespace Samurai. It will be the global namespace containing all the engines.
For now, let’s focus on the lib folder located at SamuraiCRM/engines/core/lib. In this
folder, we’ll find the heart of our engine but first, we have to do a bit of reorganization.
Rearrange the lib folder until you have the following structure or run the command below.
SamuraiCRM/engines/core/
lib/
samurai_core.rb
samurai/
core.rb
core/
engine.rb
version.rb
cd lib && mkdir samurai && mv core core.rb samurai/ && touch samurai_core.rb
This file is a very simple file that is going to make the link between our engine and the
parent application. How? By requiring the right files!
# SamuraiCRM/engines/core/lib/samurai_core.rb
require "samurai/core"
require "samurai/core/engine"
And this one is where we load our modules for the first time, before the engine gets
loaded.
# SamuraiCRM/engines/core/lib/samurai/core.rb
module Samurai
module Core
end
end
Since the Core module will be released as a gem, we need to give it a proper version and
add our top-level namespace Samurai.
# SamuraiCRM/engines/core/lib/samurai/core/version.rb
module Samurai
module Core
VERSION = "0.0.1"
end
end
Each Ruby on Rails engine comes with a file named engine.rb. This is really the heart of
the engine. We need to add our new namespace to it. Note that we are also removing
Core from isolate_namespace. We won’t namespace our models and controllers with
Core in the Core module. You will understand why in the next chapter, but for now, you
should know that it makes extending the Core much easier.
# SamuraiCRM/engines/core/lib/samurai/core/engine.rb
module Samurai
module Core
class Engine < ::Rails::Engine
isolate_namespace Samurai
end
end
end
Step 8: Gemspec
Next, we need to give some proper details about our engine inside the gemspec file. Head
over to SamuraiCRM/engines/core/core.gemspec. First, you will want to rename the
file to samurai_core.gemspec to match the namespaces we are now using. Here’s the file
with the changes.
# SamuraiCRM/engines/core/samurai_core.gemspec
$:.push File.expand_path("../lib", __FILE__)
require "samurai/core/version"
Gem::Specification.new do |s|
s.name = "samurai_core" # Rename core to samurai_core
s.version = Samurai::Core::VERSION # Add namespace
s.authors = ["Thibault Denizet"] # Your Name
s.email = ["[email protected]"] # Your Email
s.homepage = "http://samurails.com"
s.summary = "Core features of SamuraiCRM."
s.description = "Core features of SamuraiCRM."
s.license = "MIT"
Step 9: bin/rails
To make everything works smoothly, we need to change one small thing inside the
bin/rails file.
# SamuraiCRM/engines/core/bin/rails
# ...
# Changed from 'lib/core/engine'
ENGINE_PATH = File.expand_path('../../lib/samurai/core/engine', __FILE__)
If you open the routes file for the Core engine located at
SamuraiCRM/engines/core/config/routes.rb, you will see that the Samurai namespace
is missing. Obviously, we need to add it!
# SamuraiCRM/engines/core/config/routes.rb
Samurai::Core::Engine.routes.draw do
end
Go back to the parent application and open the SamuraiCRM/Gemfile file. All we have to
do now is add our module:
# SamuraiCRM/Gemfile
# ...
gem 'samurai_core', path: 'engines/core'
Now, do a quick bundle install from the parent application and everything should work! If
not, review the setup and see if you changed everything correctly.
The Core module is now integrated inside the parent application. But it’s not accessible
yet. For that, we need to mount it inside the parent’s routes file. Don’t expect something
hard, it’s as easy as:
# SamuraiCRM/config/routes.rb
Rails.application.routes.draw do
mount Samurai::Core::Engine => "/", as: 'samurai'
end
Et voila, we are finally done with the setup! You should be able to start your rails server
now but you will end up on the default rails view if you try to access it in a browser. How
about adding some content?
To keep things simple, we won’t be writing tests in a TDD style. However, at the end of
this chapter, we’ll go over the setup of the testing tools and write a few tests so you can
see how to test your modular application.
The first controller we will create is the dashboard_controller. It’s where the users will
land once they logged in. On this page, they should be able to have a summary of
everything that happened recently. Their recent contacts, their recent tasks, their current
opportunities and so on.
For now, we’ll create an empty page because we simply don’t have anything to show yet.
In the next chapters, we will add content to this page with each module.
The first step before continuing is to re-arrange the controller folder inside the Core
engine. Since we will only use Samurai to namespace our controllers, we need to rename
the Core folder.
Your controllers folder should look like the following:
engines/core/app/
controllers/
samurai/
application_controller.rb
Here’s a command you can run from the engine folder to get this structure:
mv app/controllers/core app/controllers/samurai
Now we can add the dashboard_controller which will have only one action: index.
Here’s a command to create it. Run it from the engine.
touch app/controllers/samurai/dashboard_controller.rb
# SamuraiCRM/engines/core/app/controllers/samurai/dashboard_controller.rb
module Samurai
class DashboardController < ApplicationController
def index
end
end
end
# SamuraiCRM/engines/core/config/routes.rb
Samurai::Core::Engine.routes.draw do
root to: "dashboard#index"
end
Now, you can try to access the application. Run rails s and head to localhost:3000.
Or don’t, because it’s gonna crash anyway! We have to fix the views before we can see
anything.
Step 4: Fix the layout and add the index view for the dashboard
Once again, due to the namespace we added, we need to create an extra folder for the
views. Change your views folder until you have the following or use the command below.
SamuraiCRM/engines/core/app
views/
layouts/
samurai/
application.html.erb
samurai/
dashboard/
index.html.erb
Now, if you access localhost:3000, the application works and returns a beautiful white
screen!
We’re going to use bootstrap-sass to style SamuraiCRM. To do that, we need to add a few
gems to the engine gemspec.
# SamuraiCRM/engines/core/samurai_core.gemspec
# ...
s.add_dependency "rails", "~> 4.2.0"
When used inside an engine, gems are not automatically loaded. To load them, we need to
require them inside SamuraiCRM/engines/core/lib/samurai/core.rb:
# SamuraiCRM/engines/core/lib/samurai/core.rb
require 'sass-rails'
require 'bootstrap-sass'
require 'autoprefixer-rails'
module Samurai
module Core
end
end
Let’s run bundle install from the parent application to get all the gems we just added.
Don’t forget to restart your server.
Step 2: Fix the assets folder
Once again, we have to fix and rearrange the assets folder to add one more level. Change
your assets folder inside the Core engine to look like this or use the commands below.
assets/
images/
samurai/
javascripts/
samurai/
application.js
stylesheets/
samurai/
application.css
mv app/assets/images/core app/assets/images/samurai
mv app/assets/javascripts/core app/assets/javascripts/samurai
mv app/assets/stylesheets/core app/assets/stylesheets/samurai
Since we’re going to use Bootstrap, we need to change the application.css file. Simply
rename it to application.css.scss and change its content to:
// SamuraiCRM/engines/core/assets/stylesheets/samurai/application.css.scss
@import "bootstrap-sprockets";
@import "bootstrap";
body {
padding-top: 65px; // For the nav bar
}
Because we changed a bit the assets folders, we need to update the layout file by changing
the stylesheet and javascript link locations.
<!-- SamuraiCRM/engines/core/app/views/layouts/samurai/application.html.erb -->
<!DOCTYPE html>
<html>
<head>
<title>Core</title>
<%= stylesheet_link_tag "samurai/application", media: "all" %>
<%= javascript_include_tag "samurai/application" %>
<%= csrf_meta_tags %>
</head>
<body>
<%= yield %>
</body>
</html>
Since we added Bootstrap, we’re going to leverage it! You can paste the following code
inside the <body> tag.
This is some pretty basic Bootstrap stuff. We added a navbar that will contain the links of
our menu and we surrounded the yield call with the container class provided by
Bootstrap. The jumbotron is just here to add a bit of style and break with the super white
design.
One important point to note however is the use of samurai.root_path. Why don’t we just
put root_path? Well it’s due to Devise. Without samurai in front of every route, the app
will crash when we try to access the views from Devise in the future. There is some kind
of path conflict when using Devise inside a namespaced engine.
Is it a good practice to prefix all our paths with samurai to avoid any problem in the
future.
If you check your browser, you should see the following. Figure 4.1
Last step before we create our first model, we’re going to give a title to the blank
dashboard. Just add the following code to the dashboard index view.
Figure 4.2: Now, it looks pretty good. If you have poor taste!
Our modular application is still pretty basic. It’s time to allow people to create an account
and login! We’re not going to reinvent the wheel, so let’s use Devise, the great
Authentication gem (which is actually built as a Rails engine).
Step 1: Add the Devise gem in the Core module gemspec file.
# SamuraiCRM/engines/core/samurai_core.gemspec
# ...
s.add_dependency 'devise', '~> 3.4.1'
# SamuraiCRM/engines/core/lib/samurai/core.rb
require 'sass-rails'
require 'bootstrap-sass'
require 'autoprefixer-rails'
require 'devise'
module Samurai
module Core
end
end
And run bundle install from the parent application. Don’t forget to restart your
webserver too!
Devise comes with a handy generator that we’re going to use to create the required files.
Run the following command from inside the Core engine.
Basically, we’re telling Devise that it’s going to be running inside an engine and that it
should use these values instead of its default ones.
Step 4: Add Flash messages
We’re going to add flash messages so Devise can tell a user when something is wrong. To
do that and keep it clean, we’re going to use a helper method. But first, we need to
reorganize the helpers folder in the Core engine.
core/app/helpers/
samurai/
application_helper.rb
# core/app/helpers/samurai/application_helper.rb
module Samurai
module ApplicationHelper
end
end
Since this is a sample app, I’m gonna put my helper methods inside the
ApplicationHelper. In a real application, you should split your helpers in different files
depending on the kind of ‘help’ they provide.
# core/app/helpers/samurai/application_helper.rb
module Samurai
module ApplicationHelper
FLASH_CLASSES = {
notice: "alert alert-info",
success: "alert alert-success",
alert: "alert alert-danger",
error: "alert alert-danger"
}
def flash_class(level)
FLASH_CLASSES[level]
end
end
end
Add the following code at the top of the container in the layout file inside the Core engine,
right before the jumbotron!
Wait, our migration is inside the Core engine. How are we going to migrate it from the
parent app? We don’t want to run the migrations manually from each engine one after the
other, that would be annoying. Well, we just have to tell the parent application to look for
migrations inside the engines!
To do that, we need to add an initializer to the Core engine. Open the engine file located
at core/lib/samurai/core/engine.rb and add the following inside the class Engine.
Remember when I said we would have some tweaks to do in order to have Devise
working inside another engine? Well, here’s one! When we ran the Devise generator, it
added a new line to the routes.rb file. The line looks like this:
devise_for :users, :class_name => "Samurai::User"
But it’s not gonna work like this. We need to tell Devise that we’re running it inside an
engine!
devise_for :users, class_name: "Samurai::User", module: :devise
The class_name specify the model we want to use. module is here to tell Devise that it’s
not running inside a regular Rails application.
Step 8: Migrate
To run your first modular migration, go into the parent application folder and run:
rake db:migrate
The table for the User model should be created without any problem! Restart your server
before continuing.
Devise’s default views are nice, but we want to adapt them to our style. Bootstrap style.
So we’re going to generate the view files and edit them. Run the following command
from inside the Core engine:
rails g devise:views
Before we can access these views, we need to actually check if we have a current_user.
If not, we want the unsigned user to create an account or login. To do that, we just have to
add a before_filter to the ApplicationController located inside the Core.
# core/app/controllers/samurai/application_controller.rb
module Samurai
module Core
class ApplicationController < ActionController::Base
before_action :authenticate_user!
end
end
end
Now if you try to access the app at http://localhost:3000, you should be redirected to
the sign in view. But it does not look good at all! Figure 4.3
Figure 4.3
There is nothing interesting enough to point out in this file, being mostly a design change.
Let’s check the new look. It looks much better now! Figure 4.4
Figure 4.4
Now let’s check the registration. Simply click on Sign Up. Figure 4.5
Figure 4.5
<div class="form-group">
<%= f.label :email, class: "col-sm-2 control-label" %>
<div class="col-sm-6">
<%= f.email_field :email, class: "form-control" %>
</div>
</div>
<div class="form-group">
<%= f.label :password, class: "col-sm-2 control-label" %>
<div class="col-sm-6">
<%= f.password_field :password, autocomplete: "off",
class: "form-control" %>
</div>
</div>
<div class="form-group">
<%= f.label :password_confirmation, class: "col-sm-2 control-label" %>
<div class="col-sm-6">
<%= f.password_field :password_confirmation, autocomplete: "off",
class: "form-control" %>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-6">
<%= f.submit "Sign up", class: "btn btn-primary" %>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-6">
<%= render "devise/shared/links" %>
</div>
</div>
<% end %>
Figure 4.6
We’re going to skip the forgot password and a few other Devise views because, well, it’s
not really interesting. Let’s play with authentication instead!
Moment of truth. Let’s try to create a user and sign it. If everything goes correctly, you
should end up on the dashboard we created earlier.
That would be nice if users could edit their information… We’re going to add it then. It’s
also the perfect time to add a few things to our navigation bar which is still quite empty.
Devise gives us the current_user method and we’re going to use to show the nav bar
only to people who logged in.
<!-- SamuraiCRM/engines/core/app/views/layouts/samurai/application.html.erb -->
<!-- ... -->
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<%= link_to 'SamuraiCRM', samurai.root_path, class: 'navbar-brand' %>
</div>
<%- if current_user %>
<div class="navbar-collapse collapse" id="navbar">
<ul class="nav navbar-nav">
<li>
<%= link_to 'Home', samurai.root_path %>
</li>
<li>
<%= link_to 'My Account', samurai.edit_user_registration_path %>
</li>
<li>
<%= link_to 'Logout', samurai.destroy_user_session_path,
method: :delete %>
</li>
</ul>
</div>
<% end %>
</div>
</nav>
<!-- ... -->
Figure 4.7
Neat!
<div class="form-group">
<%= f.label :email, class: 'col-sm-2 control-label' %>
<div class="col-sm-6">
<%= f.email_field :email, class: 'form-control' %>
</div>
</div>
<div class="form-group">
<%= f.label :password, class: 'col-sm-2 control-label' %>
<i>(leave blank if you don't want to change it)</i>
<div class="col-sm-6">
<%= f.password_field :password, autocomplete: "off",
class: 'form-control' %>
</div>
</div>
<div class="form-group">
<%= f.label :password_confirmation, class: 'col-sm-2 control-label' %>
<div class="col-sm-6">
<%= f.password_field :password_confirmation, autocomplete: "off",
class: 'form-control' %>
</div>
</div>
<div class="form-group">
<%= f.label :current_password, class: 'col-sm-2 control-label' %>
<i>(we need your current password to confirm your changes)</i>
<div class="col-sm-6">
<%= f.password_field :current_password, autocomplete: "off",
class: 'form-control' %>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-6">
<%= f.submit "Update", class: "btn btn-primary" %>
</div>
</div>
<% end %>
<h2>Cancel my account</h2>
<hr>
<p>Unhappy?
<%= button_to "Cancel my account", registration_path(resource_name),
data: { confirm: "Are you sure?" },
method: :delete,
class: 'btn btn-danger' %></p>
<hr>
<%= link_to "Back", :back, class: 'btn btn-default' %>
Knowing on which menu we are currently would be nice, so let’s add a highlight. The
best way to do that, in my opinion, is to use a Helper method. So let’s add an active
method that’s going to check if the current page matches a specific link.
# SamuraiCRM/engines/core/app/helpers/samurai/application_helper.rb
def active(path)
current_page?(path) ? 'active' : ''
end
If the passed path equals the current page, we’ll return the string active and use it as the
link class. You can use it in the nav bar like this:
<!-- SamuraiCRM/engines/core/app/views/layouts/samurai/application.html.erb -->
<!-- ... -->
<ul class="nav navbar-nav">
<li class="<%= active(samurai.root_path) %>">
<%= link_to 'Home', samurai.root_path %>
</li>
<li class="<%= active(samurai.edit_user_registration_path) %>">
<%= link_to 'My Account', samurai.edit_user_registration_path %>
</li>
<li>
<%= link_to 'Logout', samurai.destroy_user_session_path, method: :delete %>
</li>
</ul>
<!-- ... -->
Super easy, try it now! The menu you’re currently on should be highlighted. Figure 4.10
Figure 4.10
We will write some automated tests very soon, but for now, just ensure that everything is
working. Login, logout, navigation, everything! If it looks fine, let’s continue and start
working on the admin panel!
In our CRM, we want to give the rights to administrate the CRM to some users. The
admin panel will be a new screen available only to administrators where the users (among
other things) can be managed. Later, we will extend it to list all contacts, opportunities
and see what everyone’s doing. To be able to create administrators, we just need to add an
admin flag to users.
Navigate inside the Core engine and run the migration generation command.
rake db:migrate
Now, we need an admin! Easiest way to do that is by running the rails console and
updating a user that you previously created. Run the following from the parent app.
rails console
To keep the administration logic encapsulated, we’re going to add an admin namespace.
First, create a folder named admin in core/app/controllers/samurai/. Inside this new
folder, create a controller named admin_controller.rb and add the following content
inside. You can also run the command below to generate the folder and the file.
mkdir app/controllers/samurai/admin && \
touch app/controllers/samurai/admin/admin_controller.rb
# SamuraiCRM/engines/core/app/controllers/samurai/admin/admin_controller.rb
module Samurai
module Admin
class AdminController < ApplicationController
def index
end
end
end
end
# SamuraiCRM/engines/core/config/routes.rb
Samurai::Core::Engine.routes.draw do
devise_for :users, :class_name => "Samurai::User", module: :devise
namespace :admin do
get '/' => 'admin#index'
end
root to: "dashboard#index"
end
Now that the route is defined, we can add a link by using the path generated by Rails.
Let’s add the link to the admin panel between ‘My Account’ and ‘Logout’.
<li>
<%= link_to 'Logout', samurai.destroy_user_session_path, method: :delete %>
</li>
<!-- ... -->
Don’t forget to use samurai.admin_path, else we will get a crash when accessing one of
Devise views.
If you click on the link we just created, you will get an exception saying that we don’t
have a view yet. Let’s create the view at the following location. Note that you need to
create two folders named ‘admin’ before creating the actual file. The first folder is for the
namespace Admin and the second one for our controller named AdminController.
core/
app/
views/
samurai/
admin/
admin/
index.html.erb
Here’s the command to create these folders. Run it from the Core engine.
mkdir -p app/views/samurai/admin/admin && \
touch app/views/samurai/admin/admin/index.html.erb
And paste the following code in index.html.erb. Then read it to understand what we’re
doing!
<div class='clearfix'></div>
<hr>
<div class="row">
<div class="col-md-6">
<!--Will show tables with the last changes for each model -->
</div>
</div>
You should see the following when accessing /admin. Figure 4.11
Figure 4.11
We just created a new menu for everything related to the administration of the CRM. Let’s
see what we can add to our empty admin panel!
The only thing we can show for now inside the admin dashboard is the list of all the users.
To do that, we’re going to create a new controller.
# SamuraiCRM/engines/core/app/controllers/samurai/admin/users_controller.rb
module Samurai
module Admin
class UsersController < AdminController
def index
@users = Samurai::User.ordered
end
end
end
end
Once again, a very simple controller. But let’s not forget that we are building a super
simple sample application!
Now we need to add one line to the routes file to access the list of users. We just need the
index action since we just want to list the users. If you feel the need to, you can add the
rest of the RESTful actions to handle Users creation, update and so on.
# SamuraiCRM/engines/core/config/routes.rb
# ...
namespace :admin do
get '/' => 'admin#index'
resources :users, only: :index
end
# …
We can now add a link to the users listing view. But since we’re going to re-use the admin
menu in all the admin views, it’s time to extract it to a partial.
Before we do that, there is a neat little trick to avoid having to write long path for our
partials due to our namespacing policy. The trick is simply to add the path containing our
namespaces to the list of folders looked up by Rails when it is looking for partials. We
need to add one line to the engine.rb in the Core module. Restart your server after
adding this code.
# SamuraiCRM/engines/core/lib/samurai/core/engine.rb
module Samurai
module Core
class Engine < ::Rails::Engine
isolate_namespace Samurai
# ...
</li>
</ul>
<div class='clearfix'></div>
In this view, we find the dashboard link that we created earlier as well as a new one
linking to the users list. We also used one of Bootstrap element to add a counter next to
the name with the total number of users.
Let’s replace the old admin menu with this partial inside the admin dashboard view.
<div class="row">
<div class="col-md-6">
<!--Will show tables with the last changes for each model -->
</div>
</div>
Before adding the users table, we’re going to add a scope to the User model to have them
sorted by creation date.
# SamuraiCRM/engines/core/app/models/samurai/user.rb
module Samurai
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
Finally, we can add the users listing! We need to create a new folder named users in
core/app/views/samurai/admin/. Add the index file in this new folder.
<table class="table">
<thead>
<th>ID</th>
<th>Email</th>
<th>Signed Up</th>
<th>Admin?</th>
</thead>
<tbody>
<%- @users.ordered.each do |user| %>
<tr>
<td><%= user.id %></td>
<td><%= user.email %></td>
<td><%= user.created_at.strftime("%d %b. %Y") %></td>
<td><%= user.admin? %></td>
</tr>
<% end %>
</tbody>
</table>
</div>
If you access the user admin screen, you should now have a beautiful table listing the
users in your application! Figure 4.12
Figure 4.12
<div class="row">
<div class="col-md-6">
<div class="panel panel-primary">
<div class="panel-heading">
Last 3 Users
</div>
<table class="table">
<thead>
<th>ID</th>
<th>Email</th>
<th>Signed Up</th>
</thead>
<tbody>
<%- Samurai::User.ordered.limit(3).each do |user| %>
<tr>
<td><%= user.id %></td>
<td><%= user.email %></td>
<td><%= user.created_at.strftime("%d %b. %Y") %></td>
</tr>
<% end %>
</tbody>
</table>
<div class="panel-body text-center">
<%= link_to '...', samurai.admin_users_path %>
</div>
</div>
</div>
</div>
Figure 4.13
In the next part, we’re going to add authorization with the CanCan gem.
Authorization is here to ensure that users don’t access some pages of the application that
they are not supposed to see. For example, a regular user is not supposed to see the admin
panel. CanCan, created by Ryan Bates, is an easy to use gem that let you do just that!
The first step is to actually add the gem to the Core gemspec.
# SamuraiCRM/engines/core/samurai_core.gemspec
# ...
s.add_dependency 'devise', '~> 3.4.1'
s.add_dependency 'cancan', '~> 1.6.10'
# ...
module Samurai
module Core
end
end
And bundle install from the parent application. Don’t forget to restart your app!
What a user can or cannot do is defined inside the ability.rb file. Let’s generate this file
right now by using CanCan generator inside the Core module.
rails g cancan:ability
mv app/models/ability.rb app/models/samurai/
# SamuraiCRM/engines/core/app/models/samurai/ability.rb
module Samurai
class Ability
include CanCan::Ability
def initialize(user)
end
end
end
CanCan uses a method called current_ability to get an ability object. Since we are
using custom namespaces, we need to override this method. Do the following in the
ApplicationController inside the Core module.
# SamuraiCRM/engines/core/app/controllers/samurai/application_controller.rb
module Samurai
class ApplicationController < ActionController::Base
before_action :authenticate_user!
def current_ability
@current_ability ||= Samurai::Ability.new(current_user)
end
end
end
# core/app/controllers/samurai/application_controller.rb
module Samurai
class ApplicationController < ActionController::Base
before_action :authenticate_user!
def current_ability
@current_ability ||= Samurai::Ability.new(current_user)
end
end
end
Now we can start adding authorization checks to our controllers. First, the
DashboardController:
# SamuraiCRM/engines/core/app/controllers/samurai/dashboard_controller.rb
module Samurai
class DashboardController < ApplicationController
authorize_resource class: false
def index
end
end
end
See the authorize_resource? That’s the method we use to confirm the access for a user.
We have to pass it class: false since this controller is not linked to any model.
Then, we need to add the same in the AdminController. Since all the other controllers in
the admin namespace inherit from it, we just need to put it one time.
# SamuraiCRM/engines/core/app/controllers/samurai/admin/admin_controller.rb
module Samurai
module Admin
class AdminController < ApplicationController
authorize_resource :class => false
def index
end
end
end
end
Finally, we have to define the abilities! They are defined in ability.rb as you probably
know.
# SamuraiCRM/engines/core/app/models/samurai/ability.rb
module Samurai
class Ability
include CanCan::Ability
def initialize(user)
if user.admin?
can :manage, :all
else
can :read, :dashboard
end
end
end
end
An admin can do anything, but a user can only access the dashboard, for now at least.
Don’t forget to restart your server before you play with it. We will give the users more
permissions when we create the next modules. Before arriving at the end of this (long)
chapter, let’s talk about testing!
Testing should be a big part of your coding workflow. I decided to add tests after the code
in this chapter to let you focus on the engines and how to create your first one. The truth
is that testing an engine is not that different from testing a regular Ruby on Rails
application. There are entire books available focusing only on testing and they offer better
explanations that I could in just a few pages.
However, there are a few little tricks to know to make the tests run correctly inside your
engine. That’s the important part when testing a modular application and that’s what I’m
going to show you right now.
Everyone has their favorite testing environment. Some people love factories, other prefer
fixtures. I’m going to show you ONE way to test by using the tools I’m used to. You
should adapt the following to your liking. Let’s add the gems that we need to the Core
module gemspec.
# SamuraiCRM/engines/core/samurai_core.gemspec
# ...
Gem::Specification.new do |s|
# ...
s.add_development_dependency "sqlite3"
s.add_development_dependency "rspec-rails",
s.add_development_dependency "factory_girl_rails"
s.add_development_dependency "faker"
s.add_development_dependency "database_cleaner"
end
Since we don’t need those gems in production, we can add them as development
dependency. As you can see, we’re using Rspec, Factory Girl, Faker and Database
Cleaner.
Don’t forget to run bundle install from the Core engine this time. If you have the test
folder generated by Rails in the Core engine, you can delete it. In the next steps, we will
create the spec folder with Rspec.
Let’s generate the test database. Run the following command from the parent application
folder.
Navigate to the Core engine and run the command to generate the Rspec files.
rails g rspec:install
Step 4: spec_helper.rb
Since we use Rspec inside an engine, we need to tweak the file a little bit.
Here’s the full spec_helper with comments about the various changes.
Here’s what we did:
Requiring the other testing tools we will be using: Factory Girl and DatabaseCleaner.
Change the location of the support files
Add DatabaseCleaner configuration
Load the Core path helpers
# SamuraiCRM/engines/core/spec/spec_helper.rb
ENV["RAILS_ENV"] ||= 'test'
# Requires supporting ruby files with custom matchers and macros, etc,
# in spec/support/ and its subdirectories.
Dir[File.join(ENGINE_RAILS_ROOT, "spec/support/**/*.rb")].each {|f| require f }
RSpec.configure do |config|
config.before(:each) do
DatabaseCleaner.start
end
config.after(:each) do
DatabaseCleaner.clean
end
end
Step 5: .rspec
I like to add the following to the .rspec file to get a better output when running the specs.
--color
--require spec_helper
--format documentation
We can finally try to run the tests! From the Core engine folder, run rspec and you should
get: Figure 4.14
Figure 4.14
Great, it’s working! Let’s add some factories and some simple tests now!
# core/spec/factories/samurai/user.rb
require 'faker'
module Samurai
FactoryGirl.define do
factory :user, class: 'Samurai/User' do |f|
f.email { Faker::Internet.email }
password 'password'
password_confirmation 'password'
admin false
end
It’s a super simple factory as you can see! Note the use of class: ’Samurai/User’ to
link to the User model.
With our new factory, we can now create some specs. Create the folders
core/spec/models/samurai/ and create a file named user_spec.rb inside.
There is nothing complicated in the User model for now, so we’re going to add some
pretty basic tests.
# spec/models/samurai/user_spec.rb
require 'spec_helper'
module Samurai
describe User do
it "has a valid factory" do
expect(FactoryGirl.build(:user)).to be_valid
end
Run rspec from the Core engine folder and you should only get green lights! Now, you
know how to tests models inside a Ruby on Rails engine. Figure 4.15
Figure 4.15
Before we add the tests for the dashboard controller, we need to add two support files that
will simplify our life. The first file is the configuration for Devise. Create the file
core/spec/support/devise.rb and paste the following:
# core/spec/support/devise.rb
require 'devise'
RSpec.configure do |config|
config.include Devise::TestHelpers, :type => :controller
config.extend ControllerMacros, :type => :controller
config.infer_spec_type_from_file_location!
end
# core/spec/support/controller_macros.rb
module ControllerMacros
def login_admin
before(:each) do
@request.env["devise.mapping"] = Devise.mappings[:admin]
sign_in FactoryGirl.create(:admin)
end
end
def login_user
before(:each) do
@request.env["devise.mapping"] = Devise.mappings[:user]
user = FactoryGirl.create(:user)
sign_in user
end
end
def set_engine_routes
before(:each) do
@routes = Samurai::Core::Engine.routes
end
end
end
Now we can use login_admin and login_user in our controller specs to get a logged in
user. We can also call set_engine_routes to use the routes defined in the Core. If you
don’t call that method, you will get error when running rspec.
Finally, let’s add some specs for DashboardController. Create the folders
core/spec/controllers/samurai/ and add a file named
dashboard_controller_spec.rb.
# spec/controllers/samurai/dashboard_controller_spec.rb
require 'spec_helper'
module Samurai
describe DashboardController do
set_engine_routes
end
end
context 'user' do
login_user
it 'has a current_user' do
expect(subject.current_user).to_not be_nil
end
end
end
context 'admin' do
login_admin
it 'has a current_user' do
expect(subject.current_user).to_not be_nil
end
The tests are self-explanatory. You should read them! You can run them with rspec from
the Core engine folder. Figure 4.16
Figure 4.16
4.3 Round Up
We finally reached the end of this chapter. Let’s go over everything we’ve learned!
To do so, we first generated an empty Ruby on Rails application which we called the
parent application. Not much will be added to this app throughout the book since we will
use modules to add new features.
Then, we actually generated the Core module. This module is the foundation of our
application and future modules will be built on top of it. Inside this module, we set in
place the authentication and authorization. We also created the views that will be
extended by other modules in the next chapters.
We also learned how to setup the testing tools inside a Ruby on Rails engine.
In the next chapter, we will create the second module of our modular application. We will
add a new feature to our CRM: the creation and management of contacts. We will also
learn how to extend the functionalities of the Core module inside another module.
Chapter 5
SamuraiCRM: The Contacts module
In the previous chapter, we built our first engine. I hope you learned a few things on the
way. Things are about to get more interesting in this chapter. We’re going to build the
Contact engine on top of the Core by extending its classes and views.
We want to be able to easily access the list of contacts from the menu.
Users and Contacts relationship
Linking User with Contact will give us the option to show only the relevant contacts
for each user.
Contact overview on the dashboard
The dashboard is still quite empty. We’re going to extend it and add a list of the last
contacts.
5.2 Build it
To avoid having name conflicts with an engine named Contact and the model inside also
named Contact, we need to give a more appropriate name to this engine. In this case,
we’re simply going to pluralize it and call it Contacts.
In the previous chapter, we had to do a lot of folder rearranging which was super boring.
Actually, there is a better way to generate a modular engine the way it should look. But
for the first chapter, I wanted you to understand deeply how and why we organize the
engine like this so we went through all the annoying steps. Now, we’re about to generate a
new engine, a feature one (that gets plugged on a Core type engine).
Add the gems modular_engine and deface to the Parent Gemfile and run bundle
install.
# SamuraiCRM/Gemfile
# ... gems…
gem 'modular_engine'
gem 'deface' # We will use it soon
We have to edit a few values in the gemspec (homepage, summary, description) and add
the Core engine as a dependency.
# SamuraiCRM/engines/contacts/samurai_contacts.gemspec
$:.push File.expand_path("../lib", __FILE__)
We are working with local gems and the method s.add_dependency won’t let us specify a
local path. To load the gems correctly, we need to add samurai_core in the Contacts
Gemfile with the local path.
# SamuraiCRM/engines/contacts/Gemfile
# ... other stuff…
gem 'samurai_core', path: '../core'
Since we are building this engine on top of the Core and we want to make it easy to work
with a set of engines, all our feature engines will extend the Core routes instead of
implementing their own. How do we do that? Well, it’s very simple. Just open the
Contacts routes file and change Contacts to Core.
# SamuraiCRM/engines/contacts/config/routes.rb
Samurai::Core::Engine.routes.draw do
end
# SamuraiCRM/Gemfile
# ... gems…
gem 'samurai_core', path: 'engines/core'
gem 'samurai_contacts', path: 'engines/contacts'
Now you can run a quick bundle install from the parent app, restart your server and
everything should work! See, that wasn’t that hard thanks to the modular_engine gem.
Let’s start adding some content to the Contacts engine.
The first step to build the Contacts engine is to create the model. We’ll then work on the
controller.
Step 5: Migrate!
Run rake db:migrate from the parent app folder.
Once again, we need to move the controller and its views at the right place. You can run
the following commands to get the desired result.
mv app/controllers/samurai/contacts_controller.rb \
app/controllers/samurai/contacts/contacts_controller.rb
# SamuraiCRM/engines/contacts/app/controllers/samurai/contacts/application_controller
module Samurai
module Contacts
class ApplicationController < Samurai::ApplicationController
end
end
end
We have a few things to change in the controller generated by Rails. First, we need to
prefix all the path helpers with samurails. Then, we need to update the contact_params
method to include the parameters we want to keep.
Here’s the entire controller, including comments about what we changed, so you can just
copy/paste it and read it.
# contacts/app/controllers/samurai/contacts/contacts_controller.rb
module Samurai
module Contacts
class ContactsController < ApplicationController
before_action :set_contact, only: [:show, :edit, :update, :destroy]
def index
@contacts = Contact.all
end
def show
end
def new
@contact = Contact.new
end
def edit
end
def create
@contact = Contact.new(contact_params)
@contact.user = current_user
if @contact.save
# Add samurai to access the correct path
redirect_to [samurai, @contact],
notice: 'Contact was successfully created.'
else
render :new
end
end
def update
if @contact.update(contact_params)
# Add samurai to access the correct path
redirect_to [samurai, @contact],
notice: 'Contact was successfully updated.'
else
render :edit
end
end
def destroy
@contact.destroy
# Add samurai to access the correct path
redirect_to samurai.contacts_url,
notice: 'Contact was successfully destroyed.'
end
private
def set_contact
@contact = Contact.find(params[:id])
end
def contact_params
# Add the parameters we allow
params.require(:contact).permit(:first_name, :last_name, :company,
:email, :phone, :user_id)
end
end
end
end
Step 4: Update the views.
I prepared some views so you don’t have to think about it. They are basic views with
Bootstrap forms and elements. The only thing that you should remember in those views
in the use of samurai.my_path instead of just my_path.
index.html.erb
<h2>Listing Contacts</h2>
<hr>
<div class="panel panel-primary">
<div class="panel-heading">
My Contacts
</div>
<table class="table">
<thead>
<th>ID</th>
<th>First Name</th>
<th>Last Name</th>
<th>Company</th>
<th>Email</th>
<th>Phone</th>
<th></th>
</thead>
<tbody>
<% @contacts.each do |contact| %>
<tr>
<td><%= contact.id %></td>
<td><%= contact.first_name %></td>
<td><%= contact.last_name %></td>
<td><%= contact.company %></td>
<td><%= contact.email %></td>
<td><%= contact.phone %></td>
<td>
<%= link_to 'Show', [samurai, contact],
class: 'btn btn-primary' %>
<%= link_to 'Edit', samurai.edit_contact_path(contact),
class: 'btn btn-primary' %>
<%= link_to 'Destroy', [samurai, contact],
class: 'btn btn-primary' , method: :delete,
data: { confirm: 'Are you sure?' } %>
</td>
</tr>
<% end %>
</tbody>
</table>
</div>
<br>
form.html.erb
new.html.erb
<div class="form-group">
<div>
<%= f.submit "Create Contact", class: "btn btn-primary" %>
<%= link_to 'Back', samurai.contacts_path, class: 'btn btn-default' %>
</div>
</div>
show.html.erb
<div class="row">
<div class="col-md-8">
<strong>First Name:</strong>
<%= @contact.first_name %>
<br/>
<strong>Last Name:</strong>
<%= @contact.last_name %>
<br/>
<strong>Company:</strong>
<%= @contact.company %>
<br/>
<strong>Email:</strong>
<%= @contact.email %>
<br/>
<strong>Phone:</strong>
<%= @contact.phone %>
</div>
</div>
<hr>
<%= link_to 'Edit', samurai.edit_contact_path(@contact), class: "btn btn-primary" %>
<%= link_to 'Back', samurai.contacts_path, class: 'btn btn-default' %>
edit.html.erb
If we want to access the contacts somehow, we need to define some routes for it.
# SamuraiCRM/engines/contacts/config/routes.rb
Samurai::Core::Engine.routes.draw do
scope module: 'contacts' do
resources :contacts
end
end
Note that we are just extending the Core routes. That means that in any of our Samurai
engines, all the routes are easily accessible by using samurai.my_path.
Step 6: Try it
Let’s see how it looks like in the browser. Access http://localhost:3000/contacts and
you should see: Figure 5.1
Figure 5.1
Ow, not that great huh! Why don’t we see the beautiful layout that we created in the
Core? Simply because we have a layout in Contacts that overrides the Core version.
Simply delete the layouts folder in the Contacts engine or run the following command
from the Contacts folder.
rm -r app/views/layouts
Figure 5.2
Nice! But hey, having to type the url is annoying, we want a link in our menu!
Let’s fix that right now.
So how are we going to extend the views that we wrote in the Core engine from the
Contacts engine? We’re going to use a neat little gem called Deface. It was created by
Spree.
Thanks to this gem, we can define hooks in our views and easily extend them. It’s really
easy to extend views as you’re about to see.
Step 1: The Deface Gem
# SamuraiCRM/engines/contacts/samurai_contacts.gemspec
# ...
s.add_dependency "samurai_core"
s.add_dependency "deface", '~> 1.0.1'
# …
# SamuraiCRM/engines/contacts/lib/samurai/contacts.rb
require "deface"
module Samurai
module Contacts
end
end
Let’s run bundle install from the parent app to get Deface and restart the server to load
our new gem.
We’re going to add a hook inside the Core engine in the layout file.
Locate the following piece of code:
See data-samurai-hook=’main_nav’?
With this, we basically defined this list element as a hook. With Deface, we can now add
some code before or after it very easily. Note that it doesn’t have to be a data-
attribute. It can be literally anything: an ID, a class or any kind of selectable element.
To keep the code clean, I like to use a unique data-attribute with different values. It
makes it easy to find your hooks.
A Deface override is a small piece of code that will detect a hook and insert the code we
tell it to. There is actually another way to use Deface, using their custom DSL, but I
personally prefer the solution I’m going to show you. You can learn more about the other
technique on the GitHub page.
# SamuraiCRM/engines/contacts/overrides/add_contacts_link_to_nav.rb
Deface::Override.new(:virtual_path => "layouts/samurai/application",
:name => "add_contacts_link_to_nav",
:insert_after => "[data-samurai-hook='main_nav']",
:partial => "overrides/contacts_link",
:namespaced => true,
:original => 'f5fe48b6dc6986328e0b873b3ffa1b228dd52a7c')
virtual_path
The template / partial / layout where the override should take effect eg:
shared/_person, admin/posts/new this will apply to all controller actions that use
the specified template.
name
Unique name for override so it can be identified and modified later. This needs to be
unique within the same :virtual_path.
insert_after
Inserts after all elements that match the supplied selector. One of the many options
available like insert_before, insert_top and so on. Check the documentation for
more options!
partial
namespaced
original
String containing original markup that is being overridden. If supplied Deface will
log when the original markup changes, which helps highlight overrides that need
attention when upgrading versions of the source application. Only really warranted
for :replace overrides. NB: All whitespace is stripped before comparison.
This is just the options we’ll be using in this book. Don’t hesitate to check what else you
can do and adapt what I’m showing you to match your needs! Now, let’s see how it looks
like: Figure 5.3
Figure 5.3
Figure 5.4
Wow, isn’t that great? You can play with it, try removing the Contacts engine from the
Gemfile, run bundle install, restart the server and refresh the page. No contacts link
anymore! Now you know how to easily extend a view in another engine. Just to be sure,
we’ll do it a few more times in this chapter and in the next.
How about we extend some models now?
5.2.5 Part 5: Extending the Core models
Extending models is much easier than extending views. Thanks to Ruby open classes, we
can always add more methods to an existing class.
To keep it organized, we’ll extend models and controllers in files called decorators.
By using the modular_engine generator, you should have a decorators folder in your
engine app/ folder. In there, you will find two more folders: models and controllers.
We’re going to create one decorator for each class that we want to extend. But first, let’s
make sure our decorators will be loaded by Rails automatically.
To load the content of your decorators, we need to tell Rails to load them. Actually, with
the gem we used to generate the engine, the piece of code to do it should already be there,
but let’s take a look.
# ...
config.to_prepare do
Dir.glob(Engine.root.join("app", "decorators", "**", "*_decorator*.rb")) do |c|
Rails.configuration.cache_classes ? require(c) : load(c)
end
end
# ...
Now, let’s try it. Create a decorator for User and put the following code inside:
# SamuraiCRM/engines/contacts/app/decorators/models/user_decorator.rb
Samurai::User.class_eval do
has_many :contacts, class_name: Samurai::Contacts::Contact
end
And that’s it! You can put anything you’d like to add to the User model in the class_eval
block. Note however, that we need to give the namespaced name of the module we’re
linking else it will try to find the Contact model in the Samurai::User scope!
We should try it to be sure it’s working. Let’s fire up the rails console and type
Samurai::User.first.contacts (Obviously, you need a user but you should have one by
now).
You should get a CollectionProxy object showing up in your terminal. Figure 5.5
Figure 5.5
A few steps back, we added the possibility to show all the contacts to a user. But that’s not
the behavior we want. We want to show a user only his contacts.
To do this, we need to change the value we give to @contacts in the index action of the
ContactsController.
From:
def index
@contacts = Contact.all
end
To:
def index
@contacts = current_user.contacts
end
Thanks to the has_many relationship that we added, it’s working! Time to extend the
dashboard!
5.2.6 Part 6: Extending the Dashboard
So you’ve just learned how to extend anything in any way you might want. But to learn
something, you need to do, do, do and do again. That’s what we’re going to do now! The
dashboard is so white it’s hurting my eyes, I think it would be nice to see a table on this
page showing the last contacts a user created.
You know the deal already, let’s add a hook to the dashboard view so we can easily extend
it in the Contacts engine. We’re going to call this hook dashboard.
<div class="row">
</div>
A simple span is enough for the dashboard. We’re going to insert code right after it with
Deface.
Time to create a new override that will be responsible for adding a list of contacts on the
dashboard making SamuraiCRM much better.
# SamuraiCRM/engines/contacts/app/overrides/add_contacts_list_to_dashboard.rb
Deface::Override.new(:virtual_path => "samurai/dashboard/index",
:name => "add_contacts_list_to_dashboard",
:insert_after => "[data-samurai-hook='dashboard']",
:partial => "overrides/contacts_list",
:namespaced => true)
And finally, the list of contacts. This is a simple view with a table showing a user’s
contacts.
<!-- contacts/app/views/samurai/contacts/overrides/_contacts_list.html.erb -->
<div class="col-md-6">
<div class="panel panel-primary">
<div class="panel-heading">
Last 3 Contacts
</div>
<table class="table">
<thead>
<th>ID</th>
<th>Name</th>
<th>Email</th>
<th>Created On</th>
</thead>
<tbody>
<%- current_user.contacts.each do |contact| %>
<tr>
<td><%= contact.id %></td>
<td><%= contact.first_name %> <%= contact.first_name %></td>
<td><%= contact.email %></td>
<td><%= contact.created_at.strftime("%d %b. %Y") %></td>
</tr>
<% end %>
</tbody>
</table>
Did you see how we got the list of contacts? We used current_user.contacts directly in
the view. That would be better to put it in the controller and I’m gonna show you how to
do it in a minute. First let’s take a look.
Step 4: Admire!
Figure 5.6
Figure 5.6
Just like when we extended a model, we’re going to create a decorator. A controller
decorator to be precise. We cannot override the index because that would mean
overriding it again in the next chapter. A better option is to use a before_action to assign
the @contacts value.
# contacts/app/decorators/controllers/dashboard_controller_decorator.rb
Samurai::DashboardController.class_eval do
before_action :set_contacts, only: :index
private
def set_contacts
@contacts = current_user.contacts
end
end
Now we can change the override view that we created a few steps back and use @contacts
instead of current_user.contacts.
It should still be working! So now you know how to extend models, controllers and
views! Is there anything else we have to extend? The answer is yes!
First, we need to add a bit of content to the ability file located in the Core.
# SamuraiCRM/engines/core/app/models/samurai/ability.rb
module Samurai
class Ability
include CanCan::Ability
class_attribute :abilities
self.abilities = Set.new
def initialize(user)
Rails.logger.info self.abilities.inspect
if user.admin?
can :manage, :all
else
can :read, :dashboard
end
Then, we added two class methods. These classes give us access to the abilities attribute
and allow us to add or remove something from it. But what?
def self.register_ability(ability)
self.abilities.add(ability)
end
def self.remove_ability(ability)
self.abilities.delete(ability)
end
Ability.abilities.each do |klass|
ability = klass.send(:new, user)
@rules = rules + ability.send(:rules)
end
This code loop through the content of the abilities attribute and instantiate each element by
passing the user object. Then, we get the rules, which is a CanCan method and contains all
the permissions, from this new instance and add them to the rules of the current ability
file.
Thanks to this code, we can simply create any number of Ability classes, define our
permissions inside and add it to the abilities attribute of the original Ability file. If it
sounds abstract, keep reading, we’re going to do it right now!
Since the original Ability file is stored in the models folder, we’re going to create our
new Ability in the decorator folder as a model decorator.
# SamuraiCRM/engines/contacts/app/decorators/models/ability_decorator.rb
require 'cancan'
module Samurai
module Contacts
class AbilityDecorator
include CanCan::Ability
def initialize(user)
unless user.admin?
can :manage, Samurai::Contacts::Contact, user_id: user.id
end
end
end
end
end
See, we simply defined a new Ability class, called it AbilityDecorator and put some
permissions. The magic happens thanks to this line:
Samurai::Ability.register_ability(Samurai::Contacts::AbilityDecorator)
We’re calling the register_ability defined earlier to add this class to the abilities
attribute.
Step 3: Add authorize_resource to ContactsController
# SamuraiCRM/engines/contacts/controllers/samurai/contacts/contacts_controller.rb
module Samurai
module Contacts
class ContactsController < ApplicationController
before_action :set_contact, only: [:show, :edit, :update, :destroy]
authorize_resource class: Samurai::Contacts::Contact
# ...
Step 4: Test
Restart your server and try to access a contact that don’t belong to your current user.
As I said earlier, this mechanism can be used for various purposes. I once used it to
create an entity duplicator. The idea was to duplicate a record and all the linked
records. So you would have Entity1Duplicator, Enity2Duplicator and so on. Each
duplicator is a simple Ruby class that deals with duplicating the associated model. The
problem was that some of the models I wanted to duplicate were in other engines. By
using this technique, I was able to load a list of all the duplicators and call them one by
one.
I don’t know if you’ve noticed but we didn’t touch the admin panel in this chapter. But we
have some work to do on it! There are three things that we should add.
First, we need an admin contacts controller just like the one we created for users.
The complete list of contacts should be accessible for admins only at
/admin/contacts.
The second thing we want is a link to the list of contacts we just created. We need to
add this link to the admin navigation menu.
Finally, we should show the last 3 contacts created in the admin dashboard just like
what we did for the last 3 users.
The good news is that I’m not going to show you how to do it. I want you to try it by
yourself by using what you’ve learned in this chapter. I’m going to give you the steps to
follow but you’ll have to write the code yourself. Here are also a few screenshots to help
you get started.
Step 1: Create an admin controller for contacts, an index view and admin routes
Good luck!
5.3 Round Up
We finally reached the end of this chapter. Let’s go over everything we’ve learned!
We built our first feature module of top of the Core that we created in the last chapter.
First, we created the Contact model, the associated controller and its views.
Then, we started the interesting part: extending the Core views to add some info about the
contacts. But we also had to extend the Core models to get what we wanted. Finally, to
remove some logic from the views, we also extended the Core controller.
We also learned that some classes cannot be extended in a simple way and we need to use
a different technique to get it to work. That’s what we did for the Ability class.
Anything can be extended, but you need to keep it clean and organized Deface is a great
gem to extend views by using overrides Adding the routes to the Core routes makes it
easier to use them in all our engines (by calling samurai.my_path)
In the next chapter, we’re going to build a new module: Tasks. With this module, a user
will be able to create tasks and link them to a contact if he wants to. We’ll reuse what we
learned in this chapter but also talk about how we can keep the Contacts module and the
Tasks module independent.
Chapter 6
SamuraiCRM: The Tasks module
In the previous chapter, we built our first feature engine. We saw how we could extend
the Core module by using Deface and class_eval. In this chapter, we’re going to keep
extending code from the Core module but also from the Contacts module. But we don’t
want Tasks to depend on Contacts so we’ll see how we can link them while keeping the
option to run them separately.
6.1 What’s the Tasks module
In this chapter we’re going to create the Tasks module which will add tasks to our CRM.
The first part of this chapter might be a bit boring because we will be redoing the same
steps that we did in the last chapter. You might want to just copy paste the code until the
fourth part.
In the fourth part, we’ll start extending the Contacts module and we’ll see how we can
simply check if an other engine is loaded or not. By doing this, we’ll keep the Tasks
engine independent which will allow us to run it even if we decide to remove the
Contacts module.
We’re going to create the basic views to create, edit and delete Tasks. A User will be
able to manage his tasks and assign them to other users and contacts.
Typing /tasks in the URL bar every time we want to see our tasks wouldn’t be very
user-friendly.
A user has_many contacts and tasks and a contact can have many tasks associated.
For example, a contact could have the following tasks linked to him ‘call him at
09:00 tomorrow’ or ‘send email to check if still interested’. You get the idea. So we
will show a list of tasks under each contacts.
6.2 Build it
Let’s generate the Tasks engine and update some of its files. Just follow the steps!
# SamuraiCRM/engines/tasks/samurai_tasks.gemspec
$:.push File.expand_path("../lib", __FILE__)
require "samurai/tasks/version"
Because we cannot specify a local path in the gemspec, we need to load the gem in the
engine gemfile.
# SamuraiCRM/engines/tasks/Gemfile
# ... other stuff…
gem 'samurai_core', path: '../core'
# SamuraiCRM/Gemfile
# ... gems…
gem 'samurai_core', path: 'engines/core'
gem 'samurai_contacts', path: 'engines/contacts'
gem 'samurai_tasks', path: 'engines/tasks'
Now, it’s time to create the Task model. Once again, it’s pretty much the same steps that
you found in the previous chapter, so let’s go through it quickly.
A task belongs to a user and to a potential contact. Run this command from the Tasks
engine.
mv app/models/samurai/task.rb app/models/samurai/tasks/task.rb
Step 5: Migrate!
In this part, we’re going to add a controller and the basic views for our tasks.
The ApplicationController:
# SamuraiCRM/engines/tasks/app/controllers/samurai/tasks/application_controller.rb
module Samurai
module Tasks
class ApplicationController < Samurai::ApplicationController
end
end
end
# SamuraiCRM/app/engines/tasks/app/controllers/samurai/tasks/tasks_controller.rb
module Samurai::Tasks
class TasksController < ApplicationController
before_action :set_task, only: [:show, :edit, :update, :destroy]
def index
@tasks = Task.all
end
def show
end
def new
@task = Task.new
end
def edit
end
def create
@task = Task.new(task_params)
if @task.save
redirect_to [samurai, @task], notice: 'Task was successfully created.'
else
render :new
end
end
def update
if @task.update(task_params)
redirect_to [samurai, @task], notice: 'Task was successfully updated.'
else
render :edit
end
end
def destroy
@task.destroy
redirect_to samurai.tasks_url, notice: 'Task was successfully destroyed.'
end
private
def set_task
@task = Task.find(params[:id])
end
def task_params
params.require(:task).permit(:title, :content, :user_id, :contact_id)
end
end
end
_form.html.erb
<div class="form-group">
<%= f.label :title, class: "control-label" %>
<%= f.text_field :title, class: "form-control" %>
</div>
<div class="form-group">
<%= f.label :content, class: "control-label" %>
<%= f.text_area :content, class: "form-control" %>
</div>
<div class="form-group">
<%= f.label :user_id, class: "control-label" %>
<%= f.select :user_id, Samurai::User.all.collect { |p| [ p.email, p.id ] },
{ selected: current_user.id }, class: "form-control" %>
</div>
</div>
</div>
new.html.erb
<div class="form-group">
<div>
<%= f.submit "Create Task", class: "btn btn-primary" %>
<%= link_to 'Back', samurai.tasks_path, class: 'btn btn-default' %>
</div>
</div>
<% end %>
show.html.erb
edit.html.erb
# SamuraiCRM/engines/tasks/config/routes.rb
Samurai::Core::Engine.routes.draw do
scope module: 'tasks' do
resources :tasks
end
end
Step 6: Remove the layouts folder.
rm -r app/views/layouts
Finally, we can see the result of our efforts! Restart your server and access /tasks.
Figure 6.1
We’re almost at the interesting part! Before we start extending the Contacts module, let’s
add a link in the navigation menu to access the list of tasks.
# SamuraiCRM/engines/tasks/app/overrides/add_tasks_link_to_nav.rb
Deface::Override.new(:virtual_path => "layouts/samurai/application",
:name => "add_tasks_link_to_nav",
:insert_after => "[data-samurai-hook='main_nav']",
:partial => "overrides/tasks_link",
:namespaced => true)
Figure 6.2
Figure 6.2
Now, we’d like to show a list of tasks on the Contact show view. To do that, we need to
link the Task and Contact models. But what if we decide later to remove the Contacts
module? Everything would break!
The good news is that we can easily prevent this. We just have to check if the Contacts
module is present or not. This is the best solution I could come up with to deal with this
kind of situations. I’m still looking for a better way to do it.
Basically, we’re going to create a method in the Core to check if a specific module is
defined. With this method, we can check if the engine containing the module Contacts is
present or not. Let’s create this method.
# SamuraiCRM/engines/core/lib/samurai/core.rb
require 'sass-rails'
require 'bootstrap-sass'
require 'autoprefixer-rails'
require 'devise'
require 'cancan'
module Samurai
module Core
def self.available?(engine_name)
Object.const_defined?("Samurai::#{engine_name.to_s.camelize}")
end
end
end
With the available? method, we can update the task form to add a contact field. Now,
we can link a task to a contact!
<!-- SamuraiCRM/app/engines/tasks/app/views/samurai/tasks/tasks/_form.html.erb →
<!-- ... -->
<%- if Samurai::Core.available?(:contacts) %>
<div class="form-group">
<%= f.label :contact_id, class: "control-label" %>
<%= f.select :contact_id,
Samurai::Contacts::Contact.all.collect { |p| [ p.email, p.id ] },
{ allow_blank: true }, class: "form-control" %>
</div>
<% end %>
</div>
</div>
It’s time to add a few relationships between our models. Note how we check if the
Contacts module is present or not before trying to extend the Contact model.
# SamuraiCRM/engines/tasks/app/decorators/models/contact_decorator.rb
if Samurai::Core.available?(:contacts)
Samurai::Contacts::Contact.class_eval do
has_many :tasks, class_name: Samurai::Tasks::Task
end
end
We can now add the other side of the relationships in the Task model. Once again, we
reuse the available? method before linking Task to Contact.
# SamuraiCRM/engines/tasks/app/models/samurai/tasks/task.rb
module Samurai::Tasks
class Task < ActiveRecord::Base
belongs_to :user
if Samurai::Core.available?(:contacts)
belongs_to :contact, class_name: 'Samurai::Contacts::Contact'
end
end
end
Restart your server and you will see the result of the last steps. We now have a contact
linked to a task.
Figure 6.3
Before we add an override, we need a hook in the show view for contacts. Add the
following line at the end of the file.
I’m sure you’re an expert with Deface by now and you can extend views even with your
eyes closed. Here’s the override to add a list of tasks to a contact.
# SamuraiCRM/engines/tasks/app/overrides/add_tasks_to_contact.rb
if Samurai::Core.available?(:contacts)
Deface::Override.new(:virtual_path => "samurai/contacts/contacts/show",
:name => "add_tasks_to_contact",
:insert_after => "[data-samurai-hook='contacts_show']",
:partial => "overrides/contact_tasks_list",
:namespaced => true)
end
Figure 6.5
Figure 6.7
Figure 6.7: Still working and no contacts, great!
Before we continue, let’s uncomment all our modules, re-run bundle install and restart
our server one more time.
Well that’s it! You know everything about extending and working with different engines.
Let’s wrap up this chapter by adding a list of tasks to the dashboard view.
We saw in the previous chapter how to extend the Ability file provided by CanCan. We
can super easily add a new decorator for the permissions related to tasks.
# SamuraiCRM/engines/tasks/app/decorators/models/ability_decorator.rb
require 'cancan'
module Samurai
module Tasks
class AbilityDecorator
include CanCan::Ability
def initialize(user)
unless user.admin?
can :manage, Samurai::Tasks::Task, user_id: user.id
end
end
end
end
end
(But you can try to access a task that does not belong to your current user.)
Ah! Your favorite part. The one when I can rest and you get to work. I really recommend
doing the following exercises to be sure you understand everything. It’s by doing that you
will learn. Plus, it’s the same thing that you did in the previous chapter. Yes, it’s a bit
repetitive, but that’s how you will get it and be able to work on your own engines.
Step 1: Create an admin controller for tasks, the index view and the routes
Step 5: Add override view with Tasks list for Admin Dashboard
Figure 6.9
Figure 6.10
Figure 6.10: The Tasks summary in the Admin Dashboard
6.3 Round Up
And one more chapter done! Let’s see what we learned.
In this chapter, we learned how to extend an engine without defining a dependency on it.
We also did more module extensions with Deface and class_eval.
First, we created the Task model, added a controller and the corresponding views. We also
extend the Core to add some logic related to Tasks to dashboard and to the navigation
menu.
Then we extended the Contacts module to show a list of tasks for each contact.
Finally, we added one more AbilityDecorator for the permissions related to tasks.
You can check if an engine is present by checking if the module is defined with
defined?.
Creating overrides get easier and faster.
Creating engines gets boring.
We’re not going to build more engines in this book, that was the last one. It’s getting
boring and you already know how to do it. In the next chapter, we’re going to see how to
work with your engines and how to package them as gem.
Chapter 7
From development to production
In the last chapters, you have built a complete modular Rails application.
Congratulations!
SamuraiCRM is only composed of three modules but I hope you learned a lot while
building them. Since we built a ‘web’ application, it should be available online, right?
There are a few steps before we can actually push our application to staging/production.
Before we do that, I also want to show you how to work with your modular application
and how to maintain it. That’s exactly what we’re going to see in this chapter.
The first thing we will do in this chapter is learn how to work with a growing set of
engines. Indeed, we will see how to deal with the source code and how to manage your
modules as gems. Finally, we will push SamuraiCRM to Heroku.
7.1 Packaging your engines
Until now, we’ve kept the source code of our modules inside the parent application. It was
fine because we were creating our first engines and we didn’t care about distribution yet.
But now, we want our modules to be easily reused and shared between different
applications. Let’s see how we can do it.
We said earlier than an engine is a ‘fat free’ Ruby on Rails application. But it also got a
gemspec. That means we can package it as a gem! By encapsulating the source code of
each of our module inside a gem, it becomes super easy to share them!
First, we would have to push all our packaged modules to a gem server, like Rubygems for
example. Then, all we would have to do is put them inside an application Gemfile and run
bundle install. That would be awesome! That also means we need to version our
engines. We need to give each module a version like 1.0.0, then 1.0.1 when we change
something and so on.
There are a lot of reasons for packaging your engines as gems. First of all, when building
a modular application, you actually don’t have that many choices. To manage your
module source code, either you package them as gems, or you use git subtree.
Honestly, git subtree is a bit of a pain to work with. I wouldn’t recommend it. We
actually tried it for a while but it was unproductive and boring. Using gems and simply
changing a version number is so much easier as we will see soon.
Here are the main reasons to encapsulate your engines as gems:
Gems can be pushed to a gems server and be easily loaded from anywhere.
Can be loaded in your application with Bundle.
Can be versioned.
7.1.3 Make your first module gem
Let’s package one of our module as an engine to see how easy it is. Navigate inside the
Tasks module folder and run the following command.
And it will generate you a new file named samurai_tasks-0.0.1.gem. That’s it, we got
ourselves a gem!
If you now want to make your gem public and available to everyone, you can just run gem
push samurai_tasks-0.0.1.gem. It’s probably not going to work if someone else pushed
a gem with the same name and version so you should try with your own modules!
The version is still 0.0.1 since we didn’t give it a proper version yet. We will do that in
the next part after we’ve talked about how you should version your engines.
7.2 Managing your engines
When you work with one or more modular applications, your coding workflow changes
quite a bit. It gives you some advantages but also comes with annoying inconvenient.
When working on a modular application, you will need two ‘versions’ of the same
application. We’ll do it for SamuraiCRM at the end of this chapter, but for now, let’s talk
theory. We’re going to use SamuraiCRM as an example.
Our current SamuraiCRM application is the development version. It contains the source
code of each engine and you will need to pull the source code of any new module in the
engines/engine_name folder. That’s the copy you’ll work with when you want to add
new features.
If I change something in the Core module, I will commit my changes and push the Core
module from this application. Once the code is pushed, we will be able to generate a gem.
With this new gem pushed somewhere, we’ll switch to the production application. If you
don’t have one yet, you just need to pull the code from the development version. Then,
you’ll have to delete the engines folder, update the Gemfile to use the gems instead of the
local folders and bundle install. With this, you got a clean production application that
you can push to a new repository.
Here are the mains points to remember about development vs production applications:
Development Application
Production Application
Git is an awesome tool to keep your source code versioned and work with other people. If
you want to work on a modular application and keep it smooth, you need to have your
engines source code versioned. Using Git (or any VCS) will allow you to easily work on
your modules with your colleagues and safely add new features by using branches!
But what’s the best approach? Should you just version the parent application with the
source code of the modules? That’s one solution, but in my humble opinion, not the best
one.
I recommend versioning your parent application of course, but without the modules
inside. If you use Git (git init .) inside each engine, they won’t be versioned with the
parent app. You will have to push the parent application and each module to their own
repository but it will give you a log for each module instead of a confusing log only for
the parent application.
Github
Github is a great platform to store your source code. As long as you’re doing open source
projects, everything will be free. If you need private repositories however, it might not be
the optimal solution. Pricing on GitHub is based on the number of repositories that you
have and with modular applications, you will need a lot of repositories: one for each
module and for each parent application. It can get pricey!
Bitbucket
Bitbucket offers a different pricing strategy: you pay for the number of users and not the
number of repositories. If you work alone, this is your best option because you won’t
have to pay anything! Indeed, it’s free for up to 5 users. That’s a great solution for
modular applications.
If you have access to any kind of server, you can configure your own git repositories. It
won’t cost you anything except for the server price. You can even install some web
applications to access Bitbucket/Github-like interfaces. If you’re looking for this kind of
thing, checkout Digital Ocean + Gitlab!
Branches are an awesome feature of Git which makes working on different features at the
same time much easier. Working on modules is not much different than working on a
regular Rails application.
You want to create a new branch for a module anytime you have to change something.
The code should only be merged into Master once it’s been tested and is ready to go live.
When you want to test a packaged module, you should add the branch name to the version
number:
MAJOR.MINOR.PATCH.BRANCH
1.0.0 will become 1.0.0.branch_name. This should mainly be used to test gems in a
staging environment.
Have you ever heard the saying ‘Eating your own dog food’? If you don’t know it, it
means using the tools you create to ensure their quality and capabilities.
Let’s say you’re working on a CMS, like WordPress. Obviously, you want to write it in
Ruby. One of the most interesting feature of WordPress is how extendable it is. You can
easily create a plugin by hooking into WordPress. Of course you want your CMS to be as
famous as WordPress, so having a plugin-ready application is a big plus. With everything
you’ve learned in this book, you now have two choices.
Either you create a regular Ruby on Rails application. When it’s done, you start to think
about how people could extend your application. Basically, you’re not eating your own
dog food because you won’t be using this extension system yourself. And everybody
loves dog food!
The other option is to build a modular application the way I showed you. You’re making
your application extendable for yourself so you can detect the flaws and fix them before
you let other people create plugins. You could create the Core as a super basic system and
create your own modules to add more features like comments, tags, and so on. You can
even make it so people can disable some feature super easily by not loading the engines!
Next time you’re building an application that has to be extendable, think about it,
modularity might be the way to go.
7.3 Publishing your engines
Now that your engines are packaged as gems, they can easily be loaded in your
application by using bundle. But if we want Bundle to find them, they need to be
available from somewhere online!
How do we do that?
To push your gems online, you have two solutions. It really depends on what kind of work
you are doing and if you are working on an open-source engine or on an engine that’s
supposed to stay private.
If you want to share your engine with the world, your life will be super easy. That’s what
the karma is all about right? If you are ready to share it, you can just use Rubygems.
That’s it.
Rubygems stores all gems for free and you can put yours up there too. Anyone will be
able to use it and see your code so if that’s not a problem for you, go for it!
Unfortunately, it’s not always possible. Even if you want to, you might not have the right
to share your code. The corporate world doesn’t like it when you share their secrets!
That’s understandable so we need another solution, but don’t expect it to be free (It’s fine,
the company will pay…).
Lucky us, if Rubygems can do it, why can’t we? Other people have thought about this
too. I guess that’s how Gemfury was born.
Gemfury
Gemfury gives you a gem server where you can store private and public gems. You
control if you want this gem to be public and this one to stay private. That’s up to you!
Unfortunately, Gemfury is not free. Their plans are interesting except if you plan on
pushing lots of gems.
So if Gemfury is too pricey for you, how about making our own? It will have a cost too.
But it’s going to be a small and flat fee. The only thing we need to create our own gem
server is a server. VPS, dedicated, it doesn’t matter. Any cheap provider like Digital
Ocean or Linode will do. The server will just store packaged codes and send them when
someone run the bundle install command. Nothing too intense. All we need is for it to be
accessible remotely.
Got a server? Good! But we still have to set up a gem server. The good news is that some
people have created nice gems that we can use to set up a gem server. I’m going to show
you right now how to build a private gem server with Gem in a box on a $5 Digital Ocean
Droplet. You can get a server and follow the steps but honestly, it’s not that hard so
reading should be enough to understand how it works.
You can use any server that you have access to. If you want to get your own Digital
Ocean Droplet, you can sign up here. This is an affiliate link and you will get a credit of
$10 if you use it. That means you can try building your private gem server for free! (I
will also get a commission if you become a regular user.)
Let’s get started!
7.4 Build a private home for your
engines
Thanks to this neat little gem, Geminabox, we can very easily create a simple Gem
Server. The following commands will be run from a Digital Ocean Droplet. I will use
RVM to install Ruby. I will put the gems web application in /var/www/gemserver and
store the actual gems in /var/thibault/gems/ because this is my personal server and
nobody else has access to it.
source ~/.rvm/scripts/rvm
rvm requirements
mkdir /var/www/gemserver
mkdir home/your_folder/gems
require "rubygems"
require "geminabox"
run Geminabox::Server
If you want to keep your server open, you can remove HTTP authentication by removing
these lines:
And that’s it, we’ve got ourselves a private gem server! Figure 7.1
Figure 7.1
So how do we push gems now? Let’s push the Core module to see how it works. The
following commands can be run from your local computer.
First, navigate to SamuraiCRM/engines/core.
gem inabox -c
Step 3: Push the gem
rm samurai_core-0.0.1.gem
Figure 7.2
Adding the source to the default sources will allow us to query for our gems from
anywhere on your computer.
gem sources -a http://samurails:[email protected]:9292/
Figure 7.3
We just added our gem server as a source for the rubygems gem. Now, let’s see how we
can load gems in your Rails application using Bundle.
# Gemfile
source 'https://rubygems.org'
source "http://samurails:[email protected]:9292"
I recommend taking your secret key out of the Gemfile and store it in an environment
variable.
# Gemfile
source 'https://rubygems.org'
source "http://samurails:#{ENV['GEM_SERVER_KEY']}@samurails.com:9292"
source .zshrc
Remove the path option for the Core module inside your Gemfile.
# Gemfile
gem 'samurai_core'
bundle update
Step 7: It works!
Figure 7.4
Now that we setup our private Gem server, it’s time to push our modules! But first, let’s
update the versions. We’re going to update all our modules to 1.0.0 and officially release
them! Do the four following steps for each module: Core, Contacts and Tasks and
replace {module} with the appropriate module name.
def version
'1.0.0'
end
rm samurai_{module}-1.0.0.gem
7.5 Push your application
Our engines are now easily accessible and can be used in any Ruby on Rails application
simply by specifying the source and the gem name. That means we can push
SamuraiCRM to production! Well, ‘production’ is a big word but you get the idea.
I’m going to push SamuraiCRM to Heroku but feel free to push it wherever you want.
The free dyno from Heroku will be more than enough to handle the heavy usage of
SamuraiCRM!
cp SamuraiCRM SamuraiCRMProd
Navigate inside SamuraiCRMProd and remove the engines folder. We won’t be needing it
for the production application.
source "http://samurails:#{ENV['GEM_SERVER_KEY']}@samurails.com:9292"
# .. GEMS …
gem 'samurai_core', '1.0.0'
gem 'samurai_contacts', '1.0.0'
gem 'samurai_tasks', '1.0.0'
Step 6: Add Heroku specific gems
Note that you have to move sqlite to the development and test group else Heroku will
crash!
group :development, :test do
gem 'sqlite3'
end
group :production do
gem 'pg'
gem 'rails_12factor'
end
Step 7: Bundle
bundle update
git add .
git commit -m 'Switch to production application'
git push heroku master && heroku run rake db:migrate -a samuraicrm
Figure 7.5
Figure 7.5
7.6 The modular workflow
Before finishing this chapter, I thought it would be interesting for you to see a complete
work flow when changing some code inside a modular application.So here it is!
Go inside one of your module inside the development application and create a new branch
with:
Always be sure that you didn’t make changes that breaks your previous hard word!
git add .
git commit -m 'Amazing New Feature'
Step 6: Push
Update the Gemfile of the production application to use your beta gem:
bundle install
git add .
git commit -m 'Updated my_module to 1.0.0.my_branch'
Capistrano
Happy with your new feature? Time to merge the changes then.
rm my_module.1.0.gem
Heroku
In this chapter, we learned more about the best ways to work with a modular application.
We saw how to manage our modules and how to work on them. We also saw how we
could create a private gem server to keep our precious code from the dangerous world.
Finally, we pushed SamuraiCRM to Heroku and discovered how easy it was.
Following some rules is important to prevent your modular app from becoming a
mess.
If you want to keep your modules private, you’ll have more work!
Gem Servers are not that hard to setup and are quite cheap.
Pushing a modular application is super easy.
There is no next step! I’ve taught you everything I know about building modular
applications. I hope you enjoyed the ride and picked some new stuff on the way!
Chapter 8
The End
You’ve reached the end of Modular Rails! I hope you learned a lot while reading this
book. If you still have questions about modularity, you can send me an email at
[email protected] and I will write a blog post to answer you. Let’s see everything we’ve
learned!
First of all, you learned that there were other types of architecture than the regular Ruby
on Rails application. Don’t get me wrong, the way Rails is organized is awesome and you
can do a lot of things with it. But sometimes, you need something a bit different.
Something more modular.
Don’t rush to modularity every time you have to build a new application. You need to
carefully weigh the pros and cons before taking a decision. Yes, modular applications are
awesome but are hard to maintain and can become a pain in the a**.
Be careful. With great powers come great responsibility.
Now it’s your turn! You can start building modular applications. Feel free to reuse my
gem modular_engine, I will try to maintain it and improve it. Like with everything, you
need to practice to learn it. So build some simple modular applications before you attack a
real application. Be sure to understand how everything is connected.