Let's Build with Ruby on Rails: Extending Devise Series – Confirmation Emails
Tags: rails, ruby, tutorial, video
Published: 2019-06-23 19:38:30

---
title: Let's Build with Ruby on Rails: Extending Devise Series – Confirmation Emails
published: true
tags: rails, ruby, tutorial, video
canonical_url: https://web-crunch.com/extending-devise-confirmation-emails/
---

Continuing my Let’s Build with Ruby on Rails – Extending Devise series I take a look at adding confirmation emails to a given Ruby on Rails application.

[Download the source code](https://github.com/justalever/devise_confirmable_example.git)

Download the Kickoff Tailwind Template I reference in this tutorial (optional):  
https://github.com/justalever/kickoff\_tailwind

{% youtube D_50cO1TO0A %}

In this guide, you’ll learn how to install [Devise](https://github.com/plataformatec/devise), configure it with default settings, and then later extend it to include a feature called `:confirmable`.

To summarize, `:confirmable` appends three new fields to your `User` table (or whatever model you chose to initialize Devise with) in the database. From there, once a new user signs up to the app they are sent a confirmation email. That email then contains a link with a unique token that once clicked signals to your application to create the new user account. The perk of this is increased security. The person signing up will likely be entering their own email address. In doing so they can successfully visit their inbox to see the new confirmation email that is unique to their own email and session inside the application.

#### Testing E-mails locally

Testing e-mail delivery locally with Ruby on Rails comes with some configuration workarounds. I make use an of a gem called [mailcatcher.me](http://mailcatcher.me) which acts as a buffer on a separate port to intercept any e-mails sent locally in your given Ruby on Rails app. Devise also has a configuration required for the `config/environments/development.rb` file. Ultimately those settings are as follows:

```ruby
# config/environments/development.rb

Rails.application.configure do
  ...
  config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
  config.action_mailer.delivery_method = :smtp
  config.action_mailer.smtp_settings = { :address => "localhost", :port => 1025 }
end

```

Mailcacher is a daemon that runs in the background. Once installed you can run `$ mailcatcher` to get it up and running. If you don’t have it installed you can do so by running `$ gem install mailcatcher`.

On top of that configuration, we need to install the Devise gem:

```ruby
# Gemfile

gem 'devise', '~> 4.6', '>= 4.6.2'

```

Then run:

```bash
$ bundle install

```

After Devise is installed we can run a generator provided by the gem

```bash
$ rails generate devise:install

```

This creates a new initializer file within `config/initializers`. I’ll change two options in that file.

```bash
# config/initializers/devise.rb

Devise.setup do |config|
  ...

  # ==> Mailer Configuration
  # Configure the e-mail address which will be shown in Devise::Mailer,
  # note that it will be overwritten if you use your own mailer class
  # with default "from" parameter.
  config.mailer_sender = 'andy@example.com'

  ...

  # If true, requires any email changes to be confirmed (exactly the same way as
  # initial account confirmation) to be applied. Requires additional unconfirmed_email
  # db field (see migrations). Until confirmed, new email is stored in
  # unconfirmed_email column, and copied to email column on successful confirmation.
  config.reconfirmable = false

  ...

end

```

With those settings saved, we can generate the resource (model) that Devise will append it’s logic to. In most cases, this is often the `User` model but any model would suffice. Think `Account`, `Admin`, `Profile`, etc…

```bash
$ rails generate devise User

```

This will create a `user.rb` file within `app/models/user.rb` that contains some logic by default. This generator also creates a new routing mechanism in our routes file for devise to hook in to.

The main thing to take notice of is the migration file this command generates in `db/migrate/`. Mine looks like the following.

```ruby
# db/migrate/20190622200524_devise_create_users.rb

# frozen_string_literal: true

class DeviseCreateUsers < ActiveRecord::Migration[6.0]
  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

```

Notice the commented out declarations. By default, Devise ships with these ready to roll if you just created a new install of the gem. You can simply uncomment those fields you require and run `rails db:migrate`. In our specific case we only nee the `confirmable` fields. So I’ll uncomment all but the last. My file now looks like the following:

```ruby
# db/migrate/20190622200524_devise_create_users.rb

# frozen_string_literal: true

class DeviseCreateUsers < ActiveRecord::Migration[6.0]
  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

```

Be sure to save that file and then run:

```bash
$ rails db:migrate

```

With that in place we can refer to the `schema.rb` file in the `db/` directory for what our Database currently has in place:  
Here’ mine.

```ruby
# This file is auto-generated from the current state of the database. Instead
# of editing this file, please use the migrations feature of Active Record to
# incrementally modify your database, and then regenerate this schema definition.
#
# This file is the source Rails uses to define your schema when running `rails
# db:schema:load`. When creating a new database, `rails db:schema:load` tends to
# be faster and is potentially less error prone than running all of your
# migrations from scratch. Old migrations may fail to apply correctly if those
# migrations use external dependencies or application code.
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema.define(version: 2019_06_22_200524) do

  create_table "users", force: :cascade do |t|
    t.string "email", default: "", null: false
    t.string "encrypted_password", default: "", null: false
    t.string "reset_password_token"
    t.datetime "reset_password_sent_at"
    t.datetime "remember_created_at"
    t.string "confirmation_token"
    t.datetime "confirmed_at"
    t.datetime "confirmation_sent_at"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.index ["email"], name: "index_users_on_email", unique: true
    t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
  end

end

```

With this migrated we can now hook into the `confirmation_token`, `confirmation_sent_at`, and `confirmed_at` fields on the `users` database table that don’t come by default.

One final addition is within your main layout file. I’ll add the following to `app/views/layouts/application.html.erb`

```erb
<!DOCTYPE html>
<html>
  <head>
    <title>DeviseConfirmableExample</title>
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
  </head>

  <body>
    <%= notice %>
    <%= alert %>
    <%= yield %>
  </body>
</html>

```

I’ve added the `<%= notice %>` and `<%= alert %>` lines to display any feedback devise/our app has.

### Creating Authenticated routes

Even though we are close to being complete we still need a way to restrict users from some path prior to logging in. I’ll create two new controllers that will feature basic index actions and views. These are merely for example purposes and serve no real dynamic data but you can learn how to lock down more advanced scenarios using the code I’ve utilized here.

```bash
$ rails g controller home index
$ rails g controller dashboard index

```

The idea behind these two controllers is that the `home` controller will be a public facing page anyone can view whether they are signed in or not and the dashboard controller is only for logged in users.

#### Routing

Up until now, we have only basic routing in place inside `config/routes.rb`. I prefer to make this more extendable in the event we want to expand to more than the `index` action for both the `dashboard` or `home` controllers in the future. In doing so I’ve updated my routes file to the following:

```bash
# config/routes.rb

Rails.application.routes.draw do
  devise_for :users
  resources :dashboard, only: [:index]
  resources :home, only: [:index]
  root to: "home#index"
end

```

Here I defined a `resources` declaration for both the `dashboard` and `home` routes. This is a handy way to generate fully RESTful routing if we so desire in the future. To keep too many routes from getting generated unnecessarily we can pass an `only: [:index]` option to signal the framework to only create the url/path helpers for the given resources’ action. You can view all your routes by running `rails routes` or heading to `localhost:3000/rails/info/routes` if you have `rails server` running.

We also made a new `root` path which tells our app what page is indeed the entry point `root to: "home#index` declares the index action on the home controller our entry point.

Finally `devise_for :users` is another handy method that generates all the necessary routing that the Devise gem needs to operate.

### Making Confirmable Work

I’ll update our generic controllers to reflect the feature we are building. The `home_controller.rb` file will go unchanged but feature this code.

```ruby
# app/controllers/home_controller.rb

class HomeController < ApplicationController
  def index
  end
end

```

The `dashboard_controller.rb` file will be similar to the `home_controller.rb` file with one exception. I’ll add an action that comes from the devise gem called `authenticate_user!`. This acts as a gatekeeper to those not signed in that visit a specific action in the controller. In the dashboard’s case, I don’t want any logged out user to see the page so we can simply write `before_action :autheticate_user!`. If we wanted to only block a handful of actions instead of every action you could write something like `before_action :authenticate_user!, only:[:edit, :destroy]` for example.

```ruby
class DashboardController < ApplicationController
  before_action :authenticate_user! 

  def index
  end
end

```

Besides the migration file we modified earlier, nothing has really changed from the average Devise installation on a Rails application. We need a way to hook into the `confirmable` option to tell both our app and Devise what to do next. This means inheriting some controller logic from the devise gem and passing some options at runtime.

To do this we can start by modifying our routes slightly:

```ruby
Rails.application.routes.draw do
  devise_for :users, controllers: {
    confirmations: 'confirmations'
  }
  resources :dashboard, only: [:index]
  resources :home, only: [:index]
  root to: "home#index"
end

```

The `devise_for` method accepts quite a few options. One of those is a hash of controllers of which you can signal to the gem that you too have controller logic that Devise needs to make use of.

Since we are extending the confirmation logic we can pass our own `confirmations` controller as an option here. This means that Devise will look for a controller inside `app/controllers` for a `confirmations_controller.rb` file. Naming is important here. The name you pass as a string inside your routes must match the controller name in `app/controllers/`.

So we can create that file. Inside you’ll notice a little different syntax.

```ruby
class ConfirmationsController < Devise::ConfirmationsController

  private
    def after_confirmation_path_for(resource_name, resource)
      sign_in(resource)
      dashboard_path
    end
end

```

The confirmations controller we create needs to inherit the parent confirmations controller from the Devise gem. Think of this as a subclass of a subclass. Inside the class, I created a private method that Devise understands called `after_confirmation_path_for`. We can pass in the resource\_name, and resource to get information on the person who recently confirmed their account. If they created an account, visited their email, and clicked on the link to confirm we can use the token to find the given resource and officially grant them access to the application. After they get signed in we can tell designate where they head next. I added the `dashboard_path` as the place I want to take the user.

### Testing our work

You’ll need both `rails server` and `mailcatcher` running at this point. Assuming all is well in the app you can create a new user by heading to `localhost:3000/users/sign_up`. There will be a new form (that’s really ugly) that you can supply your email, password, and password confirmation on. Once you click sign up you should see a new email show up in mailcatcher. Within the email, you can visit the link to activate your account.

Congrats if you made it this far! Confirmable should be working well. In your production environment, you obviously wouldn’t use mailcatcher but a third-party email service provider like Postmark, Sendgrid, etc… Those configurations can be added to `config/environments/production.rb` just like we did with the development configurations. Ruby on Rails makes it quite easy to hook into other services and spend next to no time on configuration and more time where it matters, on your features and products.

### The Series So Far

- [Let’s Build: With Ruby on Rails – Extending Devise Series – Adding Custom Fields](https://web-crunch.com/extending-devise-custom-fields)

### Shameless plug time

[![](https://i1.wp.com/web-crunch.com/wp-content/uploads/2019/01/YouTube-Promo3.jpg?resize=3840%2C2160&ssl=1)](http://hellorails.io)

I have a new course called [Hello Rails](https://hellorails.io). Hello Rails is a modern course designed to help you start using and understanding Ruby on Rails fast. If you’re a novice when it comes to Ruby or Ruby on Rails I invite you to check out the site. The course will be much like these builds but a super more in-depth version with more realistic goals and deliverables. [Sign up to get notified today](https://hellorails.io/)!

Follow [@hello\_rails](https://twitter.com/hello_rails) and [myself @justalever](https://twitter.com/justalever) on Twitter.

The post [Extending Devise Series – Confirmation Emails](https://web-crunch.com/extending-devise-confirmation-emails/) appeared first on [Web-Crunch](https://web-crunch.com).