Create a Ruby on Rails 3 Application using MySQL

  1. Create the project directory
    mkdir ~/my_system
  2. Create a Rails application using MySQL DB
    % cd ~/my_system
    % rails new my_app -d mysql
    create
          create  README
          create  Rakefile
          create  config.ru
          create  .gitignore
          create  Gemfile
          create  app
          create  app/controllers/application_controller.rb
          create  app/helpers/application_helper.rb
          create  app/mailers
          create  app/models
          create  app/views/layouts/application.html.erb
          create  config
          create  config/routes.rb
          create  config/application.rb
          create  config/environment.rb
          create  config/environments
          create  config/environments/development.rb
          create  config/environments/production.rb
          create  config/environments/test.rb
          create  config/initializers
          create  config/initializers/backtrace_silencers.rb
          create  config/initializers/inflections.rb
          create  config/initializers/mime_types.rb
          create  config/initializers/secret_token.rb
          create  config/initializers/session_store.rb
          create  config/locales
          create  config/locales/en.yml
          create  config/boot.rb
          create  config/database.yml
          create  db
          create  db/seeds.rb
          create  doc
          create  doc/README_FOR_APP
          create  lib
          create  lib/tasks
          create  lib/tasks/.gitkeep
          create  log
          create  log/server.log
          create  log/production.log
          create  log/development.log
          create  log/test.log
          create  public
          create  public/404.html
          create  public/422.html
          create  public/500.html
          create  public/favicon.ico
          create  public/index.html
          create  public/robots.txt
          create  public/images
          create  public/images/rails.png
          create  public/stylesheets
          create  public/stylesheets/.gitkeep
          create  public/javascripts
          create  public/javascripts/application.js
          create  public/javascripts/controls.js
          create  public/javascripts/dragdrop.js
          create  public/javascripts/effects.js
          create  public/javascripts/prototype.js
          create  public/javascripts/rails.js
          create  script
          create  script/rails
          create  test
          create  test/fixtures
          create  test/functional
          create  test/integration
          create  test/performance/browsing_test.rb
          create  test/test_helper.rb
          create  test/unit
          create  tmp
          create  tmp/sessions
          create  tmp/sockets
          create  tmp/cache
          create  tmp/pids
          create  vendor/plugins
          create  vendor/plugins/.gitkeep
    • To create a new Rails project using Git as the source control
      mkdir my_app
      cd my_app
      git init
      rails new . --git --database=mysql
  3. Use Bundler to manage and install Rails application gem dependencies
    % cd my_app
  4. Change Gemfile

    For Rails 3.0 or below, change the Gemfile with
    gem 'mysql2', '~>0.2.7'

    When the following error happens in later stage

    WARNING: This version of mysql2 (0.3.2) doesn't ship with the ActiveRecord adapter bundled anymore as it's now part of Rails 3.1
    WARNING: Please use the 0.2.x releases if you plan on using it in Rails <= 3.0.x
    rake aborted!
    Please install the mysql2 adapter: `gem install activerecord-mysql2-adapter` (no such file to load -- active_record/connection_adapters/mysql2_adapter)

    Make sure mysql 0.2.x is installed

    sudo gem install --version  '~>0.2.7' mysql2 -- \
    --with-mysql-include=/usr/bin/mysql \
    --with-mysql-lib=/usr/lib64/mysql
  5. Install Gem
    % bundle install
  6. Change Rails' MySQL configuration for development and testing environment
    ~/my_system/my_app/config/database.yml
    development:
      adapter: mysql2
      encoding: utf8
      reconnect: false
      database: my_app_development
      pool: 5
      username: root
      password: your_secret
      socket: /tmp/mysql.sock
    
    test:
      adapter: mysql2
      encoding: utf8
      reconnect: false
      database: my_app_test
      pool: 5
      username: root
      password: your_secret
      socket: /tmp/mysql.sock
  7. Create the development DB using rake

    Skip this step if the DB already exists. Otherwise, the existing DB will be erased
    rake db:create
    
    Same as:
    rake db:create RAILS_ENV=development

    By default, the rake command assumes the development environment

    • Rake creates both the development & test DB in running rake db:create even only the development environment is specify
      /usr/local/lib/ruby/gems/1.8/gems/activerecord-3.0.4/lib/active_record/railties/databases.rake
      if Rails.env.development? && ActiveRecord::Base.configurations['test']
            create_database(ActiveRecord::Base.configurations['test'])
          end
  8. Start Rails
    % rails server
    => Booting WEBrick
    => Rails 3.0.4 application starting in development on http://0.0.0.0:3000
    => Call with -d to detach
    => Ctrl-C to shutdown server
    [2011-01-05 17:03:10] INFO  WEBrick 1.3.1
    [2011-01-05 17:03:10] INFO  ruby 1.8.7 (2010-12-23) [i686-darwin10.6.0]
    [2011-01-05 17:03:11] INFO  WEBrick::HTTPServer#start: pid=3539 port=3000
    • To use Mongrel as the server
      sudo gem install mongrel
      rails server mongrel
  9. Verify: Open a browser with the following URL and you will see a welcome window
    http://localhost:3000/
  10. To shutdown Rails server, ctl-c

