Rails Devise: after_confirmation - ruby-on-rails

Is there a way to create a after_confirmation :do_something ?
The goal is to send an e-mail after the user confirms using Devise :confirmable.

I'm using Devise 3.1.2, it has a placeholder method after_confirmation which is called after the confirmation finished successfully. We just need to override this method in User model.
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :confirmable
# Override Devise::Confirmable#after_confirmation
def after_confirmation
# Do something...
end
end
See: Devise 3.5.9 Source Code: https://github.com/plataformatec/devise/blob/d293e00ef5f431129108c1cbebe942b32e6ba616/lib/devise/models/confirmable.rb

For new versions of devise 3.x :
See a different answer http://stackoverflow.com/a/20630036/2832282
For old versions of devise 2.x :
(Original answer)
but you should be able to put a before_save callback on the user (extra credit for using an observer) and check if confirmed_at was just set by devise. You can do something like:
send_the_email if self.confirmed_at_changed?
http://api.rubyonrails.org/classes/ActiveModel/Dirty.html for more details on checking the change on the field.

You can override the confirm! method:
def confirm!
super
do_something
end
Discussion about the topic is at https://github.com/plataformatec/devise/issues/812. They say that there are no callbacks like after_confirmation :do_something because that approach would require a lot of different callbacks.

Rails 4:
combining multiple answers above
def first_confirmation?
previous_changes[:confirmed_at] && previous_changes[:confirmed_at].first.nil?
end
def confirm!
super
if first_confirmation?
# do first confirmation stuff
end
end

according to the Devise 3.5.9 source code, you can simply define a method on the Devise Resource model, e.g.:
class User < ActiveRecord::Base
...
def after_confirmation
do_something
end
end
See: Devise 3.5.9 Source Code: https://github.com/plataformatec/devise/blob/d293e00ef5f431129108c1cbebe942b32e6ba616/lib/devise/models/confirmable.rb

I don't see that callback too, maybe you can try to override the confirmation method and call your callback there.
def send_confirmation_instructions(attributes={})
super(attributes)
your_method_here
end

You can override the confirm! method on your model
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :confirmable
def confirm!
super
do_something
end
end
There is a discussion about the topic is https://github.com/plataformatec/devise/issues/812. I tried this way, and it worked great.

We're combining answers from #BernĂ¡t and #RyanJM:
def confirm!
super
if confirmed_at_changed? and confirmed_at_was.nil?
do_stuff
end
end
This seems a bit more performance aware and safe than the two answers separately.

Related

Anonymous user in devise - rails

