Codementor Events

Migrating from Rails to Sails: Comparison and Implementation Guide

Published Sep 10, 2016Last updated Jan 18, 2017

Ruby is an awesome programming language and from my experience, the easiest to learn. As a web developer with Ruby background, there are chances that you write JavaScript for a better user-facing application. You are a smart Ruby developer (you probably love the DRY principle) and that’s why you make use of Rails MVC framework to build your Ruby apps.

Your life has been going so simple and happy until you started feeling left behind because there is a new tool that everybody is using and talking about—Node. From research, you have come to realize that Node is just JavaScript on the server and because of the performance features it comes with, it has gained massive popularity.

Together, we are going to approach Node from Rails perspective so it could feel familiar. Sails is a Node framework inspired by Rails, therefore, with your little JavaScript skills, we will build an application in Node using Sails while putting in practice everything we know about the Rails architecture.

This article is structured in such a way that Rails and Sails’ core concepts are discussed side by side starting from their background , down to their REST implementation. We have a long way to go so grab a cup of coffee and relax so we can have a better time learning.

Project Demo

Background

Rails

Rails is written in the Ruby programming language and was created by David Hansson. Rails was open sourced by David in 2004. The framework was designed using the MVC (Model-View-Controller) pattern for better separation of concerns. This particularly made Rails intuitive to use because as long as a developer is comfortable with Object-Oriented Programming and the MVC pattern, one can easily jump on Rails with basic understanding of Ruby concepts and start building awesome web products.

Sails

Sails is written in JavaScript, precisely Node.js. Sails framework was created by Mike McNeil and was inspired by Rails. This fact that Sails was inspired by Rails made them have a lot in common with each other—the architecture (MVC), directory structure, assets management, etc. For these reasons, if you are coming from Rails and have basic JavaScript skills, then you are good to start writing Node apps with Sails.

Version Managers & Installation

Version managers are useful when you have to build different kinds of projects on the same machine and the same platform but require different versions of the platform. For instance, you could have v.2.3 of Ruby on your machine but you are working with a team on a given project that strictly requires v1.9. The possible solutions to this are virtual machines (which, of course, are harsh on machine resources) or version managers.

Keeping in mind that we are going to build two projects side by side on different platforms (Ruby & Node), let’s install their version managers:

Ruby (Rails)

Ruby is known as Ruby Version Manager (RVM). To get RVM on our machine, we need to first install the author’s public key:

gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3

Then we can go ahead to install RVM using curl:

curl -sSL https://get.rvm.io | bash -s stable

You can run any RVM command to confirm installation:

rvm --help

With RVM installed, we can have multiple installations of Ruby versions but let’s just install one as default:

rvm --default install 2.3.1

The install command is used to install specific versions and the —default flag sets the particular version as default.

You can confirm installation by running:

ruby -v

Node (Sails)

Node is known as Node Version Manager (NVM). We can install NVMwith the following curl script:

curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.31.7/install.sh | bash

You need to add NVM to your path using:

# Add to path
export NVM_DIR="$HOME/.nvm"
# Load NVM
[-s "$NVM_DIR/nvm.sh"] && . "$NVM_DIR/nvm.sh"

We want to use the latest version of Node so to install, run:

nvm install node

Now we have installed both Ruby and Node, our platforms are ready.

Package Managers

Code re-use is beautiful but it’s more enjoyed when you re-use other’s. These days, the community of a given technology is calculated with the amount of contributions made by the community in mind. When building large projects, there is a tendency that you are going to rely on other people’s solution to enhance yours. The way these solutions are managed and kept in sync are through package managers.

Ruby (Rails)

Rails package manager is called gem and you can install any gem that is useful to you using the following syntax:

gem install <package-name>

Eg:

ruby install sass

Where sass is the name of the package we want to install.

Rails itself is a gem so we install it straight away:

gem install rails

Node (Sails)

Node’s package manager is npm and comes installed when we install Node. npm has much in common with gem:

npm install <package-name>

Eg:

npm install express

Sails, just like Rails, is also a Node package and can be installed with:

npm install -g sails

The -g flag tells npm to install Sails globally so it can only be installed once and used for subsequent projects. If the -g flag is ignored, Sails can be installed in the directory the command was run, which could be inside a folder labeled node_modules.

Setup

We have gone through the preparation process for a Rails and Sails project and now it’s time to set it up. Just a reminder, we will build the same project twice. One with Rails and the other with Sails. Because we are coming from a Rails background, we will first handle a task in Rails and then see how we can complete that in Sails.

Create a folder named cm-rails-sails where cm stands for code mentor. It is inside this directory that both projects will live.

Rails

To create a new project in Rails, inside the created folder, run:

rails new todo-rails

Sails

Just as we have done for Rails, in the same cm-rails-sails folder, run:

