How to manage users with Devise - Ruby on Rails
By: Lukasz Muzyka, On:
This tutorial assumes you have already completed:
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
-
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
andtest.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.comThis 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
-
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
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.
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.
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!
Comments
Comment
You can login to comment
On: Brunitob wrote:
On: Brunitob wrote:
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:
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.