Different password validation on create and on update

I’ve got a model Officer that relies on password validation in all
cases, but I need it to validate the password at different times. For
on create, I want to verify that the password and password
confirmation exists at all times. For update, want to make it
possible for providing password optional, and only when the password
is provided, to make sure it matches the password confirmation and the
within a certain length (such as when calling method
update_attributes).

The best ways I see it right now is to define either
before_validation_on_create, after_validation_on_create, or
before_create for method new, and define either
before_validation_on_update, after_validation_on_update, or
before_update for method update_attributes. It’s worth noting that
Officer extends off of Person, which has it’s own validations to do.

I have several questions about this. First, which function should I
define for validating create and update? What are the pros and cons
on each one?
I’ve also noticed that I cannot use validate_length_of and other
helper methods inside these, and wondered why I’m not allowed to.
Does this mean I have to make my own validation?
If I do have to make my own validations within these methods, I will
have to verify that the pseudo-parameters :password
and :password_confirmation exists. Do I have to call attr_accessor on
either :password, :password_confirmation, or both in this case?

Many thanks in advance!

P.S. Currently, my Officer and Person model looks like this:

class Officer < Person
#take care of password thingy
validates_length_of :password, :within => 5…20
#attr_accessor :password_confirmation
validates_presence_of :password_confirmation, :if => :password
validates_confirmation_of :password

#some functions
def validate
errors.add_to_base(“Missing password”) if hashed_password.blank?
end

def self.authenticate( rin, password )
person = self.find( :first, :conditions => [ ‘rin = ?’, rin ] )
if person
expected_password = encrypted_password( password, person.salt )
if person.hashed_password != expected_password
person = nil
end
end
person
end
end

class Person < ActiveRecord::Base
#first_name is required: must start with a capital
validates_presence_of :first_name
validates_format_of :first_name,
:with => /^[A-Z][a-zA-Z0-9, .]+$/

#last_name is required: must start with a capital
validates_presence_of :last_name
validates_format_of :last_name,
:with => /^[A-Z][a-zA-Z0-9, .]+$/

#rin is required: must be unique; also, must all be lowercases
#with 0 to 2 numbers following the letters.
validates_presence_of :rin,
:message => ‘: A Rensselaer ID must be provided’
validates_format_of :rin,
:with => /^[a-z]+[0-9]{0,2}$/,
:message => ‘: Invalid Rensselaer ID’
validates_uniqueness_of :rin,
:message => ‘: Someone already has this Rensselaer ID’

#email must be unique, and it is required.
validates_presence_of :email
validates_format_of :email,
:with => /^[a-zA-Z0-9_]+@[a-zA-Z0-9_]+(.[a-z]{2,3}){1,2}$/
validates_uniqueness_of :email

#year is required, and must be a 4 digit number greater than 2000
validates_presence_of :year
validates_numericality_of :year,
:only_integer => true
validates_length_of :year,
:is => 4

#completely ignore the passwords
def password
@password
end

def password=(pwd)
@password = pwd
create_new_salt
self.hashed_password = Officer.encrypted_password(self.password,
self.salt)
end

protected

def validate
errors.add(:year, “should be between 2000 and 3000”) if
( year.to_i < 2000 or year.to_i > 3000 )
end

def self.encrypted_password(password, salt)
string_to_hash = password + “Japan is an island of interest” +
salt
Digest::SHA1.hexdigest(string_to_hash)
end

def create_new_salt
self.salt = (self.object_id * rand).to_s + rand.to_s
end
end

I tried this:

class Officer < Person
#take care of password thingy
attr_accessor :password_confirmation

#some functions I want to imitate for on_create at all times,
#and on_update, only if password is not nil

#def validate

errors.add_to_base(“Missing password”) if hashed_password.blank?

#end

#validates_presence_of :password_confirmation, :if => :password
#validates_confirmation_of :password
#validates_length_of :password, :within => 5…20

