12/08/2018, 13:37

Modular Rails

Have you ever imagine building web application as huge and complex as Facebook or Twitter? The first thing we can think of is that no one can develop and maintain such a giant web on a single monolithic project? One will be simply drown by the flood of codes adding each day. One can ...

ruby_shadow.png

Have you ever imagine building web application as huge and complex as Facebook or Twitter? The first thing we can think of is that no one can develop and maintain such a giant web on a single monolithic project? One will be simply drown by the flood of codes adding each day. One can intuitively think of the many "small" projects those giant website can consist of. This gives us the first notion of modularity.

What is Modularity in Programming?

We will define two concepts, one is monolithic and another is modular. Monolithic is formed of a single large block of stone, modular is having parts that can be connected or combined in different ways __ defined by dictionary. We can see a monolithic project is single huge project with mostly inseparable components. Where as a modular project is the one which we seperated into single interchangeable parts.

For example: a regular monolithic Rail Application is composed of:

app
    controller
        users_controller.rb
        sessions_controller.rb
        products_controller.rb
        checkouts_controller.rb
    model
        user.rb
        product.rb
        checkout.rb
    view
        user
        product
        checkout
config
...

Here is a monolithic project, a regular project that we normally build. We are not worried much if the project is still small. But if project get bigger, adding new feature, writing test, and maintainance have become a pain in the a**. Now modularity comes to the rescue, and this is how it may look:

app
    controller
    model
    view
engines
    base
        app
            controller
                users_controller.rb
                sessions_controller.rb
            model
                user.rb
            view
                user
        config
        ...
    product
        app
            controller
                products_controller.rb
            model
                product.rb
            view
                product
        config
        ...
    checkout
        app
            controller
                checkouts_controller.rb
            model
                checkout.rb
            view
                checkout
        config
        ...
config
...

As we see files structure above, we have modularize product and checkout and leave the basic funtionality to core.

Why modularize?

Suppose We are a freelancer who often work on many projects with the common funtionalities such as user registration, user login. If we can make this functionality reuseable, how much time can we save, say, for 100 projects. So one advantage is reuseablity.

Another one is encapsulation of features into modules. In this way we don't need to care about the other features and the conflict between those feature as long as we know about common interface.

One more advantage is that the project is easier to be tested. Writing test can be baffling if functionality is mixed up.

How to modularize?

We will create a small example project to demonstrate this. Suppose you are planning to build a big project (you can envision), and one of the features you want to create is user registration.

Let's create a new app.

rails new demo

Let's create a user registration module in the project.

rails plugin new user_registration --mountable

We see a new folder user_registration is created. and inside this folder is a regular Rails application. Let's create a new folder engines and move user_registration folder inside this.

mkdir engines && mv user_registration ./engines

We have to edit user_registration.gemspec file in order to bundle successfully.

$:.push File.expand_path("../lib", __FILE__)

# Maintain your gem's version:
require "todo/version"

# Describe your gem and declare its dependencies:
Gem::Specification.new do |s|
  s.name        = "todo"
  s.version     = Todo::VERSION
  s.authors     = ["kouchchivorn"]
  s.email       = ["kouch.chivorn@gmail.com"]
  s.homepage    = "https://www.chivorn.com/"
  s.summary     = "User registration"
  s.description = "Modularize user registration functionality"
  s.license     = "MIT"
  ...

Let's connect the user registration module to the main app by adding gem to Gemfile

gem 'user_registration', path: 'engines/user_registration'

Then we can bundle to make it work.

bundle install

and we see this line show up in console.

Using user_registration 0.1.0 from source at `engines/user_registration`

Now we have to add route so that we can access user_registration through the web browser.

Rails.application.routes.draw do
  mount UserRegistration::Engine => "/", as: 'signup'
end

Let's create a scaffold user to register user in engines/user_registration

rails g scaffold user name:string email:string password:string

Now add a root path to the user_registration module.

UserRegistration::Engine.routes.draw do
  resources :users
  root 'users#index'
end

It is time to test whether our app is working or not. Start the server in the root folder.

rails s

Oh no! one more thing to do is run database migration and before we can to that we have to edit this file engines/user_registration/lib/user_registration/engine.rb

module Todo
  class Engine < ::Rails::Engine
    isolate_namespace UserRegistration

    initializer :append_migrations do |app|
      unless app.root.to_s.match(root.to_s)
        config.paths["db/migrate"].expanded.each do |p|
          app.config.paths["db/migrate"] << p
        end
      end
    end
  end
end

Now run database migration

rails db:migrate

OK. start the server again.

rails s

Now open the web browser and enter the url

localhost:3000

and try to create a user. It works!!!

I hope you enjoy the journey with me and find it useful.

0