Words

REST

  • If you read much about Ruby on Rails web development, you’ll see a lot of references to “REST”, which is an acronym for REpresentational State Transfer. REST is an architectural style for developing distributed, networked systems and software applications such as the World Wide Web and web applications. Although REST theory is rather abstract, in the context of Rails applications REST means that most application components (such as users and microposts) are modeled as resources that can be created, read, updated, and deleted—operations that correspond both to the CRUD operations of relational databases and to the four fundamental HTTP request methods: POST, GET, PATCH, and DELETE.

GET, POST

  • The hypertext transfer protocol (HTTP) defines the basic operations GET, POST, PATCH, and DELETE. These refer to operations between a client computer (typically running a web browser such as Chrome, Firefox, or Safari) and a server (typically running a web server such as Apache or Nginx). (It’s important to understand that, when developing Rails applications on a local computer, the client and server are the same physical machine, but in general they are different.) An emphasis on HTTP verbs is typical of web frameworks (including Rails) influenced by the REST architecture.
  • GET is the most common HTTP operation, used for reading data on the web; it just means “get a page”, and every time you visit a site like http://www.google.com/ or http://www.wikipedia.org/ your browser is submitting a GET request.
  • POST is the next most common operation; it is the request sent by your browser when you submit a form. In Rails applications, POST requests are typically used for creating things (although HTTP also allows POST to perform updates). For example, the POST request sent when you submit a registration form creates a new user on the remote site.
  • The other two verbs, PATCH and DELETE, are designed for updating and destroying things on the remote server. These requests are less common than GET and POST since browsers are incapable of sending them natively, but some web frameworks (including Ruby on Rails) have clever ways of making it seem like browsers are issuing such requests.
  • As a result, Rails supports all four of the request types GET, POST, PATCH, and DELETE.

Hashes and symbols

  • string as hash keys
>> user = { "first_name" => "Michael", "last_name" => "Hartl" }
=> {"last_name"=>"Hartl", "first_name"=>"Michael"}
  • symbols as hash keys
  • hyphens (-) can’t be used in symbols.
>> user = { :name => "Michael Hartl", :email => "michael@example.com" }
=> {:name=>"Michael Hartl", :email=>"michael@example.com"}
>> user[:name]              # Access the value corresponding to :name.
=> "Michael Hartl"
>> user[:password]          # Access the value of an undefined key.
=> nil
{ :name => "Michael Hartl" } and { name: "Michael Hartl" } are equivalent

Nested Hash

>> params = {}        # Define a hash called 'params' (short for 'parameters').
=> {}
>> params[:user] = { name: "Michael Hartl", email: "mhartl@example.com" }
=> {:name=>"Michael Hartl", :email=>"mhartl@example.com"}
>> params
=> {:user=>{:name=>"Michael Hartl", :email=>"mhartl@example.com"}}
>>  params[:user][:email]
=> "mhartl@example.com"

Inspect Method

>> flash = { success: "It worked!", danger: "It failed." }
=> {:success=>"It worked!", :danger=>"It failed."}
>> flash.each do |key, value|
?>   puts "Key #{key.inspect} has value #{value.inspect}"
>> end
Key :success has value "It worked!"
Key :danger has value "It failed."
  • inspect method, which returns a string with a literal representation of the object it’s called on
>> puts (1..5).to_a            # Put an array as a string.
1
2
3
4
5
>> puts (1..5).to_a.inspect    # Put a literal array.
[1, 2, 3, 4, 5]
>> puts :name, :name.inspect
name
:name
>> puts "It worked!", "It worked!".inspect
It worked!
"It worked!"
  • By the way, using inspect to print an object is common enough that there’s a shortcut for it, the p function:
>> p :name             # Same output as 'puts :name.inspect'
:name

Controller Class

>> controller = StaticPagesController.new
=> #<StaticPagesController:0x22855d0>
>> controller.class
=> StaticPagesController
>> controller.class.superclass
=> ApplicationController
>> controller.class.superclass.superclass
=> ActionController::Base
>> controller.class.superclass.superclass.superclass
=> ActionController::Metal
>> controller.class.superclass.superclass.superclass.superclass
=> AbstractController::Base
>> controller.class.superclass.superclass.superclass.superclass.superclass
=> Object

attr_accessor

class Foo
  attr_accessor :bar
end
  • creates “getter” and “setter” methods that allow us to retrieve (get) and assign (set) @bar instance variables,
  • Instance variables always begin with an @ sign, and are nil when undefined.

  • equivalent to

class Foo
  def bar
    @bar
  end
  def bar=( new_value )
    @bar = new_value
  end
end

initialize method

class User
  attr_accessor :name, :email

  def initialize(attributes = {})
    @name  = attributes[:name]
    @email = attributes[:email]
  end

  def formatted_email
    "#{@name} <#{@email}>"
  end