sails new todo-sails

Command Line Tool

Project scaffold and boilerplates can really be a pain in the skull because it is a repetitive process that just bores the brain to perform. Both frameworks provide command line tools for scaffolding projects (just as we have seen above) and generating a project’s pieces like controllers, models, etc.

Listing all the commands for both frameworks are irrelevant because trust me, you will never memorize them once. The best way to learn them is to encounter them through the learning process.

Directory Structure

Let’s have a look at the directory structure for both projects and see what we can make of it:

Rails

# Holds application core logic
|--app # Truncated to show the MVC folders
|----controllers
|----models
|----views
|--bin # Executables
|--config # Application specific configurations
|--db # Seeds Schema Migrations
|--lib
|--log # Application log files
|--public # Client facing contents
|--test # App's test files
|--tmp # Temporary contents like cache
|--vendor # Third party libs from package managers
|--Gemfile # Packages
|--Rakefile # Application specific commands

Sails

# Holds application core logic
|--api # Truncated to show the MVC folders
|----controllers
|----models
|--assets # Client facing contents
|--config # Application specific configurations
|--node_modules # Third party libs from package managers
|--tasks # Application specific commands
|--app.js # Entry
|--Gruntfile.js # Grunt task for assets
|--package,json # Packages

Sails looks simpler which would make it friendlier to approach. Rails is more matured because it has been around for a while and way older than Sails

Database Configuration

We will leave the database configuration as defaults which will leave us with sqlite for Rails and disk for Sails:

Rails

# ./config/database.yml
default: &default
  adapter: sqlite3
  pool: 5
  timeout: 5000

development:
  <<: *default
  database: db/development.sqlite3

Sails

// ./config/connections.js
localDiskDb: {
    adapter: 'sails-disk'
  },

Models and Migrations

Let’s have a look at the data layer of MVC — M. Both frameworks use the same approach to generate models, but the difference is what happens under the hood:

Rails

rails generate model Todo owner:string text:string

Sails

sails generate model Todo owner:string text:string

As you can see the only difference is rails and sails. Both commands generate the following models:

Rails

# ./app/models/todo.rb
class Todo < ApplicationRecord
end
# ./db/migrate/<time_stamp>_create_todo.rb
class CreateTodos < ActiveRecord::Migration[5.0]
  def change
    create_table :todos do |t|
      t.string :owner
      t.string :text

      t.timestamps
    end
  end
end

Sails

// ./api/models/Todo.js
module.exports = {

  attributes: {

    owner : { type: 'string' },

    text : { type: 'string' }
  }
};

Sails do not require migration files to define its schema. The schema of a given model is defined right inside the model’s definition. Each model file must export an attributes object. The attributes object defines the schema of a given model.

Rails require that you migrate by running:

rails db:migrate

On the other hand, migration is not required for Sails. Waterline ORM which is what Sails uses does the magic for you when you start hitting the database with requests.

Routing

Routing is a key aspect of every web application, so both Rails and Sails take routing matters to heart. Let’s create some routes on both projects:

Rails

# ./config/routes.rb
# Todo routes
get '/todos', to: 'todo#index'
get '/create', to: 'todo#new'
post '/create', to: 'todo#create'

# Home page
root 'todo#index'

We have created four routes to be handled by Todo controllers which we are yet to create. Now let’s see how we can do the same in Sails:

Sails

// ./config/routes.js
module.exports.routes = {
   // Todo routes
  'get /todos': 'TodoController.index',
  'get /create': 'TodoController.new',
  'post /create': 'TodoController.create',
   // Index page
  '/': 'TodoController.index'
}

Sails uses an object’s key-value pairs to map the controller’s action methods to its respective routes. The first word in the keys describes the HTTP verb that will be used to take care of this request.

Controllers

We have our data layer in place and our routes are defined. Next thing to do is to use controllers to handle requests coming from routes. In doing so, we can either send data as response or receive data as request with the help of our models:

Rails

# ./app/controllers/todo_controller.js
class TodoController < ApplicationController
  # index action method
  def index
    # View data
    @todos = Todo.all
  end

  # new action method
  def new
  end

  # create action method
  def create
    # Retrieve data from form body
    @todo = Todo.new(params.require(:todo).permit(:owner, :text))
    # Persist
    @todo.save
    # Redirect to home page after persisting
    redirect_to '/'
  end
end

We have created three action methods to handle our routes: indexnew, and create. The first two handles get requests so Rails can demand a presentation layer—the view. We will tackle that soon. The last handles form submission so no view is required, rather, we just redirect to the home page.

Sails

