Custom Validations for ActiveRecord

I want to create a model validation that ensures that the two passwords entered on the password creation page are equivalent. Sure I could put this into my controller but that wouldn't be as clean. So I created two attributes in my model for the hashed password entries...

password_hash and password1_hash...

then I created a custom validation called validates_equality_of that takes three arguments - the two attributes to compare and a descriptor of what those attributes are for the error code.

Now I think I'm a genius...only problem is when the validator executes I get a run time error stating that my 'errors' variable is undefined...this should not happen. Can someone look at this snippet of code and tell me what I've done wrong?

require 'digest/sha2' require 'rubygems' require 'breakpoint' require 'logger'

module ActiveRecord   module Validations     module ClassMethods       def validates_equality_of(attr1, attr2, description)         $LOG.debug( "In validates_equality_of") if $LOG != nil         raise "description must be a string" if !description.is_a?( String)         if ( attr1 != attr2)             $LOG.debug( "attributes not the same") if $LOG != nil             errors.add( attr2, "Both " + description + " must be the same")           else             $LOG.debug( "Attributes the same") if $LOG != nil           end         end     end   end end

class User < ActiveRecord::Base   validates_uniqueness_of :username   validates_equality_of :password_hash, :password1_hash, "passwords"

  def self.authenticate( username, password)     user = User.find( :first, :conditions => ['username = ?', username])     if user.blank? ||       Digest::SHA256.hexdigest(password + user.password_salt) != user.password_hash       raise "Username or Password invalid"     end     user   end

  def password=(pass)     $LOG.debug( "in password=")     if !self.password_salt       salt = [Array.new(6){rand(256).chr}.join].pack('m').chomp       self.password_salt, self.password_hash = salt, Digest::SHA256.hexdigest(pass + salt)     else       self.password_hash = Digest::SHA256.hexdigest(pass + self.password_salt)     end   end

  def password1=(pass)     $LOG.debug( "in password1=")     if !self.password_salt       salt = [Array.new(6){rand(256).chr}.join].pack('m').chomp       self.password_salt, self.password1_hash = salt, Digest::SHA256.hexdigest(pass + salt)     else       self.password1_hash = Digest::SHA256.hexdigest(pass + self.password_salt)     end   end    end

zdennis - that does not work - record is also not defined...

from what I can tell all the calls you can use to get a record in scope only work with one attribute at a time...

Still wondering...

Jonathan,you have a very good point...why is that not called validates_password or something equivalent...

I'm still interested in why my code doesn't work...

GreenRuby wrote:

Jonathan,you have a very good point...why is that not called validates_password or something equivalent...

I'm still interested in why my code doesn't work...

Your code doesn't work because it's being run in the wrong context. You're defining a class-level method that needs to be run in the instance context.

The validates_* methods don't actually run the validation code - they generate code to be run when valid? is called. Basically, your method, instead of checking the record's attributes and adding an error, should add a hook to be called on validation.

You could do:

validate do |record|   record.errors.add(attr1, message) unless record.send(attr1) == record.send(attr2) end

However, this sort of breaks the convetions the Rails validations tend to use, and isn't really configurable. Have a look at validations.rb in the ActiveRecord source if you want to see how validations work.