#functions that doesn’t work
def before_validation_on_create
if @password.nil? or @password.empty?
errors.add_to_base ‘You must provide a password’
elsif @password.size < 5 or @password.size > 20
errors.add_to_base ‘Password must be within 5 and 20 characters’
end
if @password_confirmation.nil? or @password_confirmation.empty?
errors.add_to_base ‘You must provide a password confirmation’
elsif @password_confirmation != @password
errors.add_to_base ‘Password does not match with Password
Confirmation’
end
end

def before_validation_on_update
unless @password.nil?
if @password.empty?
errors.add_to_base ‘You must provide a password’
elsif @password.size < 5 or @password.size > 20
errors.add_to_base ‘Password must be within 5 and 20
characters’
end
if @password_confirmation.nil? or @password_confirmation.empty?
errors.add_to_base ‘You must provide a password confirmation’
elsif @password_confirmation != @password
errors.add_to_base ‘Password does not match with Password
Confirmation’
end
end
end

#and functions that does
def self.authenticate( rin, password )
person = self.find( :first, :conditions => [ ‘rin = ?’, rin ] )
if person
expected_password = encrypted_password( password, person.salt )
if person.hashed_password != expected_password
person = nil
end
end
person
end

#turns an Officer to Admin
def turn_to_admin
self.update_attribute_with_validation_skipping(:type, ‘Admin’)
end

#This does nothing
def turn_to_officer
nil
end

#turns an Officer or Admin
def turn_to_person
self.update_attribute_with_validation_skipping(:type, ‘Person’)
end
end

(Person.rb remains the same)
But according to my unit tests, this doesn’t pass:

def test_defective_new
officer = Officer.new( :first_name => ‘John’,
:last_name => ‘Doe’, :rin => ‘doej’,
:email => ‘[email protected]’, :year => 2011,
:password => ‘forka’)
assert !officer.valid? #line 67, is not true
assert !officer.errors.empty?
end

def test_empty_password
officer = Officer.new( :first_name => ‘John’,
:last_name => ‘Doe’, :rin => ‘doej’,
:email => ‘[email protected]’, :year => 2011)
assert !officer.valid? #line 12, is not true
assert officer.errors.invalid?(:password)
end

def test_empty_password_confirmation
officer = Officer.new( :first_name => ‘John’,
:last_name => ‘Doe’, :rin => ‘doej’,
:email => ‘[email protected]’, :year => 2011,
:password => ‘jojoj’, :password_confirmation => ‘’)
assert !officer.valid? #line 21, is not true
assert officer.errors.invalid?(:password)
end

def test_password_invalid_confirmation
officer = Officer.find(:first)
assert officer
officer[:password] = ‘testing’
officer[:confirmation_password] = ‘paco’
officer.reload
assert !officer.save #line 49, is not true
end

def test_password_too_short
officer = Officer.new( :first_name => ‘John’,
:last_name => ‘Doe’, :rin => ‘doej’,
:email => ‘[email protected]’, :year => 2011,
:password => ‘jo’, :password_confirmation => ‘jo’)
assert !officer.valid? #line 39, is not true
assert officer.errors.invalid?(:password)
end

def test_unequal_password_confirmation
officer = Officer.new( :first_name => ‘John’,
:last_name => ‘Doe’, :rin => ‘doej’,
:email => ‘[email protected]’, :year => 2011,
:password => ‘jojoj’, :password_confirmation => ‘kkkkk’)
assert !officer.valid? #line 30, is not true
assert officer.errors.invalid?(:password)
end

