aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record/validations/uniqueness.rb
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib/active_record/validations/uniqueness.rb')
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb47
1 files changed, 37 insertions, 10 deletions
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index 2e2ea8c42b..9e4b588ac2 100644
--- a/activerecord/lib/active_record/validations/uniqueness.rb
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -1,4 +1,4 @@
-require 'active_support/core_ext/array/wrap'
+require 'active_support/core_ext/array/prepend_and_append'
module ActiveRecord
module Validations
@@ -25,13 +25,24 @@ module ActiveRecord
relation = build_relation(finder_class, table, attribute, value)
relation = relation.and(table[finder_class.primary_key.to_sym].not_eq(record.send(:id))) if record.persisted?
- Array.wrap(options[:scope]).each do |scope_item|
+ Array(options[:scope]).each do |scope_item|
scope_value = record.send(scope_item)
+ reflection = record.class.reflect_on_association(scope_item)
+ if reflection
+ scope_value = record.send(reflection.foreign_key)
+ scope_item = reflection.foreign_key
+ end
relation = relation.and(table[scope_item].eq(scope_value))
end
- if finder_class.unscoped.where(relation).exists?
- record.errors.add(attribute, :taken, options.except(:case_sensitive, :scope).merge(:value => value))
+ relation = finder_class.unscoped.where(relation)
+
+ if options[:conditions]
+ relation = relation.merge(options[:conditions])
+ end
+
+ if relation.exists?
+ record.errors.add(attribute, :taken, options.except(:case_sensitive, :scope, :conditions).merge(:value => value))
end
end
@@ -46,21 +57,28 @@ module ActiveRecord
class_hierarchy = [record.class]
while class_hierarchy.first != @klass
- class_hierarchy.insert(0, class_hierarchy.first.superclass)
+ class_hierarchy.prepend(class_hierarchy.first.superclass)
end
class_hierarchy.detect { |klass| !klass.abstract_class? }
end
def build_relation(klass, table, attribute, value) #:nodoc:
- column = klass.columns_hash[attribute.to_s]
- value = column.limit ? value.to_s.mb_chars[0, column.limit] : value.to_s if column.text?
+ reflection = klass.reflect_on_association(attribute)
+ if reflection
+ column = klass.columns_hash[reflection.foreign_key]
+ attribute = reflection.foreign_key
+ value = value.attributes[reflection.primary_key_column.name]
+ else
+ column = klass.columns_hash[attribute.to_s]
+ end
+ value = column.limit ? value.to_s[0, column.limit] : value.to_s if !value.nil? && column.text?
if !options[:case_sensitive] && value && column.text?
# will use SQL LOWER function before comparison, unless it detects a case insensitive collation
relation = klass.connection.case_insensitive_comparison(table, attribute, column, value)
else
- value = klass.connection.case_sensitive_modifier(value)
+ value = klass.connection.case_sensitive_modifier(value) unless value.nil?
relation = table[attribute].eq(value)
end
@@ -81,7 +99,7 @@ module ActiveRecord
#
# class Person < ActiveRecord::Base
# validates_uniqueness_of :user_name, :scope => :account_id
- # end
+ # end
#
# Or even multiple scope parameters. For example, making sure that a teacher can only be on the schedule once
# per semester for a particular class.
@@ -90,6 +108,14 @@ module ActiveRecord
# validates_uniqueness_of :teacher_id, :scope => [:semester_id, :class_id]
# end
#
+ # It is also possible to limit the uniqueness constraint to a set of records matching certain conditions.
+ # In this example archived articles are not being taken into consideration when validating uniqueness
+ # of the title attribute:
+ #
+ # class Article < ActiveRecord::Base
+ # validates_uniqueness_of :title, :conditions => where('status != ?', 'archived')
+ # end
+ #
# When the record is created, a check is performed to make sure that no record exists 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.
@@ -97,6 +123,8 @@ module ActiveRecord
# Configuration options:
# * <tt>:message</tt> - Specifies a custom error message (default is: "has already been taken").
# * <tt>:scope</tt> - One or more columns by which to limit the scope of the uniqueness constraint.
+ # * <tt>:conditions</tt> - Specify the conditions to be included as a <tt>WHERE</tt> SQL fragment to limit
+ # the uniqueness constraint lookup. (e.g. <tt>:conditions => where('status = ?', 'active')</tt>)
# * <tt>:case_sensitive</tt> - Looks for an exact match. Ignored by non-text columns (+true+ by default).
# * <tt>:allow_nil</tt> - If set to true, skips this validation if the attribute is +nil+ (default is +false+).
# * <tt>:allow_blank</tt> - If set to true, skips this validation if the attribute is blank (default is +false+).
@@ -168,7 +196,6 @@ module ActiveRecord
# The following bundled adapters throw the ActiveRecord::RecordNotUnique exception:
# * ActiveRecord::ConnectionAdapters::MysqlAdapter
# * ActiveRecord::ConnectionAdapters::Mysql2Adapter
- # * ActiveRecord::ConnectionAdapters::SQLiteAdapter
# * ActiveRecord::ConnectionAdapters::SQLite3Adapter
# * ActiveRecord::ConnectionAdapters::PostgreSQLAdapter
#