How to manage users with Devise - Ruby on Rails

By:, On:

This tutorial assumes you have already completed:

  1. Path: Setting Things Up

Devise is an awesome gem that will save us a lot of work. We will use it to allow users to create accounts on our website, edit their profiles, login, log out, send password reminders, etc. Everything is secure and easy to setup. We can implement by hand all the functionalities that Devise provides, but we won't. The reason is twofold. First, it would take a lot of time and second, the level of customization we will need for the simple projects is not that high. Therefore, a standard Devise setup will serve us just fine.


Note: This tutorial is longer than usual, please take your time.

Step 1: Read Devise's Github and Add Gem Devise

Let's head to Devise's github page. I recommend you read the documentation. Everything you need to know is there. In future tutorials, we will be customizing Devise, so we will need to come back to this page. For now, take your time and checking it out.

First, we need to add Devise to our Gemfile. The Gemfile is the list of all "plugins" or as we call them in the Ruby community "gems." Gems are self-contained mini-programs that are built in a way that allows us to merge them seamlessly with our application. Devise is one of those "gems." Let's go ahead and open Gemfile (you should find it in your brand new application in the root folder).

Gemfile


source 'https://rubygems.org'

# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '4.1.0'
# Use sqlite3 as the database for Active Record
gem 'sqlite3'
# Use SCSS for stylesheets
gem 'sass-rails', '~> 4.0.3'
# 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'
# See https://github.com/sstephenson/execjs#readme for more supported runtimes
# gem 'therubyracer',  platforms: :ruby

# Use jquery as the JavaScript library
gem 'jquery-rails'
# Turbolinks makes following links in your web application faster. Read more: https://github.com/rails/turbolinks
gem 'turbolinks'
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
gem 'jbuilder', '~> 2.0'
# bundle exec rake doc:rails generates the API under doc/api.
gem 'sdoc', '~> 0.4.0',          group: :doc

# Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
gem 'spring',        group: :development

# Use ActiveModel has_secure_password
# gem 'bcrypt', '~> 3.1.7'

# Use unicorn as the app server
# gem 'unicorn'

# Use Capistrano for deployment
# gem 'capistrano-rails', group: :development

# Use debugger
# gem 'debugger', group: [:development, :test]

Every line that starts with "#" is ignored by our software. This way, we can make comments in the ruby code itself.

In the gem file, you can already find a couple gems. They will add JavaScript jQuery frameworks to your app, setup databases on your machine and more. Let's go ahead and add the Devise gem at the bottom of our Gemfile:

Gemfile


    gem 'devise'

Step 2: Bundle Application

Now we need to bundle our application. That means that a "gem manager" - called bundler - will download all the necessary gems from the internet. This process can take a while depending on the size of the gem or if you do it for the first time.

To bundle the application, go to the terminal prompt, move inside the folder where you want to put your application and run the command:


$ bundle install


Your bundle is complete!
Use `bundle show [gemname]` to see where a bundled gem is installed.

Step 3: Generate Configuration File

Next, we need to generate a configuration file for Devise. In the terminal, run:


$ 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 on Heroku with Rails 3.2 only, 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

===============================================================================

Read this message carefully. It tells us exactly what we need to do next.

Step 4.1: Configure Environments Files

Let's take a look step by step:

Step 4.2: Modify Application File

 

  1. Inside config/environments folder there are 3 files. One for each environment. We can configure all of them with different settings.

    The Devise line is proposing that we should go inside development.rb and test.rb

    config/environments/development.rb config/environments/test.rb

    config.action_mailer.default_url_options = { host: 'localhost:3000' }
            

    Inside our production.rb file we need to put a real URL that matches our domain. If you are using Heroku, this would be the Heroku domain, for example: http://your-demo-application.herokuapp.com. If you already connected your heroku to a custom domain this would be http://www.custom-domain.com

    This is important because all emails that Devise will be sending to users - password resets, account confirmations, etc. - will be pointing to this address.

    config/environments/development.rb config/environments/production.rb

    config.action_mailer.default_url_options = { host: 'http://your-app-on-heroku.herokuapp.com' }
            

    Step 4.3: Create Static Pages

  2. If you followed along this tutorial and completed "creating static pages tutorial" you're set. Otherwise - go back and do the tutorial.  

 Step 4.4: Flash Messages

