~/pavonz

A rails 4 tutorial application for beginners

2013-08-20

Intro

Unless you lived under a rock in the past 5 years or so, you should have already heard of Ruby on Rails. In this article, we'll write a very simple web application with it to show its power and speed of development.

We'll cover the following topics:

Quick setup

The following section offers a very quick overview of the tools you need to get started with Ruby on Rails development. If you already have a working rails environment, excluding the default ruby installation that some operating systems have, then feel free to skip the steps.

Install Ruby

If you run Linux or MacOSX on your computer, you may already have Ruby installed by default, however these kind of installations are out of date (eg: ruby 1.8.x) and gems are difficult to manage. The best way is to install it manually or just use some tool that helps to manage custom Ruby installations, even multiple versions (eg: 1.9.x and 2.0.0 releases).

Instead of repeating what others have already said, I will link some useful guides:

Now, you should have all the requirements to get started.

Install Bundler gem

Bundler is a Ruby gem that helps you to manage development dependencies in a project. We'll see how it works later, for now, just install it:

gem install bundler

Install Rails v4.0.0 gem

We're going to use the upcoming new version of rails. At the time of writing (mid june 2013), it's the 2nd Release Candidate but, according to its author, it should be enough stable.

EDIT: rails 4.0.0 is out, so don't use rc2.

gem install rails -v4.0.0

A simple web application to manage and share bookmarks

Someone says that doing it is the best way to learn, so here's a less common tutorial app: a simple platform to manage or share bookmarks. It's nothing fancy, but it's enough to apply some basic concepts. Moreover, aren't you bored about todo lists and blog engines over and over? :-)

Create the rails app

When you install rails, it comes with a bunch of other libraries and/or command line tools. The main one is rails. For now, we'll use it to generate the initial skeleton:

rails new bookmarks -T -d sqlite3 -B

I've also passed some arguments to the command:

Once you run the command, you should see a long list of lines that explain what is happening:

andrea@mbair ~/Works/12dos % rails new bookmarks -T -d sqlite3 -B
create
create README.rdoc
create Rakefile
create config.ru
create .gitignore
create Gemfile
create app
[...]
andrea@mbair ~/Works/12dos % cd bookmarks
andrea@mbair ~/Works/12dos/bookmarks %

A new directory called bookmarks (or whatever name you passed) has been created, along with a set of files and directories inside it. Enter this directory, we'll see the important contents during the article.

A first look at Bundler and Gemfile

We mentioned Bundler as a tool to manage development dependencies in a Ruby app. It works by reading a file called Gemfile that should be present in the root directory of the app. You need this to make sure all the required gems are installed, even rails itself. Here's a brief look at the Gemfile generated by the rails new command:

source 'https://rubygems.org'

# Our rails version

gem 'rails', '4.0.0'

# Use sqlite3 as the database for Active Record

gem 'sqlite3'

# Use SCSS for stylesheets

gem 'sass-rails', '~> 4.0.0'

# Use Uglifier as compressor for JavaScript assets

gem 'uglifier', '>= 1.3.0'

# Use CoffeeScript for .js.coffee assets and views

gem 'coffee-rails', '~> 4.0.0

# Uncomment this if you haven't a nodejs and coffeescript installed

# gem 'therubyracer', platforms: :ruby

[...]

As you can see, this is the default list of gems you need to start working on a Rails project. Of course, you'll change it very often to add, remove or upgrade gems.

Now you can finally run bundle install (the command we skipped by specifying -B):

andrea@mbair ~/Works/12dos/bookmarks % bundle install
Fetching gem metadata from https://rubygems.org/..........
Fetching gem metadata from https://rubygems.org/..
Resolving dependencies...
Using rake (10.0.4)
Installing i18n (0.6.4)
[...]
Your bundle is complete!
Use `bundle show [gemname]` to see where a bundled gem is installed.
andrea@mbair ~/Works/12dos/bookmarks %

If you use git, this is a good moment to initialize the repo and proceed with the initial commit.

Run the development server

Even if you haven't yet wrote any line of code, you can already use the Rails development server. Run the following command:

