Nikolay NIKOLOV

Overriding Devise to Skip Setting of Password (Rails4, Devise3)

24 July 2015

I've been thinking about a light-touch workflow which allows a prospective user to register without setting a password. My first idea was to override the confirmable module (i.e. to invite the user to set a password as part of the confirmation) but I dropped it as it introduces an interim state (an unconfirmed account) and for me this is an unnecessary complication. My idea was to make the registration simpler and requiring a confirmation would be the exact opposite.

Thus I decided to override the default registration workflow so that Devise sets a password which is sent to the user. The user may decide than to change it or not - it would be the usual case of password management. There is a slight imperfection - the password is communicated in plain text. I know that this is not kosher but I decided I'd take this imperfection as I really want to achieve a light-touch registration interaction.

This lengthy blog post is, essentially, a tutorial how to do it - I've written it up as I couldn't find an exact recipe so had to go through an try-and-error process till I got it working.

1. Starting with a default Devise example

So let's start first with a relatively simple yet self-contained code example - Daniel Kehoe's excellent Devise tutorial. I forked it here and simplified it a bit. The example uses Mandrill to handle the emails so you would need to register - a free account gives you a generous quota of 12K emails per month.

Let's clone that commit. This is done by

git clone https://github.com/nikolay12/my_devise

followed by

git reset --soft 74a92e4ce4934f3e06a753c2d93e2127930192d0

You need than to run

bundle install

The example uses the econfig gem to load the environment variables and database credentials. I'm storing these in secrets.yml (you just need to enter your values)

development:
  MANDRILL_USERNAME: ""
  MANDRILL_API_KEY: ""
  MANDRILL_DOMAIN: ""
  RETURN_EMAIL: ""
  DEVISE_SECRET: ""
  secret_key_base: ""

and database.yml:

development:
  adapter: postgresql
  encoding: utf8
  database: my_devise
  pool: 5
  username: 
  password: 
  host: 127.0.0.1
  port: 5432

The example uses postgres but you could use any other DB (in fact, the mysql gem is included in the Gemfile, too). Once the DB credentials are set you need to run

rake db:setup

If you've done everything correctly running

rails server

should give you

2. Adding a customized RegistrationsController

As next we need to override the default RegistrationsController:

class RegistrationsController < Devise::RegistrationsController

  def new
    super
  end

  def edit
    super
  end

  def destroy
    super
  end

  def cancel
    super
  end

# POST /resource
  def create
    build_resource(sign_up_params)

    generated_password = Devise.friendly_token.first(8)
    resource.password = generated_password
    resource.save

    yield resource if block_given?
    if resource.persisted?
      if resource.active_for_authentication?
        set_flash_message :notice, :signed_up if is_flashing_format?
        sign_up(resource_name, resource)
        respond_with resource, location: after_sign_up_path_for(resource)
      else
        set_flash_message :notice, :"signed_up_but_#{resource.inactive_message}" if is_flashing_format?
        expire_data_after_sign_in!
        respond_with resource, location: after_inactive_sign_up_path_for(resource)
      end
    else
      clean_up_passwords resource
      set_minimum_password_length
      respond_with resource
    end

    MyMailer.welcome(resource, {password: generated_password}).deliver if resource.persisted?

  end

  def update
    super
  end
end

Instead of typing/pasting the above code you can copy

/app/controllers/registrations_controller.rb

from the github repository but you'll need to rebase it to include the latest commit (if you reset to the older commit as suggested above):

git reset HEAD@{1}

If you haven't reset your local git repo than you wouldn't need to do anything - a simple git clone has the code you need.

3. Adding customized views for the RegistrationsController

These are, basically, the default views which can be generated by

rails generate devise:views

However, they need to be placed under

app/views/registrations

A simple copy is what you need:

cp app/views/devise/registrations/* app/views/registrations/

Than you can remove the password/password confirmation fields.

4. Changing the routes to account for the customized RegistrationsController

The other controller-related change that is necessary is to set the route to:

devise_for :users, :controllers => { :registrations => "registrations" }

5. Set a custom Devise mailer

The other remaining task is to set a cusomized mailer that sends the password upon registration. It is called by the RegistrationsController abobe. The mailer is simple:

#/app/mailers/my_mailer.rb
class MyMailer < Devise::Mailer
  helper :application # gives access to all helpers defined within `application_helper`.
  include Devise::Controllers::UrlHelpers # Optional. eg. `confirmation_url`

  def welcome(record, opts={})
    devise_mail(record, :welcome, opts)
  end
end

You also need to add the views and place them under the corresponding views sub-directory. You can copy the default views:

cp app/views/devise/mailer/* app/views/mymailer

and add the view that contains the password:

#app/views/my_mailer/welcome.html.erb
<p>Welcome!</p>

<p>We've generated a password for you: <%= @resource.password %></p>

<p>If you prefer, please, feel free to change it (under "Account/Settings").</p>

That's it!