Loaded suite test/unit/officer_test
Started
…FFFFFF.
Finished in 0.438 seconds.

  1. Failure:
    test_defective_new(OfficerTest)
    [test/unit/officer_test.rb:67:in test_defective_new' c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.2-/lib/ active_support/testing/default.rb:7:inrun’]:
    is not true.

  2. Failure:
    test_empty_password(OfficerTest)
    [test/unit/officer_test.rb:12:in test_empty_password' c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.2-/lib/ active_support/testing/default.rb:7:inrun’]:
    is not true.

  3. Failure:
    test_empty_password_confirmation(OfficerTest)
    [test/unit/officer_test.rb:21:in
    test_empty_password_confirmation' c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.2-/lib/ active_support/testing/default.rb:7:inrun’]:
    is not true.

  4. Failure:
    test_password_invalid_confirmation(OfficerTest)
    [test/unit/officer_test.rb:49:in
    test_password_invalid_confirmation' c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.2-/lib/ active_support/testing/default.rb:7:inrun’]:
    is not true.

  5. Failure:
    test_password_too_short(OfficerTest)
    [test/unit/officer_test.rb:39:in test_password_too_short' c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.2-/lib/ active_support/testing/default.rb:7:inrun’]:
    is not true.

  6. Failure:
    test_unequal_password_confirmation(OfficerTest)
    [test/unit/officer_test.rb:30:in
    test_unequal_password_confirmation' c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.2-/lib/ active_support/testing/default.rb:7:inrun’]:
    is not true.

10 tests, 18 assertions, 6 failures, 0 errors

I tried this:

class Officer < Person
#take care of password thingy
attr_accessor :password_confirmation

#some functions I want to imitate for on_create at all times,
#and on_update, only if password is not nil

#def validate

errors.add_to_base(“Missing password”) if hashed_password.blank?

#end

#validates_presence_of :password_confirmation, :if => :password
#validates_confirmation_of :password
#validates_length_of :password, :within => 5…20

#functions that doesn’t work
def before_validation_on_create
if @password.nil? or @password.empty?
errors.add_to_base ‘You must provide a password’
elsif @password.size < 5 or @password.size > 20
errors.add_to_base ‘Password must be within 5 and 20 characters’
end
if @password_confirmation.nil? or @password_confirmation.empty?
errors.add_to_base ‘You must provide a password confirmation’
elsif @password_confirmation != @password
errors.add_to_base ‘Password does not match with Password
Confirmation’
end
end

def before_validation_on_update
unless @password.nil?
if @password.empty?
errors.add_to_base ‘You must provide a password’
elsif @password.size < 5 or @password.size > 20
errors.add_to_base ‘Password must be within 5 and 20
characters’
end
if @password_confirmation.nil? or @password_confirmation.empty?
errors.add_to_base ‘You must provide a password confirmation’
elsif @password_confirmation != @password
errors.add_to_base ‘Password does not match with Password
Confirmation’
end
end
end

#and functions that does
def self.authenticate( rin, password )
person = self.find( :first, :conditions => [ ‘rin = ?’, rin ] )
if person
expected_password = encrypted_password( password, person.salt )
if person.hashed_password != expected_password
person = nil
end
end
person
end

#turns an Officer to Admin
def turn_to_admin
self.update_attribute_with_validation_skipping(:type, ‘Admin’)
end

#This does nothing
def turn_to_officer
nil
end

#turns an Officer or Admin
def turn_to_person
self.update_attribute_with_validation_skipping(:type, ‘Person’)
end
end

(Person.rb remains the same)
But according to my unit tests, this doesn’t pass:

def test_defective_new
officer = Officer.new( :first_name => ‘John’,
:last_name => ‘Doe’, :rin => ‘doej’,
:email => ‘[email protected]’, :year => 2011,
:password => ‘forka’)
assert !officer.valid? #line 67, is not true
assert !officer.errors.empty?
end

def test_empty_password
officer = Officer.new( :first_name => ‘John’,
:last_name => ‘Doe’, :rin => ‘doej’,
:email => ‘[email protected]’, :year => 2011)
assert !officer.valid? #line 12, is not true
assert officer.errors.invalid?(:password)
end

def test_empty_password_confirmation
officer = Officer.new( :first_name => ‘John’,
:last_name => ‘Doe’, :rin => ‘doej’,
:email => ‘[email protected]’, :year => 2011,
:password => ‘jojoj’, :password_confirmation => ‘’)
assert !officer.valid? #line 21, is not true
assert officer.errors.invalid?(:password)
end

def test_password_invalid_confirmation
officer = Officer.find(:first)
assert officer
officer[:password] = ‘testing’
officer[:confirmation_password] = ‘paco’
officer.reload
assert !officer.save #line 49, is not true
end

def test_password_too_short
officer = Officer.new( :first_name => ‘John’,
:last_name => ‘Doe’, :rin => ‘doej’,
:email => ‘[email protected]’, :year => 2011,
:password => ‘jo’, :password_confirmation => ‘jo’)
assert !officer.valid? #line 39, is not true
assert officer.errors.invalid?(:password)
end

def test_unequal_password_confirmation
officer = Officer.new( :first_name => ‘John’,
:last_name => ‘Doe’, :rin => ‘doej’,
:email => ‘[email protected]’, :year => 2011,
:password => ‘jojoj’, :password_confirmation => ‘kkkkk’)
assert !officer.valid? #line 30, is not true
assert officer.errors.invalid?(:password)
end

Loaded suite test/unit/officer_test
Started
…FFFFFF.
Finished in 0.438 seconds.

  1. Failure:
    test_defective_new(OfficerTest)
    [test/unit/officer_test.rb:67:in test_defective_new' c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.2-/lib/ active_support/testing/default.rb:7:inrun’]:
    is not true.

  2. Failure:
    test_empty_password(OfficerTest)
    [test/unit/officer_test.rb:12:in test_empty_password' c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.2-/lib/ active_support/testing/default.rb:7:inrun’]:
    is not true.

  3. Failure:
    test_empty_password_confirmation(OfficerTest)
    [test/unit/officer_test.rb:21:in
    test_empty_password_confirmation' c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.2-/lib/ active_support/testing/default.rb:7:inrun’]:
    is not true.

  4. Failure:
    test_password_invalid_confirmation(OfficerTest)
    [test/unit/officer_test.rb:49:in
    test_password_invalid_confirmation' c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.2-/lib/ active_support/testing/default.rb:7:inrun’]:
    is not true.

  5. Failure:
    test_password_too_short(OfficerTest)
    [test/unit/officer_test.rb:39:in test_password_too_short' c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.2-/lib/ active_support/testing/default.rb:7:inrun’]:
    is not true.

  6. Failure:
    test_unequal_password_confirmation(OfficerTest)
    [test/unit/officer_test.rb:30:in
    test_unequal_password_confirmation' c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.2-/lib/ active_support/testing/default.rb:7:inrun’]:
    is not true.

10 tests, 18 assertions, 6 failures, 0 errors

def validate

this disallows empty values if the record is new

if self.new_record? && (@pswd.blank? || @verify_pswd.blank?)
self.errors.add(:pswd, ‘Password cannot be empty.’)
end

end
THIS is what I was looking for. The new_record? method! Thanks you
so much!

By the way, I apologize for the triple post. They happened
accidentally by the magic of F5 :-P.

On Jun 14, 2008, at 6:18 AM, Taro wrote:

For on create, I want to verify that the password and password
confirmation exists at all times. For update, want to make it
possible for providing password optional, and only when the password
is provided, to make sure it matches the password confirmation and the
within a certain length

Taro, 3 tips for validations:

(1) Have a look at these customized validation methods to help make
validation code smaller and more readable for many scenarios:

http://www.railsdev.ws/blog/11/custom-validations-in-rails/

(2) To validate a specific input only when it is not empty, use an
:if => Proc.new technique. In my case, I use this for an email address
like this:

validates_as_email :userEmail,
:if => Proc.new { |PrivilegedUser|
PrivilegedUser.userEmail.length > 0 }

where :userEmail is the HTML input, and PrivilegedUser is the name
of the class.

(3) For passwords, and other validations that require some custom logic,
add a “validate” method to your class. ActiveRecord will call this
automatically. Let’s assume your HTML inputs are pswd and verify_pswd:

def validate

this disallows empty values if the record is new

if self.new_record? && (@pswd.blank? || @verify_pswd.blank?)
self.errors.add(:pswd, ‘Password cannot be empty.’)
end

end

There are several waysto slice this as there are a few validation
callbacks, so have a look here to see which one best suits how
you prefer to organize your logic:


def gw
writes_at ‘www.railsdev.ws’
end