andrea@mbair ~/Works/12dos/bookmarks % rails server
=> Booting WEBrick
=> Rails 4.0.0 application starting in development on http://0.0.0.0:3000
=> Run `rails server -h` for more startup options
=> Ctrl-C to shutdown server
[2013-06-14 16:10:48] INFO WEBrick 1.3.1
[2013-06-14 16:10:48] INFO ruby 1.9.3 (2012-04-20) [x86_64-darwin12.2.0]
[2013-06-14 16:10:48] INFO WEBrick::HTTPServer#start: pid=1174 port=3000
[...]

then point your browser to http://localhost:3000 and you'll get a standard welcome page. This time we've used rails server, another useful command offered by Rails, moreover, it works only when you run it inside a Rails project folder. We'll see other useful commands later.

A quick introduction to MVC patterns and Rails

Rails is usually defined as an MVC framework. Basically, it means that the app behaviour is defined in this way:

This is, of course, an over-simplification, but it should be enough to get a vague idea of how it works. I'll dive into some details later.

Add a Bookmark resource

The goal of our app is to manage bookmarks, right? So we create our Bookmark resource:

andrea@mbair ~/Works/12dos/bookmarks % rails generate scaffold bookmark title:string url:string
invoke active_record
create db/migrate/20130614142337_create_bookmarks.rb
create app/models/bookmark.rb
invoke resource_route
route resources :bookmarks
invoke scaffold_controller
create app/controllers/bookmarks_controller.rb
[...]

This time, we've used rails generate, one of the most useful commands. In this case, we've generated a scaffold that creates all the necessary files and code to automatically get a full MVC stack that manages a Bookmark. In the next paragraphs, we'll see in detail what did the above command.

Database migration

We've specified that a Bookmark record is made of a title and a url fields, both of type string (255 chars by default). So, the rails scaffold generator has automatically created a migration script to update your database schema: db/migrate/db/migrate/20130614142337_create_bookmarks.rb

class CreateBookmarks < ActiveRecord::Migration
  def change
    create_table :bookmarks do |t|
      t.string :title
      t.string :url

      t.timestamps
    end
  end
end

Even if it's almost easy to read, this code basically means:

However, your database still doesn't know about these changes to the schema, you need to run the migration task:

andrea@mbair ~/Works/12dos/bookmarks % rake db:migrate
== CreateBookmarks: migrating ================================================
-- create_table(:bookmarks)
-> 0.0011s
== CreateBookmarks: migrated (0.0012s) =======================================

Rake is another common and useful tool in the Ruby world. It acts like a Makefile in a Ruby fashion. Rails comes with several rake tasks ready to use (you can also create custom ones, if needed):

andrea@mbair ~/Works/12dos/bookmarks % rake -T
rake about # List versions of all Rails frameworks and the environment
rake assets:clean # Remove old compiled assets
rake assets:clobber # Remove compiled assets
rake assets:environment # Load asset compile environment
[...]

The Boomark model

The model representing a bookmark was created in app/models/bookmark.rb, at the moment it's almost empty in terms of code, but it already knows how to behave with the database. To demonstrate this, we'll use another Rails command to open a console:

andrea@mbair ~/Works/12dos/bookmarks % rails console
Loading development environment (Rails 4.0.0)

>

Now, we have a shell to manually issue commands to our rails app. Let's see if it knows something about our Bookmark:

> Bookmark
> => Bookmark(id: integer, title: string, url: string, created_at: datetime, updated_at: datetime)

> Bookmark.count

     (0.3ms)  SELECT COUNT(*) FROM "bookmarks"

=> 0

It knows about the bookmarks table, its fields and how to query the database. Ok, create one Bookmark:

> Bookmark.create(title: "Hello Bookmarks app!", url: "http://localhost:3000")
> (0.1ms) begin transaction
> SQL (7.7ms) INSERT INTO "bookmarks" ("created_at", "title", "updated_at", "url") VALUES (?, ?, ?, ?) [["created_at", Fri, 14 Jun 2013 15:16:17 UTC +00:00], ["title", "Hello Bookmarks app!"], ["updated_at", Fri, 14 Jun 2013 15:16:17 UTC +00:00], ["url", "http://localhost:3000"]]
> (0.8ms) commit transaction
> => #<Bookmark id: 1, title: "Hello Bookmarks app!", url: "http://localhost:3000", created_at: "2013-06-14 15:16:17", updated_at: "2013-06-14 15:16:17"

> Bookmark.count
  (0.3ms)  SELECT COUNT(*) FROM "bookmarks"

=> 1