Run Rails or Rake for Different Environment

rails & rake can be started in development, test or production mode which each provides different level of logging and automatic code refreshing capabilities

For rails, development mode allows automatic code refreshes when source code is changed

  • To start in development mode (default)
    % rails server
  • To start in testing & production mode
    % rails server -e test
    % rails server -e production

To define environment other than development (default) in rake

rake db:create RAILS_ENV=production

Create a new Controller as the Rails Application Home page

  1. Create a Rails controller for the home page
    rails generate controller home index
  2. Edit the HTML content and change it as needed
    mate app/views/home/index.html.erb
  3. Remove the original home page
    rm public/index.html
  4. Change the home page routing for Rails
    mate config/routes.rb

    Uncomment and change it to

    root :to => "home#index"
  • Without removing the static home page, Rails treats index.html as the cached HTML file for "home#index"
  • No Rails server reboot is needed if it starts in development mode

Scaffolding: Create Ruby on Rails MVC Components

Scaffolding auto-generates MVC components to list, view, create, delete & edit a model object

Create MVC components using Rails scaffolding

% cd ~/my_system/my_app
% rails generate scaffold Account user_name:string description:text premium:boolean \
          income:integer ranking:float fee:decimal birthday:date login_time:time

"user_name:string ... login_time:time" are the DB columns for the model Account
Output
invoke  active_record
      create    db/migrate/20110222020736_create_accounts.rb
      create    app/models/account.rb
      invoke    test_unit
      create      test/unit/account_test.rb
      create      test/fixtures/accounts.yml
       route  resources :accounts
      invoke  scaffold_controller
      create    app/controllers/accounts_controller.rb
      invoke    erb
      create      app/views/accounts
      create      app/views/accounts/index.html.erb
      create      app/views/accounts/edit.html.erb
      create      app/views/accounts/show.html.erb
      create      app/views/accounts/new.html.erb
      create      app/views/accounts/_form.html.erb
      invoke    test_unit
      create      test/functional/accounts_controller_test.rb
      invoke    helper
      create      app/helpers/accounts_helper.rb
      invoke      test_unit
      create        test/unit/helpers/accounts_helper_test.rb
      invoke  stylesheets
      create    public/stylesheets/scaffold.css

Create DB table using Rails rake & Migration Files

Create the DB schema with rake (table "accounts")

% rake db:migrate
==  CreateAccounts: migrating =================================================
-- create_table(:accounts)
   -> 0.0121s
==  CreateAccounts: migrated (0.0122s) ========================================

Rails automatically lower case the name (Account) and use the plural form (accounts) as the DB table