3.  Rails applications use flash messages to notify us about events. We will use flash messages a lot in the future so we want to make them look good. The example proposed by Devise is going to work, but it will soon become insufficient for our application. We will use a different code. Open your master layout - this is the file where all other templates are wrapped around. Navigate to app/views/layouts/application.html.erb

4. app/views/layouts/application.html.erb


<!DOCTYPE html>
<html>
<head>
    <title>Demo</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>

        

"<%= yield %>" will be replaced with our templates, so that we don't need to write all the necessary HTML code that has to appear on each page. We want to display out messages right above the yield:


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

<% flash.each do |key, value| %>
    <div class="alert alert-<%= key %> alert-dismissable">
        <button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
        <%= value %>
    </div>
<% end %>

<%= yield %>

</body>
</html>

        

This code is written to take advantage of TwitterBootstrap CSS. For instructions 4, we're on the latest version of Rails, so there is nothing to do. And the same for instructions 5, we will not be making any changes to the views so we are done.

Step 5: Configure Email

We are almost done with the setup. The last thing to configure is the email system so that our application can send emails, like for ones that reset passwords. First, let's open the Devise initializer file:

config/initializers/devise.rb


config.mailer_sender = 'please-change-me-at-config-initializers-devise@example.com'

Can you see this? Change it to the email application that you will use to sign the emails. In the next step we will configure the email system to use Gmail for sending those emails. If you use Gmail, you can fill this out with your own email, if you don't use Gmail, open an account here.

Also, make sure this line is not commented out

config/initializers/devise.rb


config.secret_key = '1788ecf9deaa49646a8cf657d2c00faedd63033f8bfe3e16c7ddc0808502482a11b640c5b0a9f1089696477e7f3199a9861a1594422fccfa4222bb5d151b60fc'

Look around the file.There are a lot of options for Devise here.

Step 6: Modify development and Production Configuration

Next, we are going to add a configuration to send actual emails. In the config/environments folder we will modify both development and production configurations:

/config/environments/development.rb


config.action_mailer.default_url_options = { :host => 'localhost:3000' }
config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
      address: "smtp.gmail.com",
      port: 587,
      authentication: "plain",
      enable_starttls_auto: true,
      user_name: "your_email@gmail.com",
      password: "your_password"

  }

/config/environments/production.rb


config.action_mailer.default_url_options =   { :host => 'your_app.herokuapp.com' }
config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
      address: "smtp.gmail.com",
      port: 587,
      authentication: "plain",
      enable_starttls_auto: true,
      user_name: "your_email@gmail.com",
      password: "your_password" 

  }

Step 7: Run Rails Server

At this point, it is a good idea to restart the app. The generator we have just run created an initializer file. All initializers are loaded during application startup so if you're running a server at this point make sure you stop it and start it again. In the server window click on your keyboard "Control + C" and type:


$ rails server

Open a different terminal window from the one where your server is running and type:


$ rails generate devise user

Once you press enter, Rails will create a few files:


invoke  active_record
create    db/migrate/20140506090012_devise_create_users.rb
create    app/models/user.rb
invoke    test_unit
create      test/models/user_test.rb
create      test/fixtures/users.yml
insert    app/models/user.rb
route  devise_for :users

Step 8: Take a look at Database and Run Rake Routes

The first file is a migration file: 20140506090012_devise_create_users.rb Think about it as an instruction for our application to make changes to the database. Yes, we will be using our database for the first time. Let's open the file and have a look at what is inside:

db/migrate/20140506090012_devise_create_users.rb


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: ""

      ## Recoverable
      t.string   :reset_password_token
      t.datetime :reset_password_sent_at

      ## Rememberable
      t.datetime :remember_created_at

      ## Trackable
      t.integer  :sign_in_count, default: 0, null: false
      t.datetime :current_sign_in_at
      t.datetime :last_sign_in_at
      t.string   :current_sign_in_ip
      t.string   :last_sign_in_ip

      ## Confirmable
      # t.string   :confirmation_token
      # t.datetime :confirmed_at
      # t.datetime :confirmation_sent_at
      # t.string   :unconfirmed_email # Only if using reconfirmable

      ## Lockable
      # t.integer  :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
      # t.string   :unlock_token # Only if unlock strategy is :email or :both
      # t.datetime :locked_at


      t.timestamps
    end

    add_index :users, :email,                unique: true
    add_index :users, :reset_password_token, unique: true
    # add_index :users, :confirmation_token,   unique: true
    # add_index :users, :unlock_token,         unique: true
  end