end
  • initialize, is special in Ruby: it’s the method called when we execute User.new. This particular initialize takes one argument, attributes.

Rails routes

root 'static_pages#home'
  • root method arranges for the root path / to be routed to a controller and action of our choice.
  • Defining the root route in this way has a second important effect, which is to create named routes that allow us to refer to routes by a name rather than by the raw URL.

  • In this case, these routes are root_path and root_url, with the only difference being that the latter includes the full URL:

root_path -> '/'
root_url  -> 'http://www.example.com/'

Database indices

  • When creating a column in a database, it is important to consider whether we will need to find records by that column.

  • Consider, for example, the email attribute created by the migration. When we allow users to log in to the sample app, we will need to find the user record corresponding to the submitted email address.

  • Unfortunately, based on the naïve data model, the only way to find a user by email address is to look through each user row in the database and compare its email attribute to the given email—which means we might have to examine every row (since the user could be the last one in the database). This is known in the database business as a full-table scan, and for a real site with thousands of users it is a Bad Thing.

  • Putting an indexon the email column fixes the problem. To understand a database index, it’s helpful to consider the analogy of a book index. In a book, to find all the occurrences of a given string, say “foobar”, you would have to scan each page for “foobar”—the paper version of a full-table scan. With a book index, on the other hand, you can just look up “foobar” in the index to see all the pages containing “foobar”. A database index works essentially the same way.

Rails environments

  • Rails comes equipped with three environments:
    • test
    • development
    • production
  • The default environment for the Rails console is development

      $ rqails console
      Loading development environment
      >> Rails.env
      => "development"
      >> Rails.env.development?
      => true
      >> Rails.env.test?
      => false
  • If you ever need to run a console in a different environment (to debug a test, for example), you can pass the environment as a parameter to the console script:

$ rails console test
  Loading test environment
  >> Rails.env
  => "test"
  >> Rails.env.test?
  => true
  • As with the console, development is the default environment for the Rails server, but you can also run it in a different environment:
 $ rails server --environment production
  • If you view your app running in production, it won’t work without a production database, which we can create by running rake db:migrate in production:
$ bundle exec rake db:migrate RAILS_ENV=production
  • By the way, if you have deployed your sample app to Heroku, you can see its environment using heroku run console:
$ heroku run console
  >> Rails.env
  => "production"
  >> Rails.env.production?
  => true
  • Naturally, since Heroku is a platform for production sites, it runs each application in a production environment.

REST architecture favored in Rails applications

  • means representing data as resources that can be created, shown, updated, or destroyed—four actions corresponding to the four fundamental operations POST, GET, PATCH, and DELETE defined by the HTTP standard.
  • When following REST principles, resources are typically referenced using the resource name and a unique identifier. What this means in the context of users—which we’re now thinking of as a Users resource — is that we should view the user with id 1 by issuing a GET request to the URL /users/1.
  • Here the show action is implicit in the type of request — when Rails’ REST features are activated, GET requests are automatically handled by the show action.
  • the page for a user with id 1 has URL /users/1. Unfortunately, visiting that URL right now just gives an error, as seen in the server log

alt text

  • get the routing for /users/1 to work by adding a single line to our routes file config/routes.rb
resources :users

Command

Skip the installation of production gems

$ bundle install --without production

Undoing things

Controller

$ rails generate controller StaticPages home help
$ rails destroy  controller StaticPages home help

Model

$ rails generate model User name:string email:string
$ rails destroy model User

Migrations

$ bundle exec rake db:migrate
#undo a single migration step using
$ bundle exec rake db:rollback
#go all the way back to the beginning
$ bundle exec rake db:migrate VERSION=0

Short cuts

Full command Shortcut
rails server rails s
rails console rails c
rails generate rails g
bundle install bundle
rake test rake

Process

Generated static pages

Create Controller

$ rails generate controller StaticPages home help
      create  app/controllers/static_pages_controller.rb
       route  get 'static_pages/help'
       route  get 'static_pages/home'
      invoke  erb
      create    app/views/static_pages
      create    app/views/static_pages/home.html.erb
      create    app/views/static_pages/help.html.erb
      invoke  test_unit
      create    test/controllers/static_pages_controller_test.rb
      invoke  helper
      create    app/helpers/static_pages_helper.rb
      invoke    test_unit
      create      test/helpers/static_pages_helper_test.rb
      invoke  assets
      invoke    coffee
      create      app/assets/javascripts/static_pages.js.coffee
      invoke    scss
      create      app/assets/stylesheets/static_pages.css.scss

Controller

app/controllers/static_pages_controller.rb

 class StaticPagesController < ApplicationController

  def home
  end

  def help
  end