If failed, trace the problems with

% rake --trace db:migrate

rake db:migrate reads the migration files under db/migrate

  • Migration files are Ruby classes designed for developers/Rails to create/modify/delete database tables
    db/migrate/20110222020736_create_accounts.rb
    class CreateAccounts < ActiveRecord::Migration
      def self.up
        create_table :accounts do |t|
          t.string :user_name
          t.text :description
          t.boolean :premium
          t.integer :income
          t.float :ranking
          t.decimal :fee
          t.date :birthday
          t.time :login_time
    
          t.timestamps
        end
      end
    
      def self.down
        drop_table :accounts
      end
    end

Make changes to the Rails View

app/views/accounts/index.html.erb
<h1>Account page</h1>
<table>
<% @accounts.each do |account| %>
  <tr>
    <td><%= account.name %></td>
    <td><%= link_to 'Show', account %></td>
    <td><%= link_to 'Edit', edit_account_path(account) %></td>
    <td><%= link_to 'Destroy', account, :confirm => 'Are you sure?', :method => :delete %></td>
  </tr>
<% end %>
</table>
  • link_to: a Rails view helper method for creating a HTML link
  • accounts_path represents the account listing page URL

The Rails view files are located under

~/my_sys/my_app/app/views/accounts

Adding validation logic to the Rails Data Model

vi app/models/account.rb

Adding declarative data validation logic

class Account < ActiveRecord::Base
  validates :user_name,  :presence => true
  validates :description, :presence => true, :length => { :minimum => 10 }
end
  • user_name and description are mandatory fields
  • description must be 10 characters or more

Rails Controller and View Rendering Code Walk Through

Listing all rows in a Rails model

Rails controller generated by Scaffolding

"app/controllers/accounts_controller.rb"
class AccountsController < ApplicationController
  # GET /accounts
  # GET /accounts.xml
  def index
    @accounts = Account.all

    respond_to do |format|
      format.html # index.html.erb
      format.xml  { render :xml => @accounts }
    end
  end
  • index is the method retrieving all listings
  • @accounts (an instance variable) stores all the accounts by calling the model Account.all
  • format.html render the page in HTML in handling
    http://localhost:3000/accounts
  • format.xml render the page in XML in handling XML request
    http://localhost:3000/accounts.xml

Rendering the data in a Rails view

"app/views/accounts/index.html.erb"
<h1>Listing accounts</h1>
...

<% @accounts.each do |account| %>
  <tr>
    <td><%= account.user_name %></td>
    <td><%= account.description %></td>
    <td><%= account.premium %></td>
...
  • @accounts accesses the instance variable set previously in the controller AccountsController
  • By default, all fields (account.user_name) are HTML escaped

Create URL links in a Rails view

"app/views/accounts/index.html.erb"
<% @accounts.each do |account| %>
<%= link_to 'Show', account %>
<%= link_to 'Edit', edit_account_path(account) %>
<%= link_to 'Destroy', account, :confirm => 'Are you sure?', :method => :delete %>

<%= link_to 'New Account', new_account_path %>
  • edit_account_path and new_account_path are helper method in creating the corresponding URL link

View a Rails Model Data

"show" handles individual data viewing request

# GET /accounts/1
  # GET /accounts/1.xml
  def show
    @account = Account.find(params[:id])

    respond_to do |format|
      format.html # show.html.erb
      format.xml  { render :xml => @account }
    end
  end
  • :id contains the item id that is part of the URL /accounts/345
  • Use Account.find to locate the data

Render by

"app/views/accounts/show.html.erb"
<p id="notice"><%= notice %></p>

<p>
  <b>User name:</b>
  <%= @account.user_name %>
</p>

<p>
  <b>Description:</b>
  <%= @account.description %>
</p>

<p>
  <b>Premium:</b>
  <%= @account.premium %>
</p>

...