end

Watch out! Occasionally, due to unknown reasons, migrations are generated without the *.rb extension. If .rb extension is not present, migration won't run and you will have all sorts of problems. So if your migration file doesn't have *.rb - add it.

For the purpose of this tutorial, we will use default settings. But if you're interested in changing the defaults, head to the Devise github page and read the documentation.

Next, the document that has been created is the user model. Model defines the nature of our records. As we build our application it will become clear what functions the model has. For now, let's simply have a look at it and run down the structure of the file.

app/models/user.rb


class User < ActiveRecord::Base
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable
end

The first and last line are the opening and closing of our model declaration. All code related to the user that we are going to write will be between those two lines. Devise has added a couple lines in the middle of the file:


# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable

These declarations give some special abilities to our users. They can register (:registerable), login (:database_authenticatable) recover password(:recoverable), add a check box so that website will remember them (:rememberable), we can see when they logged in or out (:trackable), and upon registration, we will check if the data they have provided is valid (:validatable).

We have also created test files, which we will not use for now. The last line ("route devise_for :users") indicates that Devise altered our routes.rb file.

/config/routes.rb


Rails.application.routes.draw do
  devise_for :users

  root 'static_pages#home'

  #more lines of commented out code
end

This line of code allows us to use several routes to manage users. Let's have a look at them. In your terminal, from our application folder, type:

bash


$ rake routes


new_user_session GET    /users/sign_in(.:format)       devise/sessions#new
            user_session POST   /users/sign_in(.:format)       devise/sessions#create
    destroy_user_session DELETE /users/sign_out(.:format)      devise/sessions#destroy
           user_password POST   /users/password(.:format)      devise/passwords#create
       new_user_password GET    /users/password/new(.:format)  devise/passwords#new
      edit_user_password GET    /users/password/edit(.:format) devise/passwords#edit
                         PATCH  /users/password(.:format)      devise/passwords#update
                         PUT    /users/password(.:format)      devise/passwords#update
cancel_user_registration GET    /users/cancel(.:format)        devise/registrations#cancel
       user_registration POST   /users(.:format)               devise/registrations#create
   new_user_registration GET    /users/sign_up(.:format)       devise/registrations#new
  edit_user_registration GET    /users/edit(.:format)          devise/registrations#edit
                         PATCH  /users(.:format)               devise/registrations#update
                         PUT    /users(.:format)               devise/registrations#update
                         DELETE /users(.:format)               devise/registrations#destroy
                    root GET    /                              static_pages#home

Step 9: Add Link to Master Layout

Now, lets use those routes and put a link to login on our master layout:

/app/views/layouts/application.html.erb


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

<% flash.each do |key, value| %>
<div class="alert alert-<%= key %> alert-dismissable">
    <button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
    <%= value %>
</div>
<% end %>

<%= link_to 'Login', new_user_session_path %>

<%= yield %>

</body>
</html>

You can see how we used the first column of the routes "new_user_session" that points to the URL "localhost:3000/users/sign_in" and that goes to the devise/sessions controller to "new" action. Let's see how it works. Start your server if you haven't yet:

bash


rails server

=> Booting WEBrick
=> Rails 4.1.0 application starting in development on http://0.0.0.0:3000
=> Run `rails server -h` for more startup options
=> Notice: server is listening on all interfaces (0.0.0.0). Consider using 127.0.0.1 (--binding option)
=> Ctrl-C to shutdown server
[2014-05-07 11:06:08] INFO  WEBrick 1.3.1
[2014-05-07 11:06:08] INFO  ruby 2.1.1 (2014-02-24) [x86_64-darwin13.0]
[2014-05-07 11:06:08] INFO  WEBrick::HTTPServer#start: pid=22386 port=3000

And open a website in the browser: localhost:3000

devise missing migration error