The BookmarksController and its views

We said that the scaffold generator has created all the parts of the MVC stack, now is the time for the controller in app/controllers/bookmarks_controller.rb. You'll see a BookmarksController class and a bunch of methods (called actions). Each action corresponds to an HTTP PATH (eg: /bookmarks) and an HTTP verb (GET, POST, PUT, DELETE).

The autogenerated code is quite clear and, for brevity reasons, I can't dive too much in it. The interesting parts are:

Routes

The final component for the MVC stack is about routing the HTTP requests to the proper controller and action. To make this, the Rails generator has updated config/routes.rb file and has added the line resources :bookmarks that is a shorthand to say: use REST routes for the the resource bookmark. If you're wondering what routes, you can use the relative rake task:

andrea@mbair ~/Works/12dos/bookmarks % rake routes
Prefix Verb URI Pattern Controller#Action
bookmarks GET /bookmarks(.:format) bookmarks#index
POST /bookmarks(.:format) bookmarks#create
new_bookmark GET /bookmarks/new(.:format) bookmarks#new
edit_bookmark GET /bookmarks/:id/edit(.:format) bookmarks#edit
bookmark GET /bookmarks/:id(.:format) bookmarks#show
PATCH /bookmarks/:id(.:format) bookmarks#update
PUT /bookmarks/:id(.:format) bookmarks#update
DELETE /bookmarks/:id(.:format) bookmarks#destroy

Try on the server

Now, the fun part. Restart your rails server with rails server and go to http://localhost:3000/bookmarks. If you followed the above example in console, you should already see the Bookmark created earlier, otherwise, you can create a new one by clicking on New bookmark link.

Try http://localhost:3000/bookmarks.json and see what you'll get. You still haven't written a single line of Ruby, and it already does a lot of things. Awesome, isn't it? :-)

Adding users and authentication

Once we have the Bookmark resource, we need Users that own bookmarks and a way to authenticate them. The Ruby and Rails community is very active: whatever the task is, you'll probably find at least one gem to do it (if you don't, it might be a good idea to write one). Luckily, user authentication is a very common task, and there are several options. The most used is Devise, let's use it.

Installing Devise gem

Open Gemfile and add this line:

gem 'devise', '~> 3.0.0.rc'