<%= link_to 'Edit', edit_account_path(@account) %> |
<%= link_to 'Back', accounts_path %>

Enter & Save a new Rails Model Data

Account controller: Create new Rails model data by instantiates a new empty Account

# GET /accounts/new
  # GET /accounts/new.xml
  def new
    @account = Account.new

    respond_to do |format|
      format.html # new.html.erb
      format.xml  { render :xml => @account }
    end
  end
  • Account.new create a new model to be edited by the view

Rails view for entering data for a new Rails model data

"app/views/accounts/new.html.erb"
<h1>New account</h1>

<%= render 'form' %>

<%= link_to 'Back', accounts_path %>
<%= render 'form' %>

Invokes the view partial

"app/views/accounts/_form.html.erb"
<%= form_for(@account) do |f| %>
  <% if @account.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@account.errors.count, "error") %> prohibited this account from being saved:</h2>

      <ul>
      <% @account.errors.full_messages.each do |msg| %>
        <li><%= msg %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= f.label :user_name %><br />
    <%= f.text_field :user_name %>
  </div>
  <div class="field">
    <%= f.label :description %><br />
    <%= f.text_area :description %>
  </div>
  ...
  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>
  • form_for creates the HTML form
  • f.text_field builds the HTML text input field element

When the "Create Account" button is clicked, it invokes the create method in the controller

# POST /accounts
  # POST /accounts.xml
  def create
    @account = Account.new(params[:account])

    respond_to do |format|
      if @account.save
        format.html { redirect_to(@account, :notice => 'Account was successfully created.') }
        format.xml  { render :xml => @account, :status => :created, :location => @account }
      else
        format.html { render :action => "new" }
        format.xml  { render :xml => @account.errors, :status => :unprocessable_entity }
      end
    end
  end
  • Account.new create new account data with the form parameters
  • @account.save saves the data
  • If success, it redirects to the account show page
  • If fail, it goes back to the new account page
  • :notice is a Rails flash that can carry a message over to the next action such that they can be displayed

Edit & Save a Rails Model Data

Retrive a Rails model data in the controller

# GET /accounts/1/edit
  def edit
    @account = Account.find(params[:id])
  end
  • Account.find locate the model data

Displayed the data and allow a user to make change

"app/views/accounts/edit.html.erb"
<h1>Editing account</h1>

<%= render 'form' %>

<%= link_to 'Show', @account %> |
<%= link_to 'Back', accounts_path %>

When the "Post Account" button is clicked, it invokes the update method within the controller

# PUT /accounts/1
  # PUT /accounts/1.xml
  def update
    @account = Account.find(params[:id])

    respond_to do |format|
      if @account.update_attributes(params[:account])
        format.html { redirect_to(@account, :notice => 'Account was successfully updated.') }
        format.xml  { head :ok }
      else
        format.html { render :action => "edit" }
        format.xml  { render :xml => @account.errors, :status => :unprocessable_entity }
      end
    end
  end
  • @account.update_attributes saves the data

Delete a Rails Model Data

# DELETE /accounts/1
  # DELETE /accounts/1.xml
  def destroy
    @account = Account.find(params[:id])
    @account.destroy

    respond_to do |format|
      format.html { redirect_to(accounts_url) }
      format.xml  { head :ok }
    end
  end
  • @account.destroy removes the data from the DB.
  • It redirects to the account listing pages once it is completed

Testing

  1. Start Rails if not started
    % cd ~/my_system/my_app
    % rails server
  2. Verify: Open a browser with the following URLs
    http://localhost:3000
    
    http://localhost:3000/accounts

    By default, Rails starts in the development mode. Any changes in the application code or route will be reloaded automatically without restarting Rails

  3. Use the application to view/add/edit/delete an account

Create new Rails Model and Controller

Generate a new Rails Model with a Many-to-1 Relationship

% rails generate model Contact title:string phone_number:text account:references