end
  • StaticPagesController inherits from the Rails class ApplicationController
  • when visiting the URL /static_pages/home, Rails looks in the Static Pages controller and executes the code in the home action, and then renders the view (the V in MVC) corresponding to the action.
  • the home action is empty, so all visiting /static_pages/home does is render the view.
  • The generated view for the Home page : app/views/static_pages/home.html.erb
  • The generated view for the Help page : app/views/static_pages/help.html.erb

Route

config/routes.rb

Rails.application.routes.draw do
  get 'static_pages/home'
  get 'static_pages/help'
end
  • get 'static_pages/home'
    • maps requests for the URL /static_pages/home to the home action in the Static Pages controller.
  • when we generate a home action inside the Static Pages controller we automatically get a page at the address /static_pages/home

Add About Page

Add the about route

Rails.application.routes.draw do
  get 'static_pages/home'
  get 'static_pages/help'
  get 'static_pages/about'
end

Add about action

app/controllers/static_pages_controller.rb

class StaticPagesController < ApplicationController

  def home
  end

  def help
  end

  def about
  end
end

Add about.html.erb

  • in Terminal
touch app/views/static_pages/about.html.erb

Layouts Refactor

Change application.html.erb

app/views/layouts/application.html.erb

<!DOCTYPE html>
<html>
  <head>
    <title><%= yield(:title) %> | Ruby on Rails Tutorial Sample App</title>
    <%= stylesheet_link_tag    'application', media: 'all',
                                              'data-turbolinks-track' => true %>
    <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
    <%= csrf_meta_tags %>
  </head>
  <body>
    <%= yield %>
  </body>
</html>

Change home.html.erb

app/views/static_pages/home.html.erb

<% provide(:title, "Home") %>
<h1>Sample App</h1>
<p>
  This is the home page for the
  <a href="http://www.railstutorial.org/">Ruby on Rails Tutorial</a>
  sample application.
</p>

Change help.html.erb

app/views/static_pages/help.html.erb

<% provide(:title, "Help") %>
<h1>Help</h1>
<p>
  Get help on the Ruby on Rails Tutorial at the
  <a href="http://www.railstutorial.org/#help">Rails Tutorial help section</a>.
  To get help on this sample app, see the
  <a href="http://www.railstutorial.org/book"><em>Ruby on Rails Tutorial</em>
  book</a>.
</p>

Change about.html.erb

app/views/static_pages/about.html.erb

<% provide(:title, "About") %>
<h1>About</h1>
<p>
  The <a href="http://www.railstutorial.org/"><em>Ruby on Rails
  Tutorial</em></a> is a
  <a href="http://www.railstutorial.org/book">book</a> and
  <a href="http://screencasts.railstutorial.org/">screencast series</a>
  to teach web development with
  <a href="http://rubyonrails.org/">Ruby on Rails</a>.
  This is the sample application for the tutorial.
</p>

Setting the root Route

config/routes.rb

Rails.application.routes.draw do
  root 'static_pages#home'
  get  'static_pages/help'
  get  'static_pages/about'
end
  • changes the URL static_pages/home to the controller/action pair static_pages#home, which ensures that GET requests for / get routed to the home action in the Static Pages controller.

Add helper to application.html.erb

Modify application_helper.rb

app/helpers/application_helper.rb

module ApplicationHelper

  # Returns the full title on a per-page basis.
  def full_title(page_title = '')
    base_title = "Ruby on Rails Tutorial Sample App"
    if page_title.empty?
      base_title
    else
      page_title + " | " + base_title
    end
  end
end

Modify application.html.erb

app/views/layouts/application.html.erb

<!DOCTYPE html>
<html>
  <head>
    <title><%= full_title(yield(:title)) %></title>
    <%= stylesheet_link_tag    'application', media: 'all',
                                              'data-turbolinks-track' => true %>
    <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
    <%= csrf_meta_tags %>
  </head>
  <body>
    <%= yield %>
  </body>
</html>

Filling in the layout

add Bootstrap

  • Adding the bootstrap-sass gem to the Gemfile
source 'https://rubygems.org'

gem 'rails',                '4.2.2'
gem 'bootstrap-sass',       '3.2.0.0'
  • To install Bootstrap, we run bundle install as usual.
  • the Bootstrap framework natively uses the Less CSS language for making dynamic stylesheets, but the Rails asset pipeline supports the (very similar) Sass language by default, so bootstrap-sass converts Less to Sass and makes all the necessary Bootstrap files available to the current application.

put all of the CSS needed in a single file

$ touch app/assets/stylesheets/custom.css.scss
  • Inside the file app/assets/stylesheets/custom.css.scss for the custom CSS, we can use the @import function to include Bootstrap
