Home > database >  How to validate uniqueness of a field before save, if the field is changed before save?
How to validate uniqueness of a field before save, if the field is changed before save?

Time:07-28

I have a User model that needs to validate the uniqueness of a field before it is saved, this field however, is changed before it is saved (it is hashed). How can I do this? I tried the following: Adding a validate digest at the end of the before save call, but that doesn't work.

class User < ApplicationRecord        
  has_secure_password
                                                
  before_save :downcase_email, :downcase_name, :digest_email, :validate_email_digest
                                                                             
  validates :email, format: { with: URI::MailTo::EMAIL_REGEXP }, presence: true 
  validates :name, length: { within: 2..40 }, presence: true, uniqueness: true
  validates :password_digest, presence: true
                                                               
  self.implicit_order_column = 'created_at'

  private     
                                   
  def validate_email_digest
    # This throws an exception                                  
    validates_uniqueness_of :email, message: 'That email is already in use.'
  end
  
  def downcase_email
    self.email = email.downcase
  end

  def downcase_name
    self.name = name.downcase
  end

  def digest_email
    self.email = Digest::SHA2.hexdigest(email).to_s
  end
end

How can I validate the result of the model before_save before they are saved to the database? In this case how can I validate that the digested email is unique after it's been digested by before_save :digest_email?

The steps I'm looking to perform:

  1. Validate the emails form (ie: test it against email regexp)
  2. Hash it
  3. Then validate again that the hash is unique within the table

I would prefer to do this all with validations, and keep my controllers form having much logic.

CodePudding user response:

So turns out for what I needed exactly I need to make a validate call and then verify the uniqueness that way. I used the following code:

class User < ApplicationRecord
  has_secure_password

  before_validation :downcase_name, :downcase_email
  before_save :digest_email

  validates :email, format: { with: URI::MailTo::EMAIL_REGEXP }, presence: true
  validates :name, length: { within: 2..40 }, presence: true, uniqueness: true
  validates :password, presence: true, confirmation: true
  validates :password_confirmation, presence: true, on: :create

  validate :verify_email_hash_uniqueness, on: :create

  self.implicit_order_column = 'created_at'

  private

  def downcase_email
    self.email = email.downcase
  end

  def downcase_name
    self.name = name.downcase
  end

  def digest_email
    self.email = Digest::SHA2.hexdigest(email).to_s
  end

  def verify_email_hash_uniqueness
    email_digest = Digest::SHA2.hexdigest(email).to_s
    errors.add(:base, 'Email is already in use') if User.where(email: email_digest).exists?
  end
end

This will test uniqueness AFTER it tests if the email is well formed, allowing me to hash emails before putting them into the database, verifying their form and uniqueness.

  • Related