mirror of https://github.com/rails/rails
Added Base.validate_uniqueness thatv alidates whether the value of the specified attributes are unique across the system. Useful for making sure that only one user can be named "davidhh".
git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@108 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
This commit is contained in:
parent
0b92b7de2f
commit
aaf9a45ca9
|
@ -14,6 +14,21 @@
|
||||||
|
|
||||||
* Added Base.validate_presence as an alternative to implementing validate and doing errors.add_on_empty yourself.
|
* Added Base.validate_presence as an alternative to implementing validate and doing errors.add_on_empty yourself.
|
||||||
|
|
||||||
|
* Added Base.validate_uniqueness that alidates whether the value of the specified attributes are unique across the system.
|
||||||
|
Useful for making sure that only one user can be named "davidhh".
|
||||||
|
|
||||||
|
Model:
|
||||||
|
class Person < ActiveRecord::Base
|
||||||
|
validate_uniqueness :user_name
|
||||||
|
end
|
||||||
|
|
||||||
|
View:
|
||||||
|
<%= text_field "person", "user_name" %>
|
||||||
|
|
||||||
|
When the record is created, a check is performed to make sure that no record exist in the database with the given value for the specified
|
||||||
|
attribute (that maps to a column). When the record is updated, the same check is made but disregarding the record itself.
|
||||||
|
|
||||||
|
|
||||||
* Added Base.validate_confirmation that encapsulates the pattern of wanting to validate a password or email address field with a confirmation. Example:
|
* Added Base.validate_confirmation that encapsulates the pattern of wanting to validate a password or email address field with a confirmation. Example:
|
||||||
|
|
||||||
Model:
|
Model:
|
||||||
|
|
|
@ -665,7 +665,7 @@ module ActiveRecord #:nodoc:
|
||||||
end
|
end
|
||||||
|
|
||||||
until values.empty?
|
until values.empty?
|
||||||
statement.sub!(/\?/, connection.quote(values.shift))
|
statement.sub!(/\?/, encode_quoted_value(values.shift))
|
||||||
end
|
end
|
||||||
|
|
||||||
statement.gsub('?') { |all, match| connection.quote(values.shift) }
|
statement.gsub('?') { |all, match| connection.quote(values.shift) }
|
||||||
|
@ -674,7 +674,7 @@ module ActiveRecord #:nodoc:
|
||||||
def replace_named_bind_variables(statement, values_hash)
|
def replace_named_bind_variables(statement, values_hash)
|
||||||
orig_statement = statement.clone
|
orig_statement = statement.clone
|
||||||
values_hash.keys.each do |k|
|
values_hash.keys.each do |k|
|
||||||
if statement.sub!(/:#{k.id2name}/, connection.quote(values_hash.delete(k))).nil?
|
if statement.sub!(/:#{k.id2name}/, encode_quoted_value(values_hash.delete(k))).nil?
|
||||||
raise PreparedStatementInvalid, ":#{k} is not a variable in [#{orig_statement}]"
|
raise PreparedStatementInvalid, ":#{k} is not a variable in [#{orig_statement}]"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -685,6 +685,12 @@ module ActiveRecord #:nodoc:
|
||||||
|
|
||||||
return statement
|
return statement
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def encode_quoted_value(value)
|
||||||
|
quoted_value = connection.quote(value)
|
||||||
|
quoted_value = "'#{quoted_value[1..-2].gsub(/\'/, "\\\\'")}'" if quoted_value.include?("\\\'")
|
||||||
|
quoted_value
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
public
|
public
|
||||||
|
|
|
@ -324,7 +324,7 @@ module ActiveRecord
|
||||||
|
|
||||||
def quote(value, column = nil)
|
def quote(value, column = nil)
|
||||||
case value
|
case value
|
||||||
when String then "'#{quote_string(value)}'" # ' (for ruby-mode)
|
when String then %('#{quote_string(value)}') # ' (for ruby-mode)
|
||||||
when NilClass then "NULL"
|
when NilClass then "NULL"
|
||||||
when TrueClass then (column && column.type == :boolean ? "'t'" : "1")
|
when TrueClass then (column && column.type == :boolean ? "'t'" : "1")
|
||||||
when FalseClass then (column && column.type == :boolean ? "'f'" : "0")
|
when FalseClass then (column && column.type == :boolean ? "'f'" : "0")
|
||||||
|
|
|
@ -74,7 +74,6 @@ module ActiveRecord
|
||||||
# situations.
|
# situations.
|
||||||
def validate_confirmation(*attr_names)
|
def validate_confirmation(*attr_names)
|
||||||
error_message = attr_names.last.is_a?(String) ? attr_names.pop : "doesn't match confirmation"
|
error_message = attr_names.last.is_a?(String) ? attr_names.pop : "doesn't match confirmation"
|
||||||
|
|
||||||
validation_method = block_given? ? yield : "validate"
|
validation_method = block_given? ? yield : "validate"
|
||||||
|
|
||||||
for attr_name in attr_names
|
for attr_name in attr_names
|
||||||
|
@ -111,7 +110,6 @@ module ActiveRecord
|
||||||
# NOTE: The agreement is considered valid if it's set to the string "1". This makes it easy to relate it to an HTML checkbox.
|
# NOTE: The agreement is considered valid if it's set to the string "1". This makes it easy to relate it to an HTML checkbox.
|
||||||
def validate_acceptance(*attr_names)
|
def validate_acceptance(*attr_names)
|
||||||
error_message = attr_names.last.is_a?(String) ? attr_names.pop : "must be accepted"
|
error_message = attr_names.last.is_a?(String) ? attr_names.pop : "must be accepted"
|
||||||
|
|
||||||
validation_method = block_given? ? yield : "validate"
|
validation_method = block_given? ? yield : "validate"
|
||||||
|
|
||||||
for attr_name in attr_names
|
for attr_name in attr_names
|
||||||
|
@ -132,7 +130,6 @@ module ActiveRecord
|
||||||
|
|
||||||
def validate_presence(*attr_names)
|
def validate_presence(*attr_names)
|
||||||
error_message = attr_names.last.is_a?(String) ? attr_names.pop : "can't be empty"
|
error_message = attr_names.last.is_a?(String) ? attr_names.pop : "can't be empty"
|
||||||
|
|
||||||
validation_method = block_given? ? yield : "validate"
|
validation_method = block_given? ? yield : "validate"
|
||||||
|
|
||||||
for attr_name in attr_names
|
for attr_name in attr_names
|
||||||
|
@ -149,6 +146,27 @@ module ActiveRecord
|
||||||
def validate_presence_on_update(*attr_names)
|
def validate_presence_on_update(*attr_names)
|
||||||
validate_presence(*attr_names) { "validate_on_update" }
|
validate_presence(*attr_names) { "validate_on_update" }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Validates whether the value of the specified attributes are unique across the system. Useful for making sure that only one user
|
||||||
|
# can be named "davidhh".
|
||||||
|
#
|
||||||
|
# Model:
|
||||||
|
# class Person < ActiveRecord::Base
|
||||||
|
# validate_uniqueness :user_name
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# View:
|
||||||
|
# <%= text_field "person", "user_name" %>
|
||||||
|
#
|
||||||
|
# When the record is created, a check is performed to make sure that no record exist in the database with the given value for the specified
|
||||||
|
# attribute (that maps to a column). When the record is updated, the same check is made but disregarding the record itself.
|
||||||
|
def validate_uniqueness(*attr_names)
|
||||||
|
error_message = attr_names.last.is_a?(String) ? attr_names.pop : "has already been taken"
|
||||||
|
|
||||||
|
for attr_name in attr_names
|
||||||
|
class_eval(%(validate %{errors.add("#{attr_name}", "#{error_message}") if self.class.find_first(new_record? ? ["#{attr_name} = ?", #{attr_name}] : ["#{attr_name} = ? AND id <> ?", #{attr_name}, id])}))
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# The validation process on save can be skipped by passing false. The regular Base#save method is
|
# The validation process on save can be skipped by passing false. The regular Base#save method is
|
||||||
|
|
|
@ -95,6 +95,16 @@ class FinderTest < Test::Unit::TestCase
|
||||||
Company.find_first(["id=?", 2, 3, 4])
|
Company.find_first(["id=?", 2, 3, 4])
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_bind_variables_with_quotes
|
||||||
|
Company.create("name" => "37signals' go'es agains")
|
||||||
|
assert Company.find_first(["name = ?", "37signals' go'es agains"])
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_named_bind_variables_with_quotes
|
||||||
|
Company.create("name" => "37signals' go'es agains")
|
||||||
|
assert Company.find_first(["name = :name", {:name => "37signals' go'es agains"}])
|
||||||
|
end
|
||||||
|
|
||||||
def test_named_bind_variables
|
def test_named_bind_variables
|
||||||
assert_kind_of Firm, Company.find_first(["name = :name", { :name => "37signals" }])
|
assert_kind_of Firm, Company.find_first(["name = :name", { :name => "37signals" }])
|
||||||
|
|
|
@ -172,4 +172,22 @@ class ValidationsTest < Test::Unit::TestCase
|
||||||
|
|
||||||
assert t.save
|
assert t.save
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_validate_uniqueness
|
||||||
|
Topic.validate_uniqueness(:title)
|
||||||
|
|
||||||
|
t = Topic.new("title" => "I'm unique!")
|
||||||
|
assert t.save, "Should save t as unique"
|
||||||
|
|
||||||
|
t.content = "Remaining unique"
|
||||||
|
assert t.save, "Should still save t as unique"
|
||||||
|
|
||||||
|
t2 = Topic.new("title" => "I'm unique!")
|
||||||
|
assert !t2.valid?, "Shouldn't be valid"
|
||||||
|
assert !t2.save, "Shouldn't save t2 as unique"
|
||||||
|
assert_equal "has already been taken", t2.errors.on(:title)
|
||||||
|
|
||||||
|
t2.title = "Now Im really also unique"
|
||||||
|
assert t2.save, "Should now save t2 as unique"
|
||||||
|
end
|
||||||
end
|
end
|
Loading…
Reference in New Issue