@import "bootstrap-sprockets";
@import "bootstrap";

Adding CSS for some universal styling applying to all pages.

app/assets/stylesheets/custom.css.scss

/* universal */

body {
  padding-top: 60px;
}

section {
  overflow: auto;
}

textarea {
  resize: vertical;
}

.center {
  text-align: center;
}

.center h1 {
  margin-bottom: 10px;
}

Adding CSS for nice typography

@import "bootstrap-sprockets";
@import "bootstrap";
.
.
.
/* typography */

h1, h2, h3, h4, h5, h6 {
  line-height: 1;
}

h1 {
  font-size: 3em;
  letter-spacing: -2px;
  margin-bottom: 30px;
  text-align: center;
}

h2 {
  font-size: 1.2em;
  letter-spacing: -1px;
  margin-bottom: 30px;
  text-align: center;
  font-weight: normal;
  color: #777;
}

p {
  font-size: 1.1em;
  line-height: 1.7em;
}

Partials

A partial for the HTML shim.

app/views/layouts/_shim.html.erb

<!--[if lt IE 9]>
  <script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/r29/html5.min.js">
  </script>
<![endif]-->

A partial for the site header.

app/views/layouts/_header.html.erb

<header class="navbar navbar-fixed-top navbar-inverse">
  <div class="container">
    <%= link_to "sample app", '#', id: "logo" %>
    <nav>
      <ul class="nav navbar-nav navbar-right">
        <li><%= link_to "Home",   '#' %></li>
        <li><%= link_to "Help",   '#' %></li>
        <li><%= link_to "Log in", '#' %></li>
      </ul>
    </nav>
  </div>
</header>

A partial for the site footer.

app/views/layouts/_footer.html.erb

<footer class="footer">
  <small>
    The <a href="http://www.railstutorial.org/">Ruby on Rails Tutorial</a>
    by <a href="http://www.michaelhartl.com/">Michael Hartl</a>
  </small>
  <nav>
    <ul>
      <li><%= link_to "About",   '#' %></li>
      <li><%= link_to "Contact", '#' %></li>
      <li><a href="http://news.railstutorial.org/">News</a></li>
    </ul>
  </nav>
</footer>

Adding the CSS for the site footer.

app/assets/stylesheets/custom.css.scss

.
.
.
/* footer */

footer {
  margin-top: 45px;
  padding-top: 5px;
  border-top: 1px solid #eaeaea;
  color: #777;
}

footer a {
  color: #555;
}

footer a:hover {
  color: #222;
}

footer small {
  float: left;
}

footer ul {
  float: right;
  list-style: none;
}

footer ul li {
  float: left;
  margin-left: 15px;
}

Reform Site Layout

app/views/layouts/application.html.erb

<!DOCTYPE html>
<html>
  <head>
    <title><%= full_title(yield(:title)) %></title>
    <%= stylesheet_link_tag "application", media: "all",
                                           "data-turbolinks-track" => true %>
    <%= javascript_include_tag "application", "data-turbolinks-track" => true %>
    <%= csrf_meta_tags %>
    <%= render 'layouts/shim' %>
  </head>
  <body>
    <%= render 'layouts/header' %>
    <div class="container">
      <%= yield %>
      <%= render 'layouts/footer' %>
    </div>
  </body>
</html>

The initial SCSS file converted to use nesting and variables

app/assets/stylesheets/custom.css.scss

@import "bootstrap-sprockets";
@import "bootstrap";

/* mixins, variables, etc. */

$gray-medium-light: #eaeaea;

/* universal */

body {
  padding-top: 60px;
}

section {
  overflow: auto;
}

textarea {
  resize: vertical;
}

.center {
  text-align: center;
  h1 {
    margin-bottom: 10px;
  }
}

/* typography */

h1, h2, h3, h4, h5, h6 {
  line-height: 1;
}

h1 {
  font-size: 3em;
  letter-spacing: -2px;
  margin-bottom: 30px;
  text-align: center;
}

h2 {
  font-size: 1.2em;
  letter-spacing: -1px;
  margin-bottom: 30px;
  text-align: center;
  font-weight: normal;
  color: $gray-light;
}

p {
  font-size: 1.1em;
  line-height: 1.7em;
}


/* header */

#logo {
  float: left;
  margin-right: 10px;
  font-size: 1.7em;
  color: white;
  text-transform: uppercase;
  letter-spacing: -1px;
  padding-top: 9px;
  font-weight: bold;
  &:hover {
    color: white;
    text-decoration: none;
  }
}

/* footer */

footer {
  margin-top: 45px;
  padding-top: 5px;
  border-top: 1px solid $gray-medium-light;
  color: $gray-light;
  a {
    color: $gray;
    &:hover {
      color: $gray-darker;
    }
  }
  small {
    float: left;
  }
  ul {
    float: right;
    list-style: none;
    li {
      float: left;
      margin-left: 15px;
    }
  }
}

