reconfirmable uniqueness validations
This commit is contained in:
parent
6469cbc62a
commit
10ac4dbc35
|
@ -28,6 +28,33 @@ module Devise
|
||||||
module Confirmable
|
module Confirmable
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
# email uniqueness validation in unconfirmed_email column, works only if unconfirmed_email is defined on record
|
||||||
|
class ConfirmableValidator < ActiveModel::Validator
|
||||||
|
def validate(record)
|
||||||
|
if unconfirmed_email_defined?(record) && email_exists_in_unconfirmed_emails?(record)
|
||||||
|
record.errors.add(:email, :taken)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
protected
|
||||||
|
def unconfirmed_email_defined?(record)
|
||||||
|
record.respond_to?(:unconfirmed_email)
|
||||||
|
end
|
||||||
|
|
||||||
|
def email_exists_in_unconfirmed_emails?(record)
|
||||||
|
query = record.class
|
||||||
|
unless record.new_record?
|
||||||
|
if record.respond_to?(:_id)
|
||||||
|
query = query.where(:_id => {'$ne' => record._id})
|
||||||
|
else
|
||||||
|
query = query.where('id <> ?', record.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
query = query.where(:unconfirmed_email => record.email)
|
||||||
|
query.exists?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
included do
|
included do
|
||||||
before_create :generate_confirmation_token, :if => :confirmation_required?
|
before_create :generate_confirmation_token, :if => :confirmation_required?
|
||||||
after_create :send_confirmation_instructions, :if => :confirmation_required?
|
after_create :send_confirmation_instructions, :if => :confirmation_required?
|
||||||
|
@ -161,9 +188,10 @@ module Devise
|
||||||
# field. If no user is found, returns a new user with an email not found error.
|
# field. If no user is found, returns a new user with an email not found error.
|
||||||
# Options must contain the user email
|
# Options must contain the user email
|
||||||
def send_confirmation_instructions(attributes={})
|
def send_confirmation_instructions(attributes={})
|
||||||
confirmable = find_or_initialize_with_errors(confirmation_keys, attributes, :not_found)
|
confirmable = find_by_unconfirmed_email_with_errors(attributes) if reconfirmable
|
||||||
temp = find_by_unconfirmed_email(confirmation_keys, attributes, :not_found)
|
unless confirmable.try(:persisted?)
|
||||||
confirmable = temp if temp.persisted?
|
confirmable = find_or_initialize_with_errors(confirmation_keys, attributes, :not_found)
|
||||||
|
end
|
||||||
confirmable.resend_confirmation_token if confirmable.persisted?
|
confirmable.resend_confirmation_token if confirmable.persisted?
|
||||||
confirmable
|
confirmable
|
||||||
end
|
end
|
||||||
|
@ -184,10 +212,11 @@ module Devise
|
||||||
end
|
end
|
||||||
|
|
||||||
# Find a record for confirmation by unconfirmed email field
|
# Find a record for confirmation by unconfirmed email field
|
||||||
def find_by_unconfirmed_email(required_attributes, attributes, error=:invalid)
|
def find_by_unconfirmed_email_with_errors(attributes = {})
|
||||||
confirmation_keys_with_replaced_email = required_attributes.map{ |k| k == :email ? :unconfirmed_email : k }
|
unconfirmed_required_attributes = confirmation_keys.map{ |k| k == :email ? :unconfirmed_email : k }
|
||||||
attributes[:unconfirmed_email] = attributes.delete(:email)
|
unconfirmed_attributes = attributes.symbolize_keys
|
||||||
find_or_initialize_with_errors(confirmation_keys_with_replaced_email, attributes, :not_found)
|
unconfirmed_attributes[:unconfirmed_email] = unconfirmed_attributes.delete(:email)
|
||||||
|
find_or_initialize_with_errors(unconfirmed_required_attributes, unconfirmed_attributes, :not_found)
|
||||||
end
|
end
|
||||||
|
|
||||||
Devise::Models.config(self, :confirm_within, :confirmation_keys, :reconfirmable)
|
Devise::Models.config(self, :confirm_within, :confirmation_keys, :reconfirmable)
|
||||||
|
|
|
@ -25,6 +25,7 @@ module Devise
|
||||||
validates_presence_of :email, :if => :email_required?
|
validates_presence_of :email, :if => :email_required?
|
||||||
validates_uniqueness_of :email, :case_sensitive => (case_insensitive_keys != false), :allow_blank => true, :if => :email_changed?
|
validates_uniqueness_of :email, :case_sensitive => (case_insensitive_keys != false), :allow_blank => true, :if => :email_changed?
|
||||||
validates_format_of :email, :with => email_regexp, :allow_blank => true, :if => :email_changed?
|
validates_format_of :email, :with => email_regexp, :allow_blank => true, :if => :email_changed?
|
||||||
|
validates_with Devise::Models::Confirmable::ConfirmableValidator
|
||||||
|
|
||||||
validates_presence_of :password, :if => :password_required?
|
validates_presence_of :password, :if => :password_required?
|
||||||
validates_confirmation_of :password, :if => :password_required?
|
validates_confirmation_of :password, :if => :password_required?
|
||||||
|
|
|
@ -179,6 +179,12 @@ class ConfirmationTest < ActionController::IntegrationTest
|
||||||
end
|
end
|
||||||
|
|
||||||
class ConfirmationOnChangeTest < ConfirmationTest
|
class ConfirmationOnChangeTest < ConfirmationTest
|
||||||
|
|
||||||
|
def create_second_user(options={})
|
||||||
|
@user = nil
|
||||||
|
create_user(options)
|
||||||
|
end
|
||||||
|
|
||||||
def setup
|
def setup
|
||||||
Devise.reconfirmable = true
|
Devise.reconfirmable = true
|
||||||
end
|
end
|
||||||
|
@ -214,16 +220,19 @@ class ConfirmationOnChangeTest < ConfirmationTest
|
||||||
assert user.reload.confirmed?
|
assert user.reload.confirmed?
|
||||||
end
|
end
|
||||||
|
|
||||||
test 'user who changed email should get a detailed message about email being not unique' do
|
test 'user email should be unique also within unconfirmed_email' do
|
||||||
user = create_user(:confirm => true)
|
user = create_user(:confirm => true)
|
||||||
user.update_attributes(:email => 'new_test@example.com')
|
user.update_attributes(:email => 'new_test@example.com')
|
||||||
assert 'new_test@example.com', user.unconfirmed_email
|
assert 'new_test@example.com', user.unconfirmed_email
|
||||||
|
|
||||||
@user = nil
|
get new_user_registration_path
|
||||||
create_user(:email => 'new_test@example.com', :confirm => true)
|
|
||||||
|
|
||||||
visit_user_confirmation_with_token(user.confirmation_token)
|
fill_in 'email', :with => 'new_test@example.com'
|
||||||
|
fill_in 'password', :with => 'new_user123'
|
||||||
|
fill_in 'password confirmation', :with => 'new_user123'
|
||||||
|
click_button 'Sign up'
|
||||||
|
|
||||||
|
assert_have_selector '#error_explanation'
|
||||||
assert_contain /Email.*already.*taken/
|
assert_contain /Email.*already.*taken/
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -312,4 +312,12 @@ class ConfirmableOnChangeTest < ConfirmableTest
|
||||||
assert confirmation_user.errors[:email]
|
assert confirmation_user.errors[:email]
|
||||||
assert_equal "not found", confirmation_user.errors[:email].join
|
assert_equal "not found", confirmation_user.errors[:email].join
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test 'should find user with email in unconfirmed_emails' do
|
||||||
|
user = create_user
|
||||||
|
user.unconfirmed_email = "new_test@email.com"
|
||||||
|
assert user.save
|
||||||
|
user = User.find_by_unconfirmed_email_with_errors(:email => "new_test@email.com")
|
||||||
|
assert user.persisted?
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -105,6 +105,18 @@ class ValidatableTest < ActiveSupport::TestCase
|
||||||
assert_equal 'is too long (maximum is 128 characters)', user.errors[:password].join
|
assert_equal 'is too long (maximum is 128 characters)', user.errors[:password].join
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test 'should check if email is unique in unconfirmed_email column' do
|
||||||
|
swap Devise, :reconfirmable => [:username, :email] do
|
||||||
|
user = create_user
|
||||||
|
user.update_attributes({:email => 'new_test@email.com'})
|
||||||
|
assert 'new_test@email.com', user.unconfirmed_email
|
||||||
|
|
||||||
|
@user = nil
|
||||||
|
user = new_user(:email => 'new_test@email.com')
|
||||||
|
assert user.invalid?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
test 'shuold not be included in objects with invalid API' do
|
test 'shuold not be included in objects with invalid API' do
|
||||||
assert_raise RuntimeError do
|
assert_raise RuntimeError do
|
||||||
Class.new.send :include, Devise::Models::Validatable
|
Class.new.send :include, Devise::Models::Validatable
|
||||||
|
|
Loading…
Reference in New Issue