I'm using Devise with Confirmable module enabled.
I realised that unconfirmed email column is only used when the user already has a confirmed email and wants to change it.
I have the following issue:
I think that on sign up page, the email should be stored in unconfirmed email column to avoid the behavior above.
There is a workaround to prevent this?
It is very interesting question. I think that the simplest way (but not sure that it is the best way) to prevent this problem is just to change email uniqueness validation and make it work only if email was confirmed.
To do this you should disallow validatable
module in your User model and implement validations manually.
You can copy all default validations from here https://github.com/plataformatec/devise/blob/master/lib/devise/models/validatable.rb, but validates_uniqueness_of
. Then implement your own email uniqueness validation:
class User < ActiveRecord::Base
# do not include :validatable module here
devise :confirmable, :database_authenticatable, :registerable, ...
# your own validations
...
validates_uniqueness_of :email, allow_blank: true, if: lambda { |u| u.email_changed? && u.confirmed? }
...
Edited:
My solution is not right. First of all, email can't be confirmed if it just changed. Even if my solution would work and allow user to register with existing unconfirmed email, when the user would try to confirm his email he would fail anyway.
The right solution is:
1) Add validation to prevent registration if email exists and confirmed.
2) Redefine #confirme!
method to don't confirm email if it exists and confirmed
3) (not neсessary) Redefine #after_confirmation
method to remove all other unconfirmed accounts with this email
class User < ActiveRecord::Base
# do not include :validatable module here
devise :confirmable, :database_authenticatable, :registerable # , ...
validate :confirmed_email_uniqueness
scope :with_confirmed_email, -> { where.not(confirmed_at: nil) }
scope :with_unconfirmed_email, -> { where(confirmed_at: nil) }
def confirm!
return false if confirmed_email_exists? # or add validation error, or raise some exception
super
end
private
def confirmed_email_uniqueness
errors.add(:email, "already exists") if email_changed? && confirmed_email_exists?
end
def confirmed_email_exists?
User.with_confirmed_email.where(email: self.email).exists?
end
protected
def after_confirmation
User.with_unconfirmed_email.where(email: self.email).destroy_all
super
end
...