Add Contact Page

Adding a route for the Contact page

config/routes.rb

Rails.application.routes.draw do
  root 'static_pages#home'
  get  'static_pages/help'
  get  'static_pages/about'
  get  'static_pages/contact'
end

Adding an action for the Contact page.

app/controllers/static_pages_controller.rb

class StaticPagesController < ApplicationController
  .
  .
  .
  def contact
  end
end

The view for the Contact page

app/views/static_pages/contact.html.erb

<% provide(:title, 'Contact') %>
<h1>Contact</h1>
<p>
  Contact the Ruby on Rails Tutorial about the sample app at the
  <a href="http://www.railstutorial.org/#contact">contact page</a>.
</p>

Rewite Rails routes by named routes

config/routes.rb

Rails.application.routes.draw do
  root             'static_pages#home'
  get 'help'    => 'static_pages#help'
  get 'about'   => 'static_pages#about'
  get 'contact' => 'static_pages#contact'
end
  • To define the named routes for the Help, About, and Contact pages, we need to make changes to the get rules

  • The second of these patterns routes a GET request for the URL /help to the help action in the Static Pages controller, so that we can use the URL /help in place of the more verbose /static_pages/help.

Using named routes

User Signup

Generating a Users controller (with a new action)

$ rails generate controller Users new
      create  app/controllers/users_controller.rb
       route  get 'users/new'
      invoke  erb
      create    app/views/users
      create    app/views/users/new.html.erb
      invoke  test_unit
      create    test/controllers/users_controller_test.rb
      invoke  helper
      create    app/helpers/users_helper.rb
      invoke    test_unit
      create      test/helpers/users_helper_test.rb
      invoke  assets
      invoke    coffee
      create      app/assets/javascripts/users.js.coffee
      invoke    scss
      create      app/assets/stylesheets/users.css.scss

Signup URL

add route for the signup page

config/routes.rb

Rails.application.routes.draw do
  root             'static_pages#home'
  get 'help'    => 'static_pages#help'
  get 'about'   => 'static_pages#about'
  get 'contact' => 'static_pages#contact'
  get 'signup'  => 'users#new'
end

Linking the button to the signup page

app/views/static_pages/home.html.erb

<div class="center jumbotron">
  <h1>Welcome to the Sample App</h1>

  <h2>
    This is the home page for the
    <a href="http://www.railstutorial.org/">Ruby on Rails Tutorial</a>
    sample application.
  </h2>

  <%= link_to "Sign up now!", signup_path, class: "btn btn-lg btn-primary" %>
</div>

<%= link_to image_tag("rails.png", alt: "Rails logo"),
            'http://rubyonrails.org/' %>

add the initial (stub) signup page

<% provide(:title, 'Sign up') %>
<h1>Sign up</h1>
<p>This will be a signup page for new users.</p>

User model

Schema

alt text

Generating a User model

$ rails generate model User name:string email:string
      invoke  active_record
      create    db/migrate/20140724010738_create_users.rb
      create    app/models/user.rb
      invoke    test_unit
      create      test/models/user_test.rb
      create      test/fixtures/users.yml

Migration for the User model (to create a users table)

db/migrate/[timestamp]_create_users.rb

class CreateUsers < ActiveRecord::Migration
  def change
    create_table :users do |t|
      t.string :name
      t.string :email

      t.timestamps null: false
    end
  end
end
  • The migration itself consists of a change method that determines the change to be made to the database.
  • In the above case, change uses a Rails method called create_table to create a table in the database for storing users.
  • The create_table method accepts a block with one block variable, in this case called t (for “table”).
  • Inside the block, the create_table method uses the t object to create name and email columns in the database, both of type string.
  • Here the table name is plural (users) even though the model name is singular (User), which reflects a linguistic convention followed by Rails: a model represents a single user, whereas a database table consists of many users.
  • The final line in the block, t.timestamps null: false, is a special command that creates two magic columns called created_at and updated_at, which are timestamps that automatically record when a given user is created and updated.
  • The full data model represented by the migration shown below

alt text

Run the migration

  • “migrating up”
$ bundle exec rake db:migrate
  • “migrate down”
$ bundle exec rake db:rollback

Creating user objects

app/models/user.rb

The brand new User model

class User < ActiveRecord::Base
end

rails console in sandbox

  • Since we don’t (yet) want to make any changes to our database, we’ll start the console in a sandbox
$ rails console --sandbox
Loading development environment in sandbox
Any modifications you make will be rolled back on exit
>>

make a new user object

>> User.new
=> #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil>
  • we designed the example User class to take an initialization hash to set the object attributes;