What is that?! Do you remember how we created users with devise? That generated a migration file. However, we haven't used that migration file yet to update our database. This is why it tells us: Migrations are pending. To resolve this issue, run: bin/rake db:migrate RAILS_ENV=development. Let's quickly fix that by running "migration." Open a new terminal window and navigate to your application. On Mac, you can easily do it by pressing command + t on your keyboard:

bash


$ rake db:migrate

== 20140506090012 DeviseCreateUsers: migrating ================================
-- create_table(:users)
   -> 0.0040s
-- add_index(:users, :email, {:unique=>true})
   -> 0.0014s
-- add_index(:users, :reset_password_token, {:unique=>true})
   -> 0.0008s
== 20140506090012 DeviseCreateUsers: migrated (0.0064s) =======================

Head back to the browser and refresh the window.

devise login screen 1 devise login screen 2 devise login screen 3 devise login screen 4

Step 10: Add Registration and Login

Excellent! We can now register and login. We're almost done for this tutorial. All we need now is to logout. Let's open our application.html.erb again and modify our link a little. We can delete: <%= link_to 'Login', new_user_session_path %> and replace it with the code below

/app/views/layouts/application.html.erb


<% if current_user %>
    <%= link_to 'Logout', destroy_user_session_path, method: :delete %>
<% else %>
    <%= link_to 'Login', new_user_session_path %>
<% end %>

If you refresh the website now, you should see a link to logout. Check if it works.

devise logout screen 1 devise logout screen 2

Notice how we use "current_user."This method is provided to us by Devise. If the user is logged in, current_user will return us his record. We could write code that says <%= current_user.email %> and put it in the layout. The issue is that when a user is not logged in, then current_user will give us "nil" and the code <%= current_user.email %> will evaluate to <%= nil.email %> which will crash our website. Therefore, we should wrap that inside the clause that checks if current_user is present, that is if a user is logged in.

/app/views/layouts/application.html.erb


<% if current_user %>
    Welcome <%= current_user.email %> <%= link_to 'Logout', destroy_user_session_path, method: :delete %>
<% else %>
    <%= link_to 'Login', new_user_session_path %>
<% end %>

Play around with it. If you setup your email already, your application should also send the password resets.

Step 11: Commit to Git and Deploy to Heroku

Finally, commit to git and deploy to heroku:

bash


$ git add .
$ git commit -m "added devise users"

[master c8666a9] added devise
 9 files changed, 250 insertions(+), 29 deletions(-)
 create mode 100644 app/models/user.rb
 create mode 100644 db/migrate/20140506090012_devise_create_users.rb
 create mode 100644 db/schema.rb
 create mode 100644 test/fixtures/users.yml
 create mode 100644 test/models/user_test.rb


$ git push


$ git push heroku master

After the push is successful, you have to migrate the database on Heroku


$ heroku run rake db:migrate

You can pat yourself on the back. You have authentication working. It's time to build something awesome!

If you login you will be able to mark this tutorial as finished to track your progress



Comments

  • On: Brunitob wrote:

    Such a wonderful guide!!!
  • On: Brunitob wrote:

    I had a minor issue when deploying to heroku, logout didn´t work. I solved -> http://stackoverflow.com/a/23317324
  • On: Mayurkumar wrote:

    thank you very much for such a wonderfull guide..

  • On: nate wrote:

    Lots of tutorials are great for diving deep and learning. These are great for building. Love it! I am wondering how to safely push to an open source repository like github if the email password is in the production.rb and development.rb files? Can't wait to work with these more.

  • On: COConsulting wrote:

    @nate I use the Figaro gem to help hide passwords when pushing my code to a public repository like github.

  • On: COConsulting wrote:

    I was a little misled by the title of this tutorial. It would be better titled "How to support users with Devise", or "How to allow users with Devise". The current title of, "How to manage users with Devise" made me think that this would be a tutorial showing how you can actually manage the users on your website using Devise. This is something that I am trying to implement on my site. I have Devise working and users are able to signup, login, update their profile, etc. And I even have multiple security levels (one being admins). Now, I want to allow a user that is an admin the ability to manage the other users on the site to reset passwords, or update the client's information. Unfortunately, that is not what this tutorial does.

    Other than a misleading title, it was a very thorough tutorial. Thank you.

Comment

You can login to comment