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

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

GreenRuby wrote:

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?

You are saying ‘errors.add( … )’. You haven’t defined errors as a
local variable so ruby is thinking you are calling a method.

You need to say @errors.add( … )’

Zach
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.2.2 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org

iD8DBQFE86f/Myx0fW1d8G0RAkSlAJ95y8Ft2Fu7L3PIg6Rk8LoggH4LsgCcDz/I
JPLuZhul7vqSFPjFFtgKqU8=
=8x2g
-----END PGP SIGNATURE-----

Can I ask why you’re not using built in validates_confirmation_of?
Also check out the LoginEngine, the code should help.

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

  • From my last post, rather the trying @errors.add( … ) try:
    record.errors.add( … )

Zach
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.2.2 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org

iD8DBQFE86lLMyx0fW1d8G0RArNJAJ9mhIcNkQuheho43ePhBGDM996iNgCfXncJ
HqlmyRKwWmNFWywUiiP/3tA=
=gLye
-----END PGP SIGNATURE-----

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…

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.

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…