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.