Devise Registration Tanpa Password, Set Password Setelah Confirmation
Prerequisite
ruby 3.0.0
rails 6.1.3
postgresql 12.5
rspec 4.0.0
Latar Belakang Masalah
Catatan kali ini tentang pemanfaatan Devise gem untuk registration tanpa menginputkan password, password akan diminta setelah user membuka link konfirmasi yang dikirimkan via email.
Pemecahan Masalah
Gemfile
- Devise, untuk authentication
- Simple Form, untuk menghandle form agar lebih praktis*
* Optional
1gem 'devise', '~> 4.7', '>= 4.7.3'2gem 'simple_form', '~> 5.1'
Jalankan bundle install,
$ bundle install
Kemudian install kedua gem tersebut ke dalam web aplikasi.
Dahulukan simple_form, dengan begitu form-form yang akan digenerate oleh Devise akan otomatis menggunakan form dari simple_form.
$ rails g simple_form:install
Setelah itu, devise.
$ rails g devise:install
ActionMailer
Selanjutnya konfigurasi ActionMailer untuk environment development.
1Rails.application.configure do2 # ...34 config.action_mailer.raise_delivery_errors = false5 config.action_mailer.perform_caching = false6 config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }7 config.action_mailer.delivery_method = :smtp8 config.action_mailer.smtp_settings = { address: '127.0.0.1', port: 1025 }910 # ...11end
Baris 7 dan 8, adalah configurasi untuk MailCatcher.
Untuk yang belum tahu MailCatcher, dapat dibaca di sini, Konfigurasi Ruby on Rails ActionMailer pada Local Environment dengan MailCatcher.
Devise Initializer
Kita perlu merubah beberapa konfigurasi pada Devise initializer.
1Devise.setup do |config|2 # ...34 # ==> Mailer Configuration5 # Configure the e-mail address which will be shown in Devise::Mailer,6 # note that it will be overwritten if you use your own mailer class7 # with default "from" parameter.8 config.mailer_sender = 'no-reply@example.com'910 # If true, requires any email changes to be confirmed (exactly the same way as11 # initial account confirmation) to be applied. Requires additional unconfirmed_email12 # db field (see migrations). Until confirmed, new email is stored in13 # unconfirmed_email column, and copied to email column on successful confirmation.14 config.reconfirmable = false1516 # ...17end
Ganti config.mailer_sender =
sesuai alamat yang teman-teman inginkan.
Ganti config.reconfirmable =
menjadi false.
Devise User Model
Kita akan membuat User model dengan devise.
$ rails g devise User
Buka migrationsnya, dan enablekan :confirmation_token
, :confirmed_at
, :confirmation_sent_at
pada bagian Confirmable.
1# frozen_string_literal: true23class DeviseCreateUsers < ActiveRecord::Migration[5.2]4 def change5 create_table :users do |t|6 # ...78 ## Confirmable9 t.string :confirmation_token10 t.datetime :confirmed_at11 t.datetime :confirmation_sent_at12 # t.string :unconfirmed_email # Only if using reconfirmable1314 # ...15 end1617 # ...18 end19end
Saya akan menambahkan filed name.
$ rails g migration add_name_to_users name:string
1class AddNameToUsers < ActiveRecord::Migration[5.2]2 def change3 add_column :users, :name, :string4 end5end
Sip, jalankan migration.
$ rails db:migrate
Aktifkan module :confirmable
pada user.rb model.
1class User < ApplicationRecord2 # Include default devise modules. Others available are:3 # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable4 devise :database_authenticatable, :registerable,5 :recoverable, :rememberable, :validatable,6 :confirmable7end
Sekalian, tambahkan logic untuk menghandle registration tanpa password di bawah module-module Devise tersebut.
1class User < ApplicationRecord2 # Include default devise modules. Others available are:3 # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable4 devise :database_authenticatable, :registerable,5 :recoverable, :rememberable, :validatable,6 :confirmable78 # new function to set the password without knowing the current9 # password used in our confirmation controller.10 def attempt_set_password(params)11 p = {}12 p[:password] = params[:password]13 p[:password_confirmation] = params[:password_confirmation]14 update_attributes(p)15 end1617 def password_match?18 self.errors[:password] << "can't be blank" if password.blank?19 self.errors[:password_confirmation] << "can't be blank" if password_confirmation.blank?20 self.errors[:password_confirmation] << "does not match password" if password != password_confirmation21 password == password_confirmation && !password.blank?22 end2324 # new function to return whether a password has been set25 def has_no_password?26 self.encrypted_password.blank?27 end2829 # Devise::Models:unless_confirmed` method doesn't exist in Devise 2.0.0 anymore.30 # Instead you should use `pending_any_confirmation`.31 def only_if_unconfirmed32 pending_any_confirmation {yield}33 end3435 def password_required?36 # Password is required if it is being set, but not for new records37 if !persisted?38 false39 else40 !password.nil? || !password_confirmation.nil?41 end42 end43end
Controller
Kita akan membuat 2 custom controller yang merupakan turunan dari Devise controller.
- RegistrationsController < Devise::RegistrationsController
- ConfirmationsController < Devise::ConfirmationsController
1class RegistrationsController < Devise::RegistrationsController2 private34 def sign_up_params5 params.require(:user).permit(:name, :email, :password, :password_confirmation)6 end78 def account_update_params9 params.require(:user).permit(:name, :email, :password, :password_confirmation, :current_password)10 end11end
1class ConfirmationsController < Devise::ConfirmationsController2 # Remove the first skip_before_filter (:require_no_authentication) if you3 # don't want to enable logged users to access the confirmation page.4 # If you are using rails 5.1+ use: skip_before_action5 # skip_before_filter :require_no_authentication6 # skip_before_filter :authenticate_user!78 # PUT /resource/confirmation9 def update10 with_unconfirmed_confirmable do11 if @confirmable.has_no_password?12 @confirmable.attempt_set_password(params[:user])13 if @confirmable.valid? and @confirmable.password_match?14 do_confirm15 else16 do_show17 @confirmable.errors.clear # so that we wont render :new18 end19 else20 @confirmable.errors.add(:email, :password_already_set)21 end22 end2324 if !@confirmable.errors.empty?25 self.resource = @confirmable26 render 'devise/confirmations/new' # Change this if you don't have the views on default path27 end28 end2930 # GET /resource/confirmation?confirmation_token=abcdef31 def show32 with_unconfirmed_confirmable do33 if @confirmable.has_no_password?34 do_show35 else36 do_confirm37 end38 end39 unless @confirmable.errors.empty?40 self.resource = @confirmable41 render 'devise/confirmations/new' # Change this if you don't have the views on default path42 end43 end4445 protected4647 def with_unconfirmed_confirmable48 @confirmable = User.find_or_initialize_with_error_by(:confirmation_token, params[:confirmation_token])49 if !@confirmable.new_record?50 @confirmable.only_if_unconfirmed {yield}51 end52 end5354 def do_show55 @confirmation_token = params[:confirmation_token]56 @requires_password = true57 self.resource = @confirmable58 render 'devise/confirmations/show' # Change this if you don't have the views on default path59 end6061 def do_confirm62 @confirmable.confirm63 set_flash_message :notice, :confirmed64 sign_in_and_redirect(resource_name, @confirmable)65 end66end
Baris baris ke 58, kita akan membuat sendiri custom view template tersebut.
Pada catatan ini, saya membuat homepage, untuk tempat bernaung setelah melakukan registrasi dan juga sebagai root_path.
$ rails g controller Home index
Routes
1Rails.application.routes.draw do2 # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html3 root to: 'home#index'45 devise_for :users, controllers: {6 registrations: 'registrations',7 confirmations: 'confirmations'8 }910 as :user do11 put '/users/confirmation' => 'confirmations#update', via: :patch, as: :update_user_confirmation12 end13end
Devise Views
Kita akan mengenerate Devise views.
$ rails g devise:views
Yang perlu dimodifikasi adalah:
- mengedit registrations/new
- menambahkan field name
- menghilangkan field password & password_confirmation
- membuat confirmations/show
- meletakkan field password & password_confirmation
Modifikasi view template registrations/new.
1<h2>Sign up</h2>23<%= simple_form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>4 <%= f.error_notification %>56 <div class="form-inputs">7 <%= f.input :name,8 required: true,9 autofocus: true,10 input_html: { autocomplete: "name" }%>11 <%= f.input :email,12 required: true,13 input_html: { autocomplete: "email" }%>1415 <%# :password & :password_confirmation, dipindahkan ke views/devise/confirmations/show.html.erb %>16 </div>1718 <div class="form-actions">19 <%= f.button :submit, "Sign up" %>20 </div>21<% end %>2223<%= render "devise/shared/links" %>
Gambar 1. Sign up form or Registration form
Dapat dilihat, pada form registrasi ini, hanya menampilkan input field berupa name dan email.
Saya memindahkan field password
dan password_confirmation
ke halaman yang lain, yaitu halaman views/devise/confirmations/show.html.erb.
1<h2>Account Activation<% if resource.try(:user_name) %> for <%= resource.user_name %><% end %></h2>23<%= simple_form_for(resource, as: resource_name, url: update_user_confirmation_path, html: { method: :put }) do |f| %>4 <%= devise_error_messages! %>56 <div class="form-inputs">7 <% if @requires_password %>8 <%= f.input :password,9 required: true,10 autofocus: true,11 hint: ("#{@minimum_password_length} characters minimum" if @minimum_password_length),12 input_html: { autocomplete: "new-password" } %>13 <%= f.input :password_confirmation,14 required: true,15 input_html: { autocomplete: "new-password" } %>16 <% end %>1718 <%= hidden_field_tag :confirmation_token,@confirmation_token %>19 </div>2021 <div class="form-actions">22 <%= f.button :submit, "Activate" %>23 </div>24<% end %>
Gambar 2. Account Activation form
1<h2>Resend confirmation instructions</h2>23<%= simple_form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f| %>4 <%= f.error_notification %>5 <%= f.full_error :confirmation_token %>67 <div class="form-inputs">8 <%= f.input :email,9 required: true,10 autofocus: true,11 value: (resource.pending_reconfirmation? ? resource.unconfirmed_email : resource.email),12 input_html: { autocomplete: "email" } %>13 </div>1415 <div class="form-actions">16 <% if resource.pending_reconfirmation? %>17 <%= f.button :submit, "Resend confirmation instructions" %>18 <% end %>19 </div>20<% end %>2122<% unless user_signed_in? %>23 <%= render "devise/shared/links" %>24<% end %>
Pasang nav untuk menempatkan link indikator apabila user telah login atau belum.
1<!DOCTYPE html>2<html>3 <head>4 <!-- ... -->5 </head>67 <body>8 <nav style="margin-bottom: 20px;">9 <% if user_signed_in? %>10 <%= link_to current_user.name, edit_user_registration_path, class:"navbar-item" %>11 <%= link_to "Log Out", destroy_user_session_path, method: :delete, class:"navbar-item" %>12 <% else %>13 <% unless [14 new_user_session_path,15 new_user_registration_path,16 new_user_confirmation_path,17 user_confirmation_path,18 new_user_password_path19 ].include? request.path %>20 <%= link_to "Sign In", new_user_session_path, class:"navbar-item" %>21 <%= link_to "Sign up", new_user_registration_path, class:"navbar-item"%>22 <% end %>23 <% end %>24 </nav>2526 <%= yield %>27 </body>28</html>
Demonstrasi
Gambar 3. Demonstrasi register and activation
Repositori
github.com/bandithijo/demo_devise_confirmable
Pesan Penulis
Sepertinya, segini dulu yang dapat saya tuliskan.
Selanjutnya, saya serahkan kepada imajinasi dan kreatifitas teman-teman. Hehe.
Mudah-mudahan dapat bermanfaat.
Terima kasih.
(^_^)
Referensi
-
mailcatcher.me
Diakses tanggal: 2021/03/13 -
Let’s Build: With Ruby on Rails - Project Management App - Part 2
Diakses tanggal: 2021/03/13