0

email and new_email are two distinct columns. Every email should be unique, so if an email is added into either column it cannot already exist in either email or new_email columns.

Back story: I create the primary email for activated accounts and have a second new_email for when a user decides to change their email address but has not yet validated the new one via an email confirmation.

Most SO searches gave the scope solution:

I've tried validates :email, uniqueness: {scope: :new_email} and validates :new_email, uniqueness: {scope: :email} however I'm pretty sure this functionality acts to create a new key among the email1, email2 pair which is not the desired effect.

Currently I'm judging the validity of my code with the following two test cases (which are failing)

test "new_email should not match old email" do
  @user.new_email = @user.email
  assert_not @user.valid?
end

test "new_email addresses should be unique" do
  duplicate_user = @user.dup
  duplicate_user.new_email = @user.email.upcase
  duplicate_user.email = @user.email + "z"
  @user.save
  assert_not duplicate_user.valid?
end
Community
  • 1
  • 1
Alexei Darmin
  • 2,079
  • 1
  • 18
  • 30

3 Answers3

0

I think you really just want to write a custom validator, something like this:

validate :emails_are_unique

def emails_are_unique
  if email == new_email
    errors.add(:base, 'Your new email can not match your old email')
  end
end

Which will do what you are looking for.

TheDelChop
  • 7,938
  • 4
  • 48
  • 70
0

Scope's not going to cut it... it's really there to make sure that mail is unique amongst all records that might share the same new_mail and won't catch uniqueness for different new_email values, nor will it compare the values across the two columns.

Use standard 'uniqueness' to ensure there's no duplication within the column.

Then you'll need to create a custom validation for cross column...

validate :emails_unique

def emails_unique
  found_user = User.find_by(new_email: email) 
  errors.add(:email, "email is someone's new email") if found_user
  return unless new_email
  found_user = User.find_by(email: new_email) 
  errors.add(:new_email, "new email is someone's regular email") if found_user
end
SteveTurczyn
  • 36,057
  • 6
  • 41
  • 53
0

validates :email, uniqueness: {scope: :new_email} will ensure that email is unique among all records with the same value for new_email. This is not what you want.

You'll have to write a custom validation function, e.g.:

def validate_email_and_new_email_unique
  if email && self.class.exists?("email = :email OR new_email = :email", email: email)
    errors.add :email, "must be unique"
  end
  if new_email && self.class.exists?("email = :email OR new_email = :email", email: new_email)
    errors.add :new_email, "must be unique"
  end
end
eirikir
  • 3,802
  • 3
  • 21
  • 39