Skip to content

How To: Email only sign up

KevinTriplett edited this page Jun 7, 2012 · 29 revisions

Sometimes, you want a "gradual engagement" feature where a visitor signs up using only their email address. Or maybe their account is setup when they contact the business and a signup invitation is sent to them via email. Whatever the logic, the visitor completes their sign up at a later date.

The following technique was documented by Claudio Marai. This page combines Claudio's example code with the code in the comments section of his post and updates everything for Devise 2. NOTE: This is for Rails 3 and Devise ~> 2.0

After performing the following modifications, you will be able to create accounts on behalf of users and a confirmation email will be sent to each user created. When the user clicks the link in the email to confirm their account, they will be asked to set their password. You can also (optionally) have a sign-up process that asks users for only their email, to make sign-up as simple as possible. Again, the user will be sent a confirmation email and during the confirmation process will be asked to set their password. If the user does not set their password, they will not be confirmed; they must set their password before their account can be confirmed.

If you use multiple resources (like :clients and :admins) there is an example at the bottom of this page.

1. Modify the #new view in app/view/users/registrations (optional)

We want to make this easy on the new user, so just ask for their email address and forget about passwords for now. (These examples use the formtastic and haml gems because they're both fantastic, they make the example cleaner, and it's less typing. Also see simple_form for easier form creation.)

This step is optional -- if you skip this step, the user will be asked to create a password and everything works as usual. But you'll need the following steps for the case when the user is created by an admin or the system, for instance a user contacts the business, their account is created by the business agent, the user is sent a confirmation email, and the user sets their password later during the confirmation process.

%h2 Sign up. All we need is your email address.
= semantic_form_for(resource, :as => resource_name, :url => user_registration_path(resource)) do |form|
  = devise_error_messages!
  = form.inputs do
    = form.input :email, :input_html => {:autofocus => true}
  = form.actions do
    = form.action :submit, :label => "Sign up"
= render 'shared/links'

2. Create a #show view in app/view/confirmations

In this view, we ask the new user to create a password and confirm it. We embed the confirmation_token in a hidden input field so that the controller will receive it. (We'll explain the confirm_path in a later step):

%h2 You're almost done! Now create a password to securely access your account.
= semantic_form_for(resource, :as => resource_name, :url => confirm_path) do |form|
  = devise_error_messages!
  = form.inputs do
    = form.input :password, :input_html => {:autofocus => true}
    = form.input :password_confirmation
    = form.input :confirmation_token, :as => :hidden
  = form.actions do
    = form.action :submit, :label => 'Confirm Account'

3. Create a #password_match? method and overwrite Devise's #password_required?

We need a convenient way to test that :password_confirmation matches :password and report any errors. We also need to overwrite Devise's #password_required? method so the user can sign up without specifying a password. (The #password_required? method can probably be skipped if you skipped step 1.) So in your model, add these public methods:

class User < ActiveRecord::Base
  def password_required?
    super if confirmed?
  end

  def password_match?
    self.errors[:password] << "can't be blank" if password.blank?
    self.errors[:password_confirmation] << "can't be blank" if password_confirmation.blank?
    self.errors[:password_confirmation] << "does not match password" if password != password_confirmation
    password == password_confirmation && !password.blank?
  end
end

4. Overwrite the Devise confirmations controller's #show

Create a new controller app/controllers/confirmations.rb to overwrite Devise's #show. If the user is already confirmed, they are only confirming a change in email address so call super. And do you remember calling #confirm_path in the #show view? The #confirm method will be called when the user submits the form. But we'll only confirm the user if :password matches :password_confirmation, otherwise re-render the #show view so the user can try again.

Because we call super if the user is already confirmed, the first step above is optional and only desired if you want to create a "gradual engagement" type of sign-up.

class ConfirmationsController < Devise::ConfirmationsController
  def show
    self.resource = resource_class.find_by_confirmation_token(params[:confirmation_token])
    super if resource.confirmed? 
  end

  def confirm
    self.resource = resource_class.find_by_confirmation_token(params[resource_name][:confirmation_token])
    if resource.update_attributes(params[resource_name]) && resource.password_match?
      self.resource = resource_class.confirm_by_token(params[resource_name][:confirmation_token])
      set_flash_message :notice, :confirmed
      sign_in_and_redirect(resource_name, resource)
    else
      render :action => "show"
    end
  end
end

5. Tell Devise to use the new controller

Finally, tell Devise about the new controller. (Notice the pluralized resource name in the #devise_for method call and the singular resource name in the #devise_scope method call):

devise_for :users, :controllers => {:sessions => 'sessions', :confirmations => 'confirmations'}

devise_scope :user do
  put "/confirm" => "confirmations#confirm"
end

Examples for multiple resources

Here's an example for multiple resources, which is why we use resource and resource_name and resource_class in the confirmations controller. We use named routes so Devise can detect the different resource scopes and use the same confirmations controller.

First, the routes:

devise_for :clients,     :controllers => {:confirmations => 'confirmations'}
devise_for :admins,      :controllers => {:confirmations => 'confirmations'}
devise_for :supervisors, :controllers => {:confirmations => 'confirmations'}

devise_scope :client do
  put "/clients/confirm" => "confirmations#confirm", :as => :client_confirm
end
devise_scope :admin do
  put "/admins/confirm" => "confirmations#confirm", :as => :admin_confirm
end
devise_scope :supervisor do
  put "/supervisors/confirm" => "confirmations#confirm", :as => :supervisor_confirm
end

Next, the #show view (we just change the confirm_path helper call):

%h2 You're almost done! Now create a password to securely access your account.
= semantic_form_for(resource, :as => resource_name, :url => send(:"#{resource_name}_confirm_path")) do |form|
  = devise_error_messages!
  = form.inputs do
    = form.input :password, :input_html => {:autofocus => true}
    = form.input :password_confirmation
    = form.input :confirmation_token, :as => :hidden
  = form.actions do
    = form.action :submit, :label => 'Confirm Account'
Clone this wiki locally