Tags

, , , ,

After implementing devise, I wanted to allow user to sign up using only email. An mail will be sent to the provided mail id which will contain a link to set password. I searched a lot but did not find perfect example. I referred https://github.com/plataformatec/devise/wiki/How-To:-Override-confirmations-so-users-can-pick-their-own-passwords-as-part-of-confirmation-activation which helped a lot but still there were issues.

Below are steps that will help you create sign up using only mail successfully.

  1. In user model uncomment the “confirmable” keyword.

devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable,
:confirmable

2. Generate migration to add field required for “confirmable” feature

class AddUserConfirmable < ActiveRecord::Migration
def self.up
add_column :users, :confirmation_token, :string
add_column :users, :confirmed_at, :datetime
add_column :users, :confirmation_sent_at, :datetime
add_column :users, :unconfirmed_email, :string
add_index :users, :confirmation_token, :unique => true

def self.down
remove_column :users, [:confirmed_at, :confirmation_token, :confirmation_sent_at]
end
end

3. Run the migration.

4. Create confirmations controller under app/controllers/users

class Users::ConfirmationsController < Devise::ConfirmationsController

# Remove the first skip_before_filter (:require_no_authentication) if you
# don’t want to enable logged users to access the confirmation page.
skip_before_filter :require_no_authentication
skip_before_filter :authenticate_user!

# PUT /resource/confirmation
def update
with_unconfirmed_confirmable do
if @confirmable.has_no_password?
@confirmable.attempt_set_password(params[:user])
if @confirmable.valid? and @confirmable.password_match?
do_confirm
else
do_show
@confirmable.errors.clear #so that we wont render :new
end
else
@confirmable.errors.add(:email, :password_already_set)
end
end

if !@confirmable.errors.empty?
self.resource = @confirmable
render ‘devise/confirmations/new’ #Change this if you don’t have the views on default path
end
end

# GET /resource/confirmation?confirmation_token=abcdef
def show
with_unconfirmed_confirmable do
if @confirmable.has_no_password?
do_show
else
do_confirm
end
end
unless @confirmable.errors.empty?
self.resource = @confirmable
render ‘devise/confirmations/new’ #Change this if you don’t have the views on default path
end
end

protected

def with_unconfirmed_confirmable
@confirmable = User.find_or_initialize_with_error_by(:confirmation_token, params[:confirmation_token])
if !@confirmable.new_record?
@confirmable.only_if_unconfirmed {yield}
end
end

def do_show
@confirmation_token = params[:confirmation_token]
@requires_password = true
self.resource = @confirmable
render ‘devise/confirmations/show’ #Change this if you don’t have the views on default path
end

def do_confirm
@confirmable.confirm!
set_flash_message :notice, :confirmed
sign_in_and_redirect(resource_name, @confirmable)
end

# GET /resource/confirmation/new
# def new
#   super
# end

# POST /resource/confirmation
# def create
#   super
# end

# GET /resource/confirmation?confirmation_token=abcdef
# def show
#   super
# end

# protected

# The path used after resending confirmation instructions.
# def after_resending_confirmation_instructions_path_for(resource_name)
#   super(resource_name)
# end

# The path used after confirmation.
# def after_confirmation_path_for(resource_name, resource)
#   super(resource_name, resource)
# end
end

5. Add following methods to user model

# User Model
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable,
:confirmable

# Code for Email only signup
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

# new function to set the password without knowing the current
# password used in our confirmation controller.
def attempt_set_password(params)
p = {}
p[:password] = params[:password]
p[:password_confirmation] = params[:password_confirmation]
update_attributes(p)
end

# new function to return whether a password has been set
def has_no_password?
self.encrypted_password.blank?
end

# Devise::Models:unless_confirmed` method doesn’t exist in Devise 2.0.0 anymore.
# Instead you should use `pending_any_confirmation`.
def only_if_unconfirmed
pending_any_confirmation {yield}
end

# Code for soft delete user account
def soft_delete
update_attribute(:deleted_at, Time.current)
end

# ensure user account is active
def active_for_authentication?
super && !deleted_at
end

# provide a custom message for a deleted account
def inactive_message
!deleted_at ? super : :deleted_account
end

private

# Code used for password change
def needs_password_change_email?
encrypted_password_changed? && persisted?
end

def send_password_change_email
UserMailer.password_changed(id).deliver
end

end

6. Add following routes in routes.rb

as :user do
patch ‘/user/confirmation’ => ‘users/confirmations#update’, :via => :patch, :as => :update_user_confirmation
end

devise_for :users, :controllers => { :confirmations => “users/confirmations” }

7. Create view “show.html.erb” to show password confirmation in app/views/devise/confirmation

<h2>Account Activation</h2>

<%#= form_for resource, :as => resource_name, :url => update_user_confirmation_path, :html => {:method => ‘put’}, :id => ‘activation-form’ do |f| %>

<%= form_for resource, :as => resource_name, :url => update_user_confirmation_path, :html => {:method => ‘patch’}, :id => ‘activation-form’ do |f| %>
<%= devise_error_messages! %>
<fieldset>
<legend>Account Activation<%# if resource.user_name %> for <%#= resource.user_name %><%# end %></legend>

<% if @requires_password %>
<p><%= f.label :password,’Choose a Password:’ %> <%= f.password_field :password %></p>
<p><%= f.label :password_confirmation,’Password Confirmation:’ %> <%= f.password_field :password_confirmation %></p>
<% end %>
<%= hidden_field_tag :confirmation_token,@confirmation_token %>
<p><%= f.submit “Activate” %></p>
</fieldset>
<% end %>

8. Add following changes to new.html.erb in app/views/devise/registrations to hide the password related text fields. The password related fields are commented.

<h2>Sign up</h2>

<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
<%= devise_error_messages! %>

<!–

( characters minimum)

–>

<% end %>

<%= render “devise/shared/links” %>

That’s it. You can now see the sign up using email is working.