class User
  attr_accessor :name, :email

  def initialize(attributes = {})
    @name  = attributes[:name]
    @email = attributes[:email]
  end
end
  • that design choice was motivated by Active Record, which allows objects to be initialized in the same way:
>> user = User.new(name: "Michael Hartl", email: "mhartl@example.com")
=> #<User id: nil, name: "Michael Hartl", email: "mhartl@example.com",
created_at: nil, updated_at: nil>

test whether user object is valid

>> user.valid?
true

save user object

  • In order to save the User object to the database, we need to call the save method on the user variable:
>> user.save
   (0.2ms)  begin transaction
  User Exists (0.2ms)  SELECT  1 AS one FROM "users"  WHERE LOWER("users".
  "email") = LOWER('mhartl@example.com') LIMIT 1
  SQL (0.5ms)  INSERT INTO "users" ("created_at", "email", "name", "updated_at)
   VALUES (?, ?, ?, ?)  [["created_at", "2014-09-11 14:32:14.199519"],
   ["email", "mhartl@example.com"], ["name", "Michael Hartl"], ["updated_at",
  "2014-09-11 14:32:14.199519"]]
   (0.9ms)  commit transaction
=> true
  • The save method returns true if it succeeds and false otherwise
>> user
=> #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com",
created_at: "2014-07-24 00:57:46", updated_at: "2014-07-24 00:57:46">
  • the id has been assigned a value of 1
  • the magic columns have been assigned the current time and date

Combine make and save

  • Active Record also lets you combine them (user.new and user.save)into one step with User.create:
>> User.create(name: "A Nother", email: "another@example.org")
#<User id: 2, name: "A Nother", email: "another@example.org", created_at:
"2014-07-24 01:05:24", updated_at: "2014-07-24 01:05:24">
>> foo = User.create(name: "Foo", email: "foo@bar.com")
#<User id: 3, name: "Foo", email: "foo@bar.com", created_at: "2014-07-24
01:05:42", updated_at: "2014-07-24 01:05:42">

destroy user object

>> foo.destroy
=> #<User id: 3, name: "Foo", email: "foo@bar.com", created_at: "2014-07-24
01:05:42", updated_at: "2014-07-24 01:05:42">
  • Like create, destroy returns the object
>> foo
=> #<User id: 3, name: "Foo", email: "foo@bar.com", created_at: "2014-07-24
01:05:42", updated_at: "2014-07-24 01:05:42">
  • the destroyed object still exists in memory

Find user object

Find By ID

>> User.find(1)
=> #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com",
created_at: "2014-07-24 00:57:46", updated_at: "2014-07-24 00:57:46">
>> User.find(3)
ActiveRecord::RecordNotFound: Couldn't find User with ID=3

Find users by specific attributes

>> User.find_by(email: "mhartl@example.com")
=> #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com",
created_at: "2014-07-24 00:57:46", updated_at: "2014-07-24 00:57:46"

Find users by index

>> User.first
=> #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com",
created_at: "2014-07-24 00:57:46", updated_at: "2014-07-24 00:57:46">
>> User.all
=> #<ActiveRecord::Relation [#<User id: 1, name: "Michael Hartl",
email: "mhartl@example.com", created_at: "2014-07-24 00:57:46",
updated_at: "2014-07-24 00:57:46">, #<User id: 2, name: "A Nother",
email: "another@example.org", created_at: "2014-07-24 01:05:24",
updated_at: "2014-07-24 01:05:24">]>
  • User.all returns all the users in the database as an object of class ActiveRecord::Relation, which is effectively an array

Update User Object

assign attributes individually

>> user           # Just a reminder about our user's attributes
=> #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com",
created_at: "2014-07-24 00:57:46", updated_at: "2014-07-24 00:57:46">
>> user.email = "mhartl@example.net"
=> "mhartl@example.net"
>> user.save
=> true
  • Note that the final step user.save is necessary to write the changes to the database. We can see what happens without a save by using reload, which reloads the object based on the database information:
>> user.email
=> "mhartl@example.net"
>> user.email = "foo@bar.com"
=> "foo@bar.com"
>> user.reload.email
=> "mhartl@example.net"

update multiple attributes by using update_attributes

>> user.update_attributes(name: "The Dude", email: "dude@abides.org")
=> true
>> user.name
=> "The Dude"
>> user.email
=> "dude@abides.org"
  • The update_attributes method accepts a hash of attributes, and on success performs both the update and the save in one step (returning true to indicate that the save went through).

update single attribute by using update_attribute

>> user.update_attribute(:name, "The Dude")
=> true
>> user.name
=> "The Dude"

User validations

Validating the presence of name and email attributes

app/models/user.rb

