-
Notifications
You must be signed in to change notification settings - Fork 5.5k
How To: Email only sign up
Sometimes, you want a gradual engagement feature where a visitor signs up using only their email address. Or maybe an admin creates user accounts when a client contacts the business. Whatever the logic, the user creates their password after their account is created.
Users will get an email asking them to confirm their email address. A link in the email takes them to a webpage that asks them to set their password. If the user does not set their password, they are not confirmed, they must set their password in order to confirm their account.
You can also keep the default Devise sign up where a new user enters both an email and password. In that case, just skip the first step.
Note: if you use multiple user resource tables (like :clients and :admins) see the example at the bottom.
(This technique was first documented by Claudio Marai. The following steps combine Claudio's example code with code contributed in the comments section of his post and updates everything for Devise 2. NOTE: This is for Rails 3 and Devise ~> 2.0. The following view code uses formtastic and haml gems because they make the code cleaner, with less typing. See simple_form for another easy way to create forms.)
This step is optional: This modification allows a user to sign up with only their email address. Skip this step if you're only interested in allowing admins to create users with only an email address.
You want to make it easy for new users to sign up -- just ask for their email address and not worry about passwords for now. That's the "gradual engagement" approach!
%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'
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'
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. 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
Create a new controller app/controllers/confirmations_controller.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.
Calling super
if the user is already confirmed makes the first step (above) optional and only needed if you want a "gradual engagement" sign-up.
class ConfirmationsController < Devise::ConfirmationsController
def show
self.resource = resource_class.find_by_confirmation_token(params[:confirmation_token]) if params[:confirmation_token].present?
super if resource.nil? or resource.confirmed?
end
def confirm
self.resource = resource_class.find_by_confirmation_token(params[resource_name][:confirmation_token]) if params[resource_name][:confirmation_token].present?
if resource.update_attributes(params[resource_name].except(:confirmation_token)) && 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
For Rails 4/Devise 3: Strong attributes will require permit
, so be sure to include the passed parameters on the update_attributes
. For example,
Finally, tell Devise about the new controller and its #confirm action. (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 => {:confirmations => 'confirmations'}
devise_scope :user do
put "/confirm" => "confirmations#confirm"
end
For Rails 4 with Devise 3 use patch "/confirm" => "confirmations#confirm"
.
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
You have to create app/views/clients/registrations/new.html.haml, app/views/admins/registrations/new.html.haml, and app/views/supervisor/registrations/new.html.haml templates.
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'