I'm new to rails and I tried to make simple authentication with anonymous user. I followed this tutorial and I have this error:
undefined method `find_or_initialize_by_token'
This is my AnonymousUser model:
class AnonymousUser < User
ACCESSIBLE_ATTRS = [:name, :email]
attr_accessible *ACCESSIBLE_ATTRS, :type, :token, as: :registrant
def register(params)
params = params.merge(type: 'User', token: nil)
self.update_attributes(params, as: :registrant)
end
end
This is my User model:
class User < ActiveRecord::Base
devise :database_authenticatable, :confirmable, :lockable, :recoverable,
:rememberable, :registerable, :trackable, :timeoutable, :validatable,
:token_authenticatable
attr_accessible :email, :password, :password_confirmation, :remember_me
end
And the last one important is my ApplicationController which has this error:
class ApplicationController < ActionController::Base
protect_from_forgery
def authenticate_user!(*args)
current_user.present? || super(*args)
end
def current_user
super || AnonymousUser.find_or_initialize_by_token(anonymous_user_token).tap do |user|
user.save(validate: false) if user.new_record?
end
end
private
def anonymous_user_token
session[:user_token] ||= SecureRandom.hex(8)
end
end
Someone told me that if AnonymousUser user inherits from User then AnonymousUser have method called find_or_initialize_by_token, but i don't know how to fix it.
Provided you have latest rails installed, try to refactor:
# in ApplicationController#current_user
AnonymousUser.find_or_initialize_by_token(anonymous_user_token).tap do |user|
user.save(validate: false) if user.new_record?
end
to something like this:
AnonymousUser.safely_find(anonymous_user_token)
and push the find_or_initialize_by_token and save(validate: false) into the model.
I wrote the blog post you referenced, but today, I would use
AnonymousUser.where(anonymous_user_token: anonymous_user_token).first_or_initialize
Dynamic finders have been deprecated AFAIK.
However, #Saurabh Jain is absolutely correct in his suggestion to refactor that block into a nice little push-button class method on the AnonymousUser.

unique after_sign_out paths with multiple models with Devise and rails

I have a rails 3.2 app with Devise 2.1
I have 2 models using devise (AdminUser and User)
Models:
class AdminUser < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
end
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
end
I have generated separate views for both models via the devise generator.
views/devise folder for AdminUser (implemented months earlier before new requirement)
views/users folder for User model
After signout, I want to redirect to specific actions that match the devise models. The code below works in application_controller.rb but it is applying to both models which I want to do without:
def after_sign_out_path_for(user)
user_landing_path
end
Signing out of either model redirects to the same landing page, but i would like to have a unique destination for both devise models.
How can I achieve this?
I figured out what seems to be a solution after looking at some examples here
http://eureka.ykyuen.info/2011/03/10/rails-redirect-previous-page-after-devise-sign-in/
def after_sign_out_path_for(resource_or_scope)
case resource_or_scope
when :user, User
user_landing_path
when :admin_user, AdminUser
admin_user_landing_path
else
super
end
end
Some things you could do:
case user.class
when AdminUser.class
do_admin_sign_out()
when User.class
do_user_sign_out()
else
no_idea_who_you_are()
end
or
if user.kind_of? AdminUser
do_admin_thing()
else
do_user_thing()
end
alternatively, you could add an admin? check to both models, and check that, ie:
if user.admin?
do_admin_thing()
...
I would probably do the later, as this might come up else where, but these are among your options.

devise_invitable: Confirm after Invitation

I override devise's confirm! method to send a welcome message to my users:
class User < ActiveRecord::Base
devise :invitable, :database_authenticatable, :registerable, :recoverable,
:rememberable, :confirmable, :validatable, :encryptable
# ...
# Devise confirm! method overriden
def confirm!
UserMailer.welcome_alert(self).deliver
super
end
end
With devise_invitable when the user accept the invitation and set his password the confirm! method is never triggered, is it possible to force it? How does devise_invitable confirms the User?
Or maybe I can override the accept_invite (or whatever its called) method the same way?
I want that invited users remain unconfirmed, and then confirmed upon accepting the invitation.
Thanks, any help very appreciated!
Original Source
UPDATE
Looking through devise_invitable model I found the two methods who may be causing this misbehavior:
# Accept an invitation by clearing invitation token and confirming it if model
# is confirmable
def accept_invitation!
if self.invited? && self.valid?
self.invitation_token = nil
self.save
end
end
# Reset invitation token and send invitation again
def invite!
if new_record? || invited?
#skip_password = true
self.skip_confirmation! if self.new_record? && self.respond_to?(:skip_confirmation!)
generate_invitation_token if self.invitation_token.nil?
self.invitation_sent_at = Time.now.utc
if save(:validate => self.class.validate_on_invite)
self.invited_by.decrement_invitation_limit! if self.invited_by
!!deliver_invitation unless #skip_invitation
end
end
end
class User < ActiveRecord::Base
devise :invitable, :database_authenticatable, :registerable, :recoverable,
:rememberable, :confirmable, :validatable, :encryptable
# ...
# devise confirm! method overriden
def confirm!
welcome_message
super
end
# devise_invitable accept_invitation! method overriden
def accept_invitation!
self.confirm!
super
end
# devise_invitable invite! method overriden
def invite!
super
self.confirmed_at = nil
self.save
end
private
def welcome_message
UserMailer.welcome_message(self).deliver
end
end
I tried benoror's answer and at first it appeared to work - but when you a user accepts the invitation and fills in the form as invalid it will actually override the token invalidating the invitation.
Instead, a callback is available to do this:
class User < ActiveRecord::Base
devise :invitable, :database_authenticatable, :registerable, :recoverable,
:rememberable, :confirmable, :validatable, :encryptable
after_invitation_accepted :send_welcome_email
def send_welcome_email
end
end

How do I test my devise user model validations using RSpec?

I can find recommendations for testing devise user controllers and views in RSpec. I've also seen suggestions that the devise gem code is already tested so it's not useful to spend a lot of time reinventing the wheel.
However, my user model has other fields that I need validated when the user signs up. I'm using standard validates... statements in the user.rb model. For example:
validates_presence_of :nickname
I'm trying to use simple validation testing in my user_spec.rb, but when I try to create the user like this:
record = Factory.create(:user)
I get this error:
undefined method `encode!' for "Confirmation":String
The encode! method is not coming from my code, it must be one of the gems that devise is using, but I haven't been able to find it, yet.
I've tried creating the user using User.new and Factory Girl. I get the same error either way. This spec was passing until I did an update of all my gems. Unfortunately I didn't keep a note of everything that got updated at the time. I've tried rolling devise back to previous versions but still get the same error.
Rails 3, RSpec2
Thanks for any advice.
It seems to be fine, may be, my testing code helps you out:
user_spec.rb
require 'spec_helper'
describe User do
before :each do
#user = Factory.build(:user)
end
it "should not be valid without a first_name" do
#user.first_name = nil
#user.should_not be_valid
end
end
user.rb (Model)
class User < ActiveRecord::Base
validates_presence_of :first_name
# Include default devise modules. Others available are:
# :token_authenticatable, :confirmable, :lockable and :timeoutable
devise :database_authenticatable, :registerable, :lockable,
:recoverable, :rememberable, :trackable
# Setup accessible (or protected) attributes for your model
attr_accessible :login, :first_name, :email, :password, :password_confirmation, :remember_me
attr_accessor :login
devise :database_authenticatable, :recoverable, :validatable
protected
def password_required?
!persisted? || password.present? || password_confirmation.present?
end
end

Devise "Creating New Session" is invoking Model's :on => :create validators

I have Devise setup on a Rails Model:
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable
I also have validation on the same model:
before_validation :geocode_address, :on => :create
When I create new User the geocode_address gets called which is what I want to do but it also gets kicked when the user logs in (creates new Devise Session) which is what I don't want.
Do you know how I can fix that?
This probably happens because the model gets validated on user log in as well. I think that it would be a better idea to utilize after_create on your model, like :
after_create :your_method
def your_method
...
end
Details : http://ar.rubyonrails.org/classes/ActiveRecord/Callbacks.html#M000061

Resources