class User < ActiveRecord::Base
  validates :name, presence: true
  validates :email, presence: true
end

equivalent to

class User < ActiveRecord::Base
  validates(:name, presence: true)
  validates(:email, presence: true)
end

Adding length validation

app/models/user.rb

class User < ActiveRecord::Base
  validates :name,  presence: true, length: { maximum: 50 }
  validates :email, presence: true, length: { maximum: 255 }
end

Validating the email format with a regular expression

app/models/user.rb

class User < ActiveRecord::Base
  validates :name,  presence: true, length: { maximum: 50 }
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
  validates :email, presence: true, length: { maximum: 255 },
                    format: { with: VALID_EMAIL_REGEX }
end

Validating the uniqueness of email addresses

app/models/user.rb

class User < ActiveRecord::Base
  validates :name,  presence: true, length: { maximum: 50 }
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
  validates :email, presence: true, length: { maximum: 255 },
                    format: { with: VALID_EMAIL_REGEX },
                    uniqueness: true
end

Validating the uniqueness of email addresses, ignoring case

app/models/user.rb

class User < ActiveRecord::Base
  validates :name,  presence: true, length: { maximum: 50 }
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
  validates :email, presence: true, length: { maximum: 255 },
                    format: { with: VALID_EMAIL_REGEX },
                    uniqueness: { case_sensitive: false }
end

Add Database indices

create using the migration generator:

$ rails generate migration add_index_to_users_email

migration for enforcing email uniqueness.

class AddIndexToUsersEmail < ActiveRecord::Migration
  def change
    add_index :users, :email, unique: true
  end
end
  • Unlike the migration for users, the email uniqueness migration is not pre-defined, so we need to fill in its contents

  • This uses a Rails method called add_index to add an index on the email column of the users table. The index by itself doesn’t enforce uniqueness, but the option unique: true does.

migrate the database

$ bundle exec rake db:migrate

Ensuring email uniqueness by downcasing the email attribute.

app/models/user.rb

class User < ActiveRecord::Base
  before_save { self.email = email.downcase }
  validates :name,  presence: true, length: { maximum: 50 }
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
  validates :email, presence: true, length: { maximum: 255 },
                    format: { with: VALID_EMAIL_REGEX },
                    uniqueness: { case_sensitive: false }
end
  • inside the User model the self keyword is optional on the right-hand side
  • equivalent to
self.email = self.email.downcase

Adding a secure password

  • The method is to require each user to have a password (with a password confirmation), and then store a hashed version of the password in the database. (There is some potential for confusion here. In the present context, a hash refers not to the Ruby data structure but rather to the result of applying an irreversible hash function to input data.)

  • add a way to authenticate a user based on a given password, a method to allow users to log in to the site.

  • The method for authenticating users will be to take a submitted password, hash it, and compare the result to the hashed value stored in the database. If the two match, then the submitted password is correct and the user is authenticated. By comparing hashed values instead of raw passwords, we will be able to authenticate users without storing the passwords themselves. This means that, even if our database is compromised, our users’ passwords will still be secure.

User data model with an added password_digest attribute.

alt text

constructs a migration to add columns to the users table

$ rails generate migration add_password_digest_to_users password_digest:string
  • generate an appropriate migration for the password_digest column.
  • We can choose any migration name we want, but it’s convenient to end the name with to_users, since in this case Rails automatically constructs a migration to add columns to the users table.

The migration to add a password_digest column to the users table

db/migrate/[timestamp]_add_password_digest_to_users.rb

class AddPasswordDigestToUsers < ActiveRecord::Migration
  def change
    add_column :users, :password_digest, :string
  end
end
  • uses the add_column method to add a password_digest column to the users table.

migrate the database

$ bundle exec rake db:migrate

Adding bcrypt to the Gemfile

source 'https://rubygems.org'

gem 'rails',                '4.2.2'
gem 'bcrypt',               '3.1.7'
.
.
.

running bundle install

$ bundle install

Adding has_secure_password to the User model

app/models/user.rb

class User < ActiveRecord::Base
  before_save { self.email = email.downcase }
  validates :name, presence: true, length: { maximum: 50 }
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
  validates :email, presence: true, length: { maximum: 255 },
                    format: { with: VALID_EMAIL_REGEX },
                    uniqueness: { case_sensitive: false }
  has_secure_password
end

Adding Minimum password standards

app/models/user.rb

class User < ActiveRecord::Base
  before_save { self.email = email.downcase }
  validates :name, presence: true, length: { maximum: 50 }
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
  validates :email, presence: true, length: { maximum: 255 },
                    format: { with: VALID_EMAIL_REGEX },
                    uniqueness: { case_sensitive: false }
  has_secure_password
  validates :password, presence: true, length: { minimum: 6 }
end