We also told Bundler to use a version of Devise that should be greater_or_equal than its minor version (eg: 3.0.1 is ok, 3.1.0 isn't).

Run bundle install to install the gem:

andrea@mbair ~/Works/12dos/bookmarks % bundle install
Resolving dependencies...
Using rake (10.0.4)
[...]
Installing devise (3.0.0.rc)
[...]
Your bundle is complete!
Use `bundle show [gemname]` to see where a bundled gem is installed.
andrea@mbair ~/Works/12dos/bookmarks %

and run the Devise generator:

andrea@mbair ~/Works/12dos/bookmarks % rails generate devise:install
create config/initializers/devise.rb
create config/locales/devise.en.yml
===============================================================================

Some setup you must do manually if you haven't yet:

1. Ensure you have defined default url options in your environments files. Here
   is an example of default_url_options appropriate for a development environment
   in config/environments/development.rb:

   config.action_mailer.default_url_options = { :host => "localhost:3000" }

   In production, :host should be set to the actual host of your application.

2. Ensure you have defined root*url to \_something* in your config/routes.rb.
   For example:

   root :to => "home#index"

3. Ensure you have flash messages in app/views/layouts/application.html.erb.
   For example:

     <p class="notice"><%= notice %></p>
     <p class="alert"><%= alert %></p>

4. If you are deploying Rails 3.1+ on Heroku, you may want to set:

   config.assets.initialize_on_precompile = false

   On config/application.rb forcing your application to not access the DB
   or load models when precompiling your assets.

5. You can copy Devise views (for customization) to your app by running:

   rails g devise:views

===============================================================================
andrea@mbair ~/Works/12dos/bookmarks %

As you can see, it has created two files under config/ directory:

Devise also told us to check five steps to complete the setup:

Bookmarks::Application.routes.draw do
  # [...]

  root 'bookmarks#index'

  # [...]
end

Generate a User model

Setup for Devise is done, but we still haven't a User model, we need to create one:

andrea@mbair ~/Works/12dos/bookmarks % rails generate devise User
invoke active_record
create db/migrate/20130617132545_devise_create_users.rb
create app/models/user.rb
insert app/models/user.rb
route devise_for :users

If you remember the scaffold generator we used to generate a Bookmark, this one is very similar even if just focused on the model, plus the route that tells Devise to handle /users/* paths. In fact, it created a migration for users and the User class. Edit the migration to look like this (remove or comment the rest):

class DeviseCreateUsers < ActiveRecord::Migration
  def change
    create_table(:users) do |t|
      ## Database authenticatable
      t.string :email, :null => false, :default => ""
      t.string :encrypted_password, :null => false, :default => ""

      ## Rememberable
      t.datetime :remember_created_at

      t.timestamps
    end

    add_index :users, :email, :unique => true
  end
end

Also the model needs to reflect the changes made to the migration:

class User < ActiveRecord::Base
  devise :database_authenticatable, :registerable, :rememberable, :validatable
end

The last step, is to run the migration with rake db:migrate

In this way, we have a simple authentication system that lets a user to register, login and logout. Try to run rake routes and you'll see the new routes for these actions, all referred to users. You can also play in the Rails console to create some user.

User has many Bookmarks

Now that we have User and Bookmark, we need to associate them. In this case, it's a one to many relation. As a shared convention, foreign keys name are made of the model name suffixed by _id. Of course, we'll not change the already created migrations, instead, we're going to create a new one:

rails generate migration AddUserIdToBookmark user_id:integer

Choosing an appropriate, arbitrary name is not mandatory, but it's a best practice that you should follow. Our generated migration will add a user_id integer column to the bookmarks table. Also, feel free to run the migration now.

At the moment, no one of our two rails models knows about each other, we've only added a field on the database, but it's meaningless without some additional instruction:

With these changes, a User instance will automatically have a bookmarks method (and many others), referred to all Bookmark records with that specific user id.

Require authentication to manage your bookmarks

Looking at the models, it looks like the data part is complete and working. However, the BookmarksController created by the scaffold command doesn't apply any authentication check, it needs some fix.

First of all, check user authentication before executing whatever action:

class BookmarksController < ApplicationController

  # other before_actions

  before_action :authenticate_user!

  # [...] actions
end

This means that, from now, each request that arrives to the BookmarksController will first check if a user is authenticated (authenticate_user! is a method provided by Devise), otherwise it will be redirected to the login page.

Then, access only bookmarks owned by the authenticated user. To do this, replace Bookmark occurrencies with current_user.bookmarks. As you may wonder, current_user is an object provided by Devise that represents the authenticated user, while .bookmarks is the method provided by the model association between User and Bookmark models.

Try on the server

If you didn't played with the web interface yet, now it's a good time to do it. Here there're some hints:

Handling wrong inputs

At the moment, we have several areas where a wrong user input might damage you application:

Let's see how to solve these issues.

Model validations

If you tried to use a password shorter than 8 chars or a bad formatted email during user registrations, you'd note that Devise has spotted those errors by not saving the record and rendering the user registration form again. In fact, Devise provided some default validations for users, but our app need validations on the Bookmark model too. Change app/models/bookmark.rb to look like this:

class Bookmark < ActiveRecord::Base
  belongs_to :user

  # ensure that a user_id is present
  validates :user_id, presence: true

  # ensure that title is present and at least 10 chars long
  validates :title, length: { minimum: 10 }, presence: true

  # ensure the url is present, and respects the URL format for http/https
  validates :url, format: {with: Regexp.new(URI::regexp(%w(http https)))}, presence: true
end

These validations are self-explanatory, so I'll not dive into them. Just keep in mind that there are many others or, in case, you can create custom ones.

When a resource is not found...

The second problem we addressed is about passing a wrong/non-existent identifier for a given resource. You can handle it in many ways, perhaps the simplest (and laziest) one is to redirect to the index, with a flash message. To do this, we only need to change some line of code in BookmarksController:

class BookmarksController < ApplicationController

  # [...] other code here

  private # Use callbacks to share common setup or constraints between actions.

  def set_bookmark
    unless @bookmark = current_user.bookmarks.where(id: params[:id]).first
      flash[:alert] = 'Bookmark not found.'
      redirect_to root_url
    end
  end
end

The set_bookmark is a method called in a before_action only for actions that need a resource id (show, edit, update, destroy). The new lines will safely check for existence on database, otherwise the user will be redirected to the root path of the site, with a short flash message.

Improving the app one step at a time

Our app is almost complete, or at leats, it lacks several little details that make a difference. We'll try to address some of these. The goal, is to demostrate how to improve an existing app with small steps.

Add a better root page

This is a simple task. All we need is a new route, controller and view. This time, we'll use a generator dedicated to the controller:

rails generate controller site index

I've omitted the output because at the moment you should already guess what it did. In short, it has created a SiteController with an action called index, plus the related view in app/views/site/index.html.erb and a new route in config/routes.rb. However, the route should be changed to reflect our goal:

Bookmarks::Application.routes.draw

# [...] other routes

# Comment/remove these lines

# get "site/index"

# root 'bookmarks#index'

# Use this

root 'site#index'
end

If you try to load http://localhost:3000 you'll see the empty template of app/views/site/index.html.erb. To render content, we need to change the controller in app/controllers/site_controller.rb:

class SiteController < ApplicationController
  def index
    # retrieve all Bookmarks ordered by descending creation timestamp
    @bookmarks = Bookmark.order('created_at desc')
  end
end

while the view in app/views/site/index.html.erb, might contain this basic markup as a starting point:

<h2>Latest Bookmarks</h2>
<table style="width: 100%">
  <thead>
    <tr>
      <th>Url</th>
    </tr>
  </thead>

  <tbody>
  <% @bookmarks.each do |bookmark| %>
    <tr>
      <td><%= link_to bookmark.title, bookmark.url %></td>
    </tr>
  <% end %>
  </tbody>
</table>

Again, note how the controller processes the request by retrieving some records from the database through the model Bookmark, then renders its view that contains the proper data to show the bookmarks.

Prettier GUI

Being a programmer, I haven't too much skills in design and layouts, but there are several CSS frameworks that help a lot. Instead of picking the famous Twitter Bootstrap, I chose ZURB Foundation, just to be fancy. There are a lot of ways to integrate existing CSS/Javascript frameworks and libraries inside a Rails app, the simplest one, is to use a gem when possible. Luckily, there's one for Zurb Foundation, just add it to your Gemfile:

gem 'zurb-foundation', '~> 4.2.2'

run bundle install, plus its install generator:

rails generate foundation:install

this command, will ask you to overwrite your default application layout in app/views/layouts/application.html.erb, press Y without worries. For brevity reasons, I've put the contents of the file here.

Moreover, we can also remove old scaffold's stylesheets:

rm app/assets/stylesheets/scaffolds.css.scss

Prettier forms

Forms are still ugly and boring to write, so we'll use another useful gem (from the same authors of Devise) to mitigate the problem. Add it to your Gemfile:

gem 'simple_form', '~> 3.0.0.rc'

run bundle install and the install generator (note that it already supports Foundation's markup structure):

rails generate simple_form:install --foundation

We'll also re-generate Devise views, because it supports SimpleForm:

rails generate devise:views

The generator will ask you if you want to overwrite existent files, press Y because that's what we really want.

The last step is to fix the Bookmarks form, it wasn't updated by the other generators, we'll do it manually by overwriting its (partial) template in app/views/bookmarks/_form.html.erb with the following content:

<%= simple_form_for(@bookmark) do |f| %>
  <%= f.error_notification %>
  <div class="form-inputs">
    <%= f.input :title %>
    <%= f.input :url %>
  </div>

  <div class="form-actions">
    <%= f.button :submit %>
  </div>
<% end %>

A welcome page

It would be professional to show a welcome page for non-registered users. To keep things simple, I'll use the same SiteController#index used for the root page. Modify app/views/site/index.html.erb to look like this.

Conclusions

Congratulations! If you carefully followed all the steps, you should have a basic, yet working, Ruby On Rails app. Of course, this is only the tip of the iceberg: for brevity reasons, I've oversimplified some concepts, and I've skipped several important aspects (eg: using CSS/SASS and Javascript/Coffeescript, testing, deploy, etc...) that you should know if you want to be a proficient Ruby On Rails developer. However, as you can see, this happened to be already a huge article, at least in terms of lenght :-P

I've uploaded the complete app on github: https://github.com/andreapavoni/12dos-bookmarks. It's a bit different from the one we wrote here, but it also adds some more features:

Clone or fork the code and experiment with it.

Webography

Bibliography