Learn-a-holic Geek Notes

Human compiled Brainwork by Kornelije Sajler.

Rails App From Scratch: User Sign Up Page

The aim of post is to create only the User Sign Up Page through TDD cycle of creating User Resource, Controller, Model and View.

The User Model will be very simple having only:

  • Email - required, update is disabled once it is created.
  • Password - required, should be greater or equal than 8 characters.
  • Full Name - required, just for human readable displaying of the user name instead of email.

Note:

The first intention of this blog post was full User authentication, but since it uses TDD step-by-step development, and there is a lot from creating Route, Controller, Model and View this post will be only creating Sign Up page. Second and maybe third post will have validation, saving, authenticating and updating user!

Generate User Integration Test

First we will start with generating Rails integration_test residing in spec/requests folder:

1
2
3
$ rails g integration_test users
      invoke  rspec
      create    spec/requests/users_spec.rb

First we will remove a GET /users as describe block generated by default in spec/requests/users_spec.rb and left simple as:

spec/requests/users_sepec.rb
1
2
3
4
require 'spec_helper'

describe 'Users' do
end

Create User Test

In the users_spec.rb we will add a context block that will call the new route and expect to have fields for email, password, password_confirmation, full_name and button Sign Up:

spec/requests/users_sepec.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
require 'spec_helper'

describe 'Users' do
  context 'GET /users/new' do
    it 'displays the create new user page' do
      visit new_user_path

      page.should have_content 'Email'
      page.should have_content 'Full Name'
      page.should have_content 'Password'
      page.should have_content 'Confirm Password'
      page.has_field? 'email'
      page.has_field? 'full_name'
      page.has_field? 'password'
      page.has_field? 'password_confirmation'
      page.has_button? 'Sign Up'
    end
  end
end

There are quite few asserts in this test, that is certainly not good practice in testing, but to make this post shorter, it is like this!

TDD Step 1: Routing (Resource)

After running RSpec test or viewing from Guard the expected error on saving file should be:

1
2
3
4
5
1) Users GET /users/new displays the create new user page
   Failure/Error: visit new_user_path
   NameError:
     undefined local variable or method `new_user_path' for #<RSpec::Core::ExampleGroup::Nested_1::Nested_1:0x007fcf4a5a30b8>
   # ./spec/requests/users_spec.rb:6:in `block (3 levels) in <top (required)>'

The problem is that there is no users route created. Open config/routes.rb, remove all commented code and add users resource:

config/routes.rb
1
2
3
JustToDoIt::Application.routes.draw do
  resources :users
end

After run command to examine Rails routes:

1
2
3
4
5
6
7
8
$ rake routes
    users GET    /users(.:format)          users#index
          POST   /users(.:format)          users#create
 new_user GET    /users/new(.:format)      users#new
edit_user GET    /users/:id/edit(.:format) users#edit
     user GET    /users/:id(.:format)      users#show
          PUT    /users/:id(.:format)      users#update
          DELETE /users/:id(.:format)      users#destroy

You will see the new_user path, but to actually get the path it is needed to be called with _path added to the route name as we called it in first test visit new_user_path.

For this simple authentication we will only need creating and updating of user account, but in future, we may need, a full administration of users, including listing all and destroy them as well!

Guard Routing Error

If there is a LoadError after applying the route in Guard terminal:

1
Exception encountered: #<LoadError: cannot load such file -- /Users/xajler/src/rb/just-todo-it/spec/routing>

Create a spec/routing folder, to fix the issue:

1
$ mkdir spec/routing

After applying the user route (and Guard fix), there is a expected message complaining for existence of users controller.

TDD Step 2: Controller

To make test green, we need to create a user controller and the action or method called new in the app/controllors/users_controller.rb:

app/controllers/users_controller.rb
1
2
3
4
5
class UsersController < ApplicationController
  def new
    @user = User.new
  end
end

We are creating a new User and setting it to a instance variable @user, so it will be visible within the View page!

The RSpec test will be failing since it doesn’t know what the User is:

app/controllers/users_controller.rb
1
2
3
4
1) Users GET /users/new displays the create new user page
   Failure/Error: visit new_user_path
   NameError:
     uninitialized constant UsersController::User

TDD Step 3: Model

To make test green, we need to create a User model in app/models and it will have email, passmord_digest and full_name.

Having a password_digest field is important as it’s the default name that’s used with Rails has_secure_password feature and we’ll be using this feature later.

Use this command to generate User:

1
2
3
4
5
6
7
8
$ rails g model User email password_digest full_name
      invoke  active_record
      create    db/migrate/20121023234400_create_users.rb
      create    app/models/user.rb
      invoke    rspec
      create      spec/models/user_spec.rb
      invoke      factory_girl
      create        spec/factories/users.rb

Change the generate attr_accessible to include email, password, password_confirmation and full_name needed for user create and edit form:

app/models/user.rb
1
2
3
4
5
class User < ActiveRecord::Base
  has_secure_password

  attr_accessible :email, :password, :password_confirmation, :full_name
end

We also added has_secure_password to the User model. This was introduced in Rails 3.1 and adds some simple authentication support to the model using that password_digest column.

Note:

To have this bcrypt-ruby gem must be in Gemfile (we included it in first post) as this gem handles hashing the password before its stored in the database.

The generator will create a Model, Migration, RSpec test and a Factory Girl factory since we use it instead default Rails fixtures!

We will have another error regarding database:

app/models/user.rb
1
2
3
4
1) Users GET /users/new displays the create new user page
   Failure/Error: visit new_user_path
   ActiveRecord::StatementInvalid:
     Could not find table 'users'

Migration

To have a database and make green our test, run command:

1
2
3
4
5
$ rake db:migrate
==  CreateUsers: migrating ====================================================
-- create_table(:users)
   -> 0.0025s
==  CreateUsers: migrated (0.0027s) ===========================================

Note:

This migration will create development.sqlite3 SQLite 3 database in db folder and users table.

We also need to prepare database for testing with:

1
$ rake db:test:prepare

Note:

This migration will create test.sqlite3 SQLite 3 database in db folder, and users table.

The RSpec test still will be failing with complaint on not having a view for user:

1
2
3
4
5
1) Users GET /users/new displays the create new user page
   Failure/Error: visit new_user_path
   ActionView::MissingTemplate:
     Missing template users/new, application/new with {:locale=>[:en], :formats=>[:html], :handlers=>[:erb, :builder, :coffee, :haml]}. Searched in:
       * "/Users/xajler/src/rb/just-todo-it/app/views"

TDD Step 4: View

As we noted in first tutorial, instead of default Template View Engine erb, we will use far more better haml View Engine.

Converting the erb layout to haml

But before using the haml View Engine we need to convert default layout generated with application:

app/views/layouts/application.html.erb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html>
<head>
  <title>JustToDoIt</title>
  <%= stylesheet_link_tag    "application", :media => "all" %>
  <%= javascript_include_tag "application" %>
  <%= csrf_meta_tags %>
</head>
<body>

<%= yield %>

</body>
</html>

to much more readable haml default layout for application:

app/views/layouts/application.html.haml
1
2
3
4
5
6
7
8
9
10
!!! 5
%html
%head
  %title JustToDoIt
  = stylesheet_link_tag    'application', media: 'all'
  = javascript_include_tag 'application'
  = csrf_meta_tags

%body
  = yield

New User View page

We need to create a view for user called new, and it needs to have a fields for email, full_name, password, confirm password and Sign Up button.

Create a app/views/users/new.html.haml:

app/views/users/new.html.haml
1
2
3
%h1 Sign Up

= render partial: 'form'

User Form Partial

Since the creating and updating User form will be identical, we shall create a partial to reuse user form on create and update views:

app/views/users/_form.html.haml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
= form_for @user do |f|
  - if @user.errors.any?
    %div.error_messages
      %h2 Form is invalid
      %ul
        - @user.errors.full_messages.each do |message|
          %li= message

  = f.label :email, 'Email:'
  = f.text_field :email
  %br

  = f.label :password, 'Password:'
  = f.text_field :password
  %br

  = f.label :password_confirmation, 'Confirm Password:'
  = f.text_field :password_confirmation
  %br

  = f.label :full_name, 'Full Name:'
  = f.text_field :full_name
  %br

  = f.submit 'Sign Up'

And finally the test will be green, ignore for now that spec/models/user_spec.rb is currently pending!

Testing Sign Up Page with Browser

The beauty of it all, is that we didn’t even start browser, so we can do it now for test by running Rails server (Thin):

1
2
3
4
5
6
7
8
$ rails s
=> Booting Thin
=> Rails 3.2.8 application starting in development on http://0.0.0.0:3000
=> Call with -d to detach
=> Ctrl-C to shutdown server
>> Thin web server (v1.5.0 codename Knife)
>> Maximum connections set to 1024
>> Listening on 0.0.0.0:3000, CTRL+C to stop

Open page:

http://localhost:3000/users/new

The design it is not really attractive at all, but it was not scope of the post, and should not be while creating application. When designers create full design it can be applied very easily. Functionality matters for now!

Creating the SignUp route

The /users/new route name is descriptive but having just /signup, I think is far more better route name!

SignUp Route Test

Create a new test context in spec/resources/users_spec.rb:

spec/requests/users_sepec.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
require 'spec_helper'

describe 'Users' do
  context 'GET /users/new' do
    it 'displays the create new user page' do
      visit new_user_path

      page.should have_content 'Email'
      page.should have_content 'Full Name'
      page.should have_content 'Password'
      page.should have_content 'Confirm Password'
      page.has_field? 'email'
      page.has_field? 'full_name'
      page.has_field? 'password'
      page.has_field? 'password_confirmation'
      page.has_button? 'Sign Up'
    end
  end

  context 'GET /signup' do
    it 'displays the sign up page' do
      visit signup_path

      page.should have_content 'Email'
      page.should have_content 'Full Name'
      page.should have_content 'Password'
      page.should have_content 'Confirm Password'
      page.has_field? 'email'
      page.has_field? 'full_name'
      page.has_field? 'password'
      page.has_field? 'password_confirmation'
      page.has_button? 'Sign Up'
    end
  end
end

The GET /signup context is added that is now visiting the signup_path!

Test fails:

spec/requests/users_sepec.rb
1
2
3
4
1) Users GET /signup displays the sign up page
   Failure/Error: visit signup_path
   NameError:
     undefined local variable or method `signup_path' for #<RSpec::Core::ExampleGroup::Nested_1::Nested_2:0x007f9f8b6459d8>

There is no signup_path so we need to create it in routes:

config/routes.rb
1
2
3
4
JustToDoIt::Application.routes.draw do
  resources :users
  get 'signup', to: 'users#new', as: 'signup'
end

The test passes and it is green, you can now try testing it in browser:

localhost:3000/signup

It should work just fine!

Conclusion

This post intended to absorb whole authentication of user, but currently is just too long and I will stop here and in second post will introduce validation, saving and maybe yet in third post authenticating and updating user!

Code

The code is hosted on GitHub and can be cloned from the xajler/just-todo-it.

Github xajler/just-todo-it commit for this post:

Created User Controller, Model and View for Sign Up or Create User, created two integration tests, changed spec_helper due issues between the database_cleaner and sprok.

Comments