// ./api/controllers/TodoController.js
module.exports = {
    // index action method
    index: function(req, res) {
            Todo.find().exec(function(err, todos) {
                if(err) throw err;
                // Render view with found todos
                res.view('todo/index', {todos: todos});
            });
    },
    // new action method
    new: function(req, res) {
        // Render todo form
        res.view('todo/new');
    },
    // create action method
    create: function(req, res) {
            // Create a new todo using the Todo model
            Todo.create(req.body).exec(function(err, todo) {
                if(err) throw err;
                // Redirect if successful
                res.redirect('/');
            });
    },
};

The three methods implemented on Rails are now available in our Sails project. The index method fetches all todos and renders a view with the todo. The new method just renders a form in a file named new. The create method creates a new todo and redirects to the home page if the attempt was successful.

Views

From the previous section which we discussed controllers, it is easy to conclude that we just need two views in the todo app—a view to show all todos, and another to show a form to create a todo. At this point, if you try to run with rails server or sails lift, you have run into errors that try to tell you that the view template files are nowhere to be found. Let’s create them:

Rails

<!-- ./app/views/todo/index.html.erb -->
<h1>Rails Todos</h1>

<table>
  <tr>
    <th>Owner</th>
    <th>Text</th>
  </tr>

  <% @todos.each do |todo| %>
    <tr>
      <td><%= todo.owner %></td>
      <td><%= todo.text %></td>
    </tr>
  <% end %>
</table>

Rails Todo List

The index view loops through the view data that was passed to it from the controller and presents the data using a table.

<!-- ./app/views/todo/new.html.erb -->
<%= form_for :todo do |f| %>
  <p>
    <%= f.label :owner %><br>
    <%= f.text_field :owner %>
  </p>

  <p>
    <%= f.label :text %><br>
    <%= f.text_field :text %>
  </p>

  <p>
    <%= f.submit %>
  </p>
<% end %>

The new view presents a form that posts to the /create route so that the request can be handled by the create action method in the Todo controller.

Sails

<!-- ./views/todo/index.ejs -->
<h1>Todos</h1>

<table>
  <tr>
    <th>Owner</th>
    <th>Text</th>
  </tr>

  <% todos.forEach(function(todo){ %>
    <tr>
      <td><%= todo.owner %></td>
      <td><%= todo.text %></td>
    </tr>
  <% }); %>
</table>

Sails Todo List

<!-- ./views/todo/new.ejs -->
<form action="/create" method="post">
  <input type="text" name="owner">
  <input type="text" name="text">
  <input type="submit" value="Submit">
</form>

The same thing we saw in Rails applies to Sails. As a matter of fact, the template engine for Sails, ejs was inspired by the template engine of Rails, erb.

REST (BONUS)

RESTful services have become the best way to go when building solutions that will be consumed by different platforms (PCs, Mobiles, IoT, etc). Before we wrap up our discussions, let’s quickly have a look on how to generate resource endpoints for REST and how to return JSON data rather than rendering views:

Rails

Add the following route in Rails route config:

# ./config/routes.rb
resources :todos

When we do so, we are served with a collection of routes that promotes building REST apps:

GET /todos(.:format) todos#index
POST /todos(.:format) todos#create
GET /todos/new(.:format) todos#new
GET /todos/:id/edit(.:format) todos#edit
GET /todos/:id(.:format) todos#show
PATCH /todos/:id(.:format) todos#update
PUT /todos/:id(.:format) todos#update
DELETE /todos/:id(.:format) todos#destroy

We can confirm this by running:

rails routes

If we architected our application in such manner, we would have to return JSON rather than views in our controller action methods’ responses:

def index
    @todos = Todo.all
    #Render data as JSON
    render :json => @todos
  end

Sails

The case is quite different in Sails. Sails uses automatic route binding for RESTful services. Let’s see for ourselves. Run:

sails generate api Todo

The above command generates a model (Todo.js) and a controller (TodoController.js) for our projects. But that is not all. Once we create an action method in the controller, Sails automatically binds the method name to a route named after the action. For instance, if we create an action method named new, Sails will create a route todo/new and bind it to new.

Just like what we saw in Rails, we can now start returning JSON rather than rendering views:

index: function(req, res) {
  Todo.find().exec(function(err, todos) {
    if(err) throw err;
    // Render data as JSON
    res.json(todos);
  });
},

Final Note

We tried as much as possible to touch the core concepts of MVC by comparing Rails and Sails side by side. You have gotten your hands dirty with this and I bet your feet are wet. For this reason, go ahead and keep building apps with Node using a tool that makes you feel like you are still writing Rails.

You are bound to encounter challenges but the community is broad with many experts that can help you, and within few minutes you are moving on with a perfect solution. Good luck writing more JavaScript.

Discover and read more posts from Christian Nwamba
get started
post comments2Replies
Bill Pearce
7 years ago

Good practical basic comparison! Exactly what I was after.

ColCh
8 years ago

probably throwing error in node is not a good idea. it can drop node process if not handled. is this case covered by Sails?