invoke  active_record
create    db/migrate/20110222200620_create_contacts.rb
create    app/models/contact.rb
invoke    test_unit
create      test/unit/contact_test.rb
create      test/fixtures/contacts.yml
  • Model Contact contains 2 columns (title & phone_number)
  • account:references defines a foreign key relationship: A contact references an account.

The Ruby code of Contact model

"app/models/contact.rb"
class Contact < ActiveRecord::Base
  belongs_to :account
end
  • belongs_to defines a foreign relationshop with account

Migration code in building the model's DB table

"db/migrate/20110222200620_create_contacts.rb"
class CreateContacts < ActiveRecord::Migration
  def self.up
    create_table :contacts do |t|
      t.string :title
      t.text :phone_number
      t.references :account

      t.timestamps
    end
  end

  def self.down
    drop_table :contacts
  end
end

To build the DB table

% rake db:migrate
(in ~/apps/my_system/my_app)
==  CreateContacts: migrating =================================================
-- create_table(:contacts)
   -> 0.0372s
==  CreateContacts: migrated (0.0375s) ========================================
Create the Rails One-to-Many Association

The one-to-many association has to be added manually

"app/models/account.rb"
class Account < ActiveRecord::Base
...
   has_many :contacts
end
  • use @account.contacts to access all contacts associate to an account

    For Multiple words
    has_many :vendor_contacts
Add a Rails Route
"config/routes.rb"
resources :accounts do
    resources :contacts
  end
  • It creates contacts as a nested resources of accounts

    For multiple words
    resources :vendor_contacts

Create the Rails Controller

% rails generate controller Contacts
   identical  app/controllers/contacts_controller.rb
      invoke  erb
       exist    app/views/contacts
      invoke  test_unit
   identical    test/functional/contacts_controller_test.rb
      invoke  helper
   identical    app/helpers/contacts_helper.rb
      invoke    test_unit
   identical      test/unit/helpers/contacts_helper_test.rb
% rails generate controller VendorContacts

Add code to the Rails controller

"app/controllers/contacts_controller.rb"
class ContactsController < ApplicationController
  def create
    @account = Account.find(params[:account_id])
    contact = @account.contacts.create(params[:contact])
    redirect_to account_path(@account)
  end
end
  • Retrive the parent object by account_id
  • Create a new contact with the HTML form data
  • Redirect to the contact detail page
    @contact = @account.vendor_contacts.create(params[:vendor_contact])

Creating the view in the Parent View (Account)

Viewing children in the parent view

"app/views/accounts/show.html.erb"
<p>Contact</p>
<% @account.contacts.each do |contact| %>
  <p>
    <b>Title:</b>
    <%= contact.title %>
  </p>

  <p>
    <b>Phone number:</b>
    <%= contact.phone_number %>
  </p>
<% end %>

Create a new children in the parent view

"app/views/accounts/show.html.erb"
<p>Add a contact:</p>
<%= form_for([@account, @account.contacts.build]) do |f| %>
  <div class="field">
    <%= f.label :title %><br />
    <%= f.text_field :title %>
  </div>
  <div class="field">
    <%= f.label :phone_number %><br />
    <%= f.text_area :phone_number %>
  </div>
  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

To provide a separate detail View for Contact

Contacts Controller

def show
    @account = Account.find(params[:account_id])
    @contact = Contact.find(params[:id])

    respond_to do |format|
      format.html # show.html.erb
      format.xml  { render :xml => @listing }
    end
  end

URL link

<td><%= link_to 'Details', [@account, @contact] %></td>

Delete a Child Programmatically

Create a link to delete a child

<%= link_to 'Delete', [@account, contact],
	               :confirm => 'Delete contact?',
	               :method => :delete %>

Delete a child in a controller

class ContactsController < ApplicationController
  def destroy
    @account = Account.find(params[:account_id])
    contact = @account.contacts.find(params[:id])
    contact.destroy
    redirect_to account_path(@account)
  end