Creating and authenticating a user

$ rails console
>> User.create(name: "Michael Hartl", email: "mhartl@example.com",
?>             password: "foobar", password_confirmation: "foobar")
=> #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com",
created_at: "2014-09-11 14:26:42", updated_at: "2014-09-11 14:26:42",
password_digest: "$2a$10$sLcMI2f8VglgirzjSJOln.Fv9NdLMbqmR4rdTWIXY1G...">


>> user = User.find_by(email: "mhartl@example.com")
>> user.password_digest
=> "$2a$10$YmQTuuDNOszvu5yi7auOC.F4G//FGhyQSWCpghqRWQWITUYlG3XVy"


>> user.authenticate("not_the_right_password")
false
>> user.authenticate("foobaz")
false

>> user.authenticate("foobar")
=> #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com",
created_at: "2014-07-25 02:58:28", updated_at: "2014-07-25 02:58:28",
password_digest: "$2a$10$YmQTuuDNOszvu5yi7auOC.F4G//FGhyQSWCpghqRWQW...">

>> !!user.authenticate("foobar")
=> true
  • has_secure_password automatically adds an authenticate method to the corresponding model objects
  • use the authenticate method to sign registered users into our site
  • authenticate returns the user itself
  • a user object is neither nil nor false, it does the job nicely

An alternate implementation of the before_save callback.

app/models/user.rb

class User < ActiveRecord::Base
  before_save { email.downcase! }
  validates :name, presence: true, length: { maximum: 50 }
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
  validates :email, presence: true, length: { maximum: 255 },
                    format: { with: VALID_EMAIL_REGEX },
                    uniqueness: { case_sensitive: false }
  has_secure_password
  validates :password, presence: true, length: { minimum: 6 }
end

Disallowing double dots in email domain names

app/models/user.rb

class User < ActiveRecord::Base
  before_save { email.downcase! }
  validates :name, presence: true, length: { maximum: 50 }
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
  validates :email, presence:   true,
                    format:     { with: VALID_EMAIL_REGEX },
                    uniqueness: { case_sensitive: false }
  has_secure_password
  validates :password, presence: true, length: { minimum: 6 }
end

Debug environments

Adding some debug information to the site layout

app/views/layouts/application.html.erb

<!DOCTYPE html>
<html>
  .
  .
  .
  <body>
    <%= render 'layouts/header' %>
    <div class="container">
      <%= yield %>
      <%= render 'layouts/footer' %>
      <%= debug(params) if Rails.env.development? %>
    </div>
  </body>
</html>

Adding code for a pretty debug box, including a Sass mixin.

app/assets/stylesheets/custom.css.scss

@import "bootstrap-sprockets";
@import "bootstrap";

/* mixins, variables, etc. */

$gray-medium-light: #eaeaea;

@mixin box_sizing {
  -moz-box-sizing:    border-box;
  -webkit-box-sizing: border-box;
  box-sizing:         border-box;
}
.
.
.
/* miscellaneous */

.debug_dump {
  clear: both;
  float: left;
  width: 100%;
  margin-top: 45px;
  @include box_sizing;
}

Users resource

Adding a Users resource to the routes file

config/routes.rb

Rails.application.routes.draw do
  root             'static_pages#home'
  get 'help'    => 'static_pages#help'
  get 'about'   => 'static_pages#about'
  get 'contact' => 'static_pages#contact'
  get 'signup'  => 'users#new'
  resources :users
end

RESTful resource

  • Although our immediate motivation is making a page to show users, the single line resources :users doesn’t just add a working /users/1 URL; it endows our sample application with all the actions needed for a RESTful Users resource, along with a large number of named routes for generating user URLs. The resulting correspondence of URLs, actions, and named routes is shown in Table 7.1. (Compare to Table 2.2.) Over the course of the next three chapters, we’ll cover all of the other entries in Table 7.1 as we fill in all the actions necessary to make Users a fully RESTful resource.

alt text

add view for URL /users/1

app/views/users/show.html.erb

<%= @user.name %>, <%= @user.email %>

define an @user variable in the corresponding show action in the Users controller

app/controllers/users_controller.rb

class UsersController < ApplicationController

  def show
    @user = User.find(params[:id])
  end

  def new
  end
end
  • find method on the User model o retrieve the user from the database
  • params used to retrieve the user id. When we make the appropriate request to the Users controller, params[:id] will be the user id 1, so the effect is the same as the find method User.find(1)
  • Technically, params[:id] is the string “1”, but find is smart enough to convert this to an integer.)

Debugger

app/controllers/users_controller.rb

class UsersController < ApplicationController

  def show
    @user = User.find(params[:id])
    debugger
  end

  def new
  end
end
  • when we visit /users/1, the Rails server shows a byebug prompt