In this blog we’re going to look at how to setup devise
gem with rails 7
using importmaps
. If you want to just setup devise
without knowing the nitty gritty follow tldr;
otherwise scroll past it for the full blog.
tldr;
Follow thsese steps to setup devise
with rails
. You must have rails 7.x
installed on your system.
- Set up new app
rails new devise-tutorial
- Inside the
devise-tutorial
folder create a new controller
rails g controller Home index
- Add
devise
gem to your Gemfile
bundle add devise
- Install
devise
configuration into your Rails app
bundle exec rails g devise:install
- Generate
devise views
rails g devise:views
- Generate a user model for
devise
bundle exec rails g devise user
- Apply rails migration
bundle exec rails db:migrate
- Restart the rails server if already running or start the rails app using
rail s
- Go to
http://localhost:3000/users/sign_in
THE END
What is devise ?
Devise is a gem which is used in Rails projects to add authentication to applications. It covers complete functionality like login, signup, reset password, user tracking, user account locking and more.
Using devise enables us to not have to implement all the components required authentication from scratch. Additionally devise also supports omniauth.
Setting up devise with Rails 7
- Let’s create a new Rails 7 app. I am working with rails 7.1.3.4 and ruby version 3.2.3:
➜ devise-tutorial git:(main) ✗ rails -v
Rails 7.1.3.4
➜ devise-tutorial git:(main) ✗ ruby -v
ruby 3.2.3 (2024-01-18 revision 52bb2ac0a6) [x86_64-linux]
- To create a new app run the following command:
rails new devise-tutorial
- We’re going to generate a new controller. In the devise-tutorial folder, run
rails g controller Home index
. This will generate a home_controller.
➜ devise-tutorial git:(main) ✗ rails g controller Home index
create app/controllers/home_controller.rb
route get 'home/index'
invoke erb
create app/views/home
create app/views/home/index.html.erb
invoke test_unit
create test/controllers/home_controller_test.rb
invoke helper
create app/helpers/home_helper.rb
invoke test_unit
- Our
HomeController
should look like this:
class HomeController < ApplicationController
def index
end
end
- Let’s change
app/views/home/index.html.erb
, I am going to change it to have anh2
, you can change however you want
<h2>Hello World</h2>
- In our
routes.rb
file we should haveget home/index
added, let’s make it the root path. Theroutes.rb
should look like this:
Rails.application.routes.draw do
# Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html
# Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500.
# Can be used by load balancers and uptime monitors to verify that the app is live.
get "up" => "rails/health#show", as: :rails_health_check
# Defines the root path route ("/")
# root "posts#index"
root to: "home#index"
end
- Run the rails server and check it lands on the home controller
- Add the
devise
gem usingbundle add devise
➜ devise-tutorial git:(main) ✗ bundle add devise
Fetching gem metadata from https://rubygems.org/.........
Resolving dependencies...
Fetching gem metadata from https://rubygems.org/.........
Resolving dependencies...
Fetching devise 4.9.4
Installing devise 4.9.4
Your Gemfile should have an additional line specifying devise now
- Configure
devise
by runningbundle exec rails g devise:install
➜ devise-tutorial git:(main) ✗ bundle exec rails g devise:install
create config/initializers/devise.rb
create config/locales/devise.en.yml
===============================================================================
Depending on your application's configuration some manual setup may be required:
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', port: 3000 }
In production, :host should be set to the actual host of your application.
* Required for all applications. *
2. Ensure you have defined root_url to *something* in your config/routes.rb.
For example:
root to: "home#index"
* Not required for API-only Applications *
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>
* Not required for API-only Applications *
4. You can copy Devise views (for customization) to your app by running:
rails g devise:views
* Not required *
===============================================================================
This will create devise configuration initializer file config/initializers/devise.rb
which will load devise when rails server starts up.
- You can additionally do the configure the mailer to
localhost
in development environment so you can see mails in the rails logs.
# Add in config/environments/development.rb
config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
- In order to enable flash notifictions from devise you can add following html in the application.html.erb
<p class="notice"><%= notice %></p>
<p class="alert"><%= alert %></p>
Your application.html.erb will look something like this
<!DOCTYPE html>
<html>
<head>
<title>DeviseTutorial</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
<%= javascript_importmap_tags %>
</head>
<body>
<%# DEVISE FLASH NOTIFICATION html GOES HERE %>
<p class="notice"><%= notice %></p>
<p class="alert"><%= alert %></p>
<%= yield %>
</body>
</html>
- Generate
devise
views by runningrails g devise:views
➜ devise-tutorial git:(main) ✗ rails g devise:views
invoke Devise::Generators::SharedViewsGenerator
create app/views/devise/shared
create app/views/devise/shared/_error_messages.html.erb
create app/views/devise/shared/_links.html.erb
invoke form_for
create app/views/devise/confirmations
create app/views/devise/confirmations/new.html.erb
create app/views/devise/passwords
create app/views/devise/passwords/edit.html.erb
create app/views/devise/passwords/new.html.erb
create app/views/devise/registrations
create app/views/devise/registrations/edit.html.erb
create app/views/devise/registrations/new.html.erb
create app/views/devise/sessions
create app/views/devise/sessions/new.html.erb
create app/views/devise/unlocks
create app/views/devise/unlocks/new.html.erb
invoke erb
create app/views/devise/mailer
create app/views/devise/mailer/confirmation_instructions.html.erb
create app/views/devise/mailer/email_changed.html.erb
create app/views/devise/mailer/password_change.html.erb
create app/views/devise/mailer/reset_password_instructions.html.erb
create app/views/devise/mailer/unlock_instructions.html.erb
These views are used by devise controllers. For eg devise/sessions/new.html.erb
is used to render the sign_in page rendered by the devise’s SessionsController
.
- Generate the user model for devise
➜ devise-tutorial git:(main) ✗ bundle exec rails g devise user
invoke active_record
create db/migrate/20240606060617_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
These models are used to store the user info who have signed up into your application.
- The user model will look like this
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
end
The special keywords are the modules which devise provides out of the box. When a module is enabled it requires to persist some data relevant to the module in the database, and hence it’s required to add it in the User model.
Similarly the user migration also have similar keywords
# frozen_string_literal: true
class DeviseCreateUsers < ActiveRecord::Migration[7.1]
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 null: false
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
These also needs to be commented out when the module is enabled.
- Run
rails db:migrate
to apply the changes onto the database
➜ devise-tutorial git:(main) ✗ rails db:migrate
== 20240606060617 DeviseCreateUsers: migrating ================================
-- create_table(:users)
-> 0.0016s
-- add_index(:users, :email, {:unique=>true})
-> 0.0005s
-- add_index(:users, :reset_password_token, {:unique=>true})
-> 0.0004s
== 20240606060617 DeviseCreateUsers: migrated (0.0026s) =======================
- Restart the Rails server.
Test if devise works
- Let’s first see the routes added by devise
➜ devise-tutorial git:(main) ✗ rails routes | grep /users/
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
new_user_password GET /users/password/new(.:format) devise/passwords#new
edit_user_password GET /users/password/edit(.:format) devise/passwords#edit
user_password PATCH /users/password(.:format) devise/passwords#update
PUT /users/password(.:format) devise/passwords#update
POST /users/password(.:format) devise/passwords#create
cancel_user_registration GET /users/cancel(.:format) devise/registrations#cancel
new_user_registration GET /users/sign_up(.:format) devise/registrations#new
edit_user_registration GET /users/edit(.:format) devise/registrations#edit
By default devise enables sign_in, sign_up, user edit and password reset as can be seen in the routes.
- Go to one of these particular route to check if devise works. Visit
localhost:3000/users/sign_in
and you should see something like this: