[** UPDATE: 24/05/2012 **]: Grazie a Kevin Triplettora c'è una pagina sul wiki di Devise che fa riferimento a questo post combinando anche i vari commenti e contributi ricevuti. Visitate la pagina, soprattutto se utilizzate Rails 3 e Devise 2: https://github.com/plataformatec/devise/wiki/How-To:-Two-Step-Confirmation
Nei miei progetti in Ruby on Rails generalemente utilizzo Devise come gem per l'autenticazione degli utenti.
Nell'ultima applicazione sviluppata avevo necessità di personalizzare Devise in modo che gli utenti potessero registrarsi fornendo solo l'indirizzo email.
La password di accesso doveva essere impostata nello step di conferma dell'account.
Dopo alcuni test, sono giunto alla seguente soluzione.
1. Per prima cosa ho dovuto sovrascrivere il ConfirmationsController. Quindi nel file routes.rb ho impostato devise perchè utilizzase il mio controller custom (il mio model degli utenti si chiamava Account).
Ho dovuto sovrascrivere anche il RegistrationsController per personalizzare la pagina di registrazione:
devise_for :accounts, :controllers => {:confirmations => "confirmations", :registrations => "registrations"} do
put "confirm_account", :to => "confirmations#confirm_account"
end
Come vedete ho anche aggiunto un metodo custom, confirm_account che userò nel quarto step, come vedremo tra poco.
2. A questo punto devo fare in modo di evitare la validazione della password di devise.
Per questo scopo ho scritto un initializer chiamato devise_customization.rb in /config/initalizers/ come segue:
module Devise
module Models
module Validatable
def password_required?
false
end
end
end
end
In questo modo ho sovrascritto il modulo validatable di devise skippando la validazione della password.
3. Il passo successivo è la personalizzazione delle view.
Poichè utilizzo i miei ConfirmationsController e RegistrationsController custom ho copiato le view standard di devise sotto /views/confirmations/ per le confirmations e sotto /views/registrations/ per le registrations.
Le ho poi modificate come segue per adattarle al mio workflow di registrazione.
La view /registrations/new è diventata cosi (utilizzo haml per i layout)
= form_for(resource, :as => resource_name, :url => account_registration_path(@account)) do |f|
= devise_error_messages!
%p
= f.label :email
= f.email_field :email
%p.clearfix
= f.submit 'Signup'
= link_to 'Home', root_url
%br/
= render :partial => 'devise/shared/links'
In questa view ho semplicemente rimosso i campi password e password_confirmation.
La view /confirmations/show è invece diventata:
= form_for(resource, :url => confirm_account_path) do |f|
= devise_error_messages!
%p
= f.label :email
= @account.email
= f.hidden_field :confirmation_token
%p
= f.label :password
%br/
= f.password_field :password
%p
= f.label :password_confirmation
%br/
= f.password_field :password_confirmation
%p.clearfix
= f.submit 'Confirm Account'
= link_to 'Home', root_url
%br/
= render :partial => 'devise/shared/links'
4. Ok, ora che ho definito le mie view è tempo di scrivere il nuovo metodo di conferma dell'account nel ConfirmationsController. Posso invece lasciare inalterato il RegistrationsController.
class ConfirmationsController < Devise::ConfirmationsController
def show
@account = Account.find_by_confirmation_token(params[:confirmation_token])
if !@account.present?
render_with_scope :new
end
end
def confirm_account
@account = Account.find(params[:account][:confirmation_token])
if @account.update_attributes(params[:account]) and @account.password_match?
@account = Account.confirm_by_token(@account.confirmation_token)
set_flash_message :notice, :confirmed
sign_in_and_redirect("account", @account)
else
render :action => "show"
end
end
end
Il metodo show semplicemente si occupa di trovare l'account in base al token ricevuto e poi renderizzza la view show.
Il punto chiave è il metodo confirm_account dove viene trovato l'account, eseguito l'update dei suoi attributes e se il metodo password_match? ritorna true allora l'account viene confermato chiamando il metodo standard di devise confirm_by_token
5. L'ultima cosa che rimane da fare è definire il metodo password_match? all'interno del model Account.
class Account < ActiveRecord::Base
...
def password_match?
self.errors[:password] << 'password not match' if password != password_confirmation
self.errors[:password] << 'you must provide a password' if password.blank?
password == password_confirmation and !password.blank?
end
end
E questo è tutto!
Ora potete riavviare il server rails e registrarvi nella vostra applicazione scegliendo la password solo in fase di conferma dell'account.