end

Delete all children with Cascade Delete (Automatically)

Delete all associated children when the parent Rails model is deleted

"app/models/account.rb"
class Account < ActiveRecord::Base
  ...
  has_many :contacts, :dependent => :destroy
end

Rails application wide view layout

"app/views/layouts/application.html.erb"
<!DOCTYPE html>
<html>
<head>
  <title>LmsParser</title>
  <%= stylesheet_link_tag :all %>
  <%= javascript_include_tag :defaults %>
  <%= csrf_meta_tag %>
</head>
<body>

<%= yield %>

</body>
</html>

Rails Help Method

Helper method

"app/helpers/accounts_helper.rb"
module AccountsHelper
  def some_method(account)
    ...
  end
end

Using a helper method in a view

<%= some_method(@data) %>

Make CSS changes in Ruby on Rails

To have different table background color for odd and even rows in the listing page

app/views/feeds/index.html.erb
<% @accounts.each do |account| %>
  <tr class="<%= cycle("odd", "even") %>">
    <td><%= account.name %></td>
    ...
  </tr>
<% end %>
  • Add a CSS class called odd/even for every other table row
    • cycle alternative returns different values in the parameters

Edit the application wide CSS

public/stylesheets/scaffold.css
table {
font-family:Helvetica, sans-serif, Arial;
border-collapse:collapse;
}

td, th {
font-size:1em;
border:1px solid #99bb22;
padding:3px 7px 3px 7px;
}

th {
font-size:1.2em;
text-align:left;
padding-top:5px;
padding-bottom:5px;
background-color:#aacc44;
color:#ffffff;
}

tr.even td {
color:#000000;
background-color:#eeffdd;
}

To add a new CSS file in Ruby on Rails

  • Drop the new CSS files in public/stylesheets
  • By default, the Ruby on Rails' application layout file load all the CSS file under public/stylesheets
    app/views/layouts/application.html.erb
    <%= stylesheet_link_tag :all %>

Set/Change Rails Application Timezone

To set the default application timezone

config/application.rb
# Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
   # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
   config.time_zone = 'Pacific Time (US & Canada)'
  • Rails Active Record will use the new time zone in displaying time. By default, it will be displayed as UTC

To display the last updated time of the data

app/views/accounts/index.html.erb
<td><%= feed.updated_at.strftime("%Y-%m-%d %I:%M %p") %></td>

Undo & Rollback Rails Coding

To undo the changes if mistakes are made:

  • Rollback DB changes
    % rake db:rollback
    Output
    =  CreateIssues: reverting ===================================================
    -- drop_table(:issues)
       -> 0.0014s
    ==  CreateIssues: reverted (0.0015s) ==========================================
  • Rollback the scaffolding

    "db:rollback" requires DB migration code generated by the scaffolding. Always rollback the DB first before destroying "Account"
    % rails destroy scaffold Account
          invoke  active_record
          remove    db/migrate/20110223004802_create_topics.rb
          remove    app/models/topic.rb
          invoke    test_unit
          remove      test/unit/topic_test.rb
          remove      test/fixtures/topics.yml
           route  resources :topics
          invoke  scaffold_controller
          remove    app/controllers/topics_controller.rb
          invoke    erb
          remove      app/views/topics
          remove      app/views/topics/index.html.erb
          remove      app/views/topics/edit.html.erb
          remove      app/views/topics/show.html.erb
          remove      app/views/topics/new.html.erb
          remove      app/views/topics/_form.html.erb
          invoke    test_unit
          remove      test/functional/topics_controller_test.rb
          invoke    helper
          remove      app/helpers/topics_helper.rb
          invoke      test_unit
          remove        test/unit/helpers/topics_helper_test.rb
          invoke  stylesheets
  • Other Rails destroy options
    % rails destroy scaffold Account
    % rails destroy controller Home
    % rails destroy model Vendor