diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index 16ec1a1e14..7c12ad482e 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -1,5 +1,22 @@ *CVS* +* Added Base.validate_confirmation that encapsulates the pattern of wanting to validate a password or email address field with a confirmation. Example: + + Model: + class Person < ActiveRecord::Base + validate_confirmation :password + end + + View: + <%= password_field "person", "password" %> + <%= password_field "person", "password_confirmation" %> + + The person has to already have a password attribute (a column in the people table), but the password_confirmation is virtual. + It exists only as an in-memory variable for validating the password. + + NOTE: This validation is only happening on create. When you want to update the record, you'll have to decide and pursue your + own course of action. + * Added validation macros to make the stackable just like the lifecycle callbacks. Examples: class Person < ActiveRecord::Base diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb index 14e79cf68b..25c43386fd 100755 --- a/activerecord/lib/active_record/validations.rb +++ b/activerecord/lib/active_record/validations.rb @@ -51,6 +51,37 @@ module ActiveRecord VALIDATIONS.each { |vd| base.class_eval("def self.#{vd}(*methods) write_inheritable_array(\"#{vd}\", methods - (read_inheritable_attribute(\"#{vd}\") || [])) end") } end + + base.extend(ClassMethods) + end + + module ClassMethods + # Encapsulates the pattern of wanting to validate a password or email address field with a confirmation. Example: + # + # Model: + # class Person < ActiveRecord::Base + # validate_confirmation :password + # end + # + # View: + # <%= password_field "person", "password" %> + # <%= password_field "person", "password_confirmation" %> + # + # The person has to already have a password attribute (a column in the people table), but the password_confirmation is virtual. + # It exists only as an in-memory variable for validating the password. + # + # NOTE: This validation is only happening on create. When you want to update the record, you'll have to decide and pursue your + # own course of action. + def validate_confirmation(*attr_names) + for attr_name in attr_names + attr_accessor "#{attr_name}_confirmation" + class_eval <<-EOC + validate_on_create(Proc.new { |record| + record.errors.add("#{attr_name}", "doesn't match confirmation") unless record.#{attr_name} == record.#{attr_name}_confirmation + }) +EOC + end + end end # The validation process on save can be skipped by passing false. The regular Base#save method is diff --git a/activerecord/test/validations_test.rb b/activerecord/test/validations_test.rb index 27a9b21c7d..a6cdc0a706 100755 --- a/activerecord/test/validations_test.rb +++ b/activerecord/test/validations_test.rb @@ -5,10 +5,7 @@ require 'fixtures/developer' class ValidationsTest < Test::Unit::TestCase - def setup - @topic_fixtures = create_fixtures("topics") - @developers = create_fixtures("developers") - end + fixtures :topics, :developers def test_single_field_validation r = Reply.new @@ -124,3 +121,24 @@ class ValidationsTest < Test::Unit::TestCase assert developer.save end end + + +class MacroValidationsTest < Test::Unit::TestCase + fixtures :topics, :developers + + def setup + Topic.validate_confirmation(:title) + end + + def teardown + Topic.write_inheritable_attribute("validate_on_create", []) + end + + def test_title_confirmation + t = Topic.create("title" => "We should be confirmed") + assert !t.save + + t.title_confirmation = "We should be confirmed" + assert t.save + end +end \ No newline at end of file