instance method validates_uniqueness_of

Ruby on Rails 2.3.18

Since v2.2.3 Last seen in v2.3.18

Available in: v2.2.3 v2.3.18

Signature

validates_uniqueness_of(*attr_names)

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”.

class Person < ActiveRecord::Base
  validates_uniqueness_of :user_name, :scope => :account_id
end

It can also validate whether the value of the specified attributes are unique based on multiple scope parameters. For example, making sure that a teacher can only be on the schedule once per semester for a particular class.

class TeacherSchedule < ActiveRecord::Base
  validates_uniqueness_of :teacher_id, :scope => [:semester_id, :class_id]
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.

Because this check is performed outside the database there is still a chance that duplicate values will be inserted in two parallel transactions. To guarantee against this you should create a unique index on the field. See add_index for more information.

Configuration options:

  • :message - Specifies a custom error message (default is: “has already been taken”)

  • :scope - One or more columns by which to limit the scope of the uniqueness constraint.

  • :case_sensitive - Looks for an exact match. Ignored by non-text columns (true by default).

  • :allow_nil - If set to true, skips this validation if the attribute is nil (default is: false)

  • :allow_blank - If set to true, skips this validation if the attribute is blank (default is: false)

  • :if - Specifies a method, proc or string to call to determine if the validation should occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The method, proc or string should return or evaluate to a true or false value.

  • :unless - Specifies a method, proc or string to call to determine if the validation should not occur (e.g. :unless => :skip_validation, or :unless => Proc.new { |user| user.signup_step <= 2 }). The method, proc or string should return or evaluate to a true or false value.

Parameters

attr_names rest
Source
# File activemodel/lib/active_model/validations/uniqueness.rb, line 37
      def validates_uniqueness_of(*attr_names)
        configuration = { :message => ActiveRecord::Errors.default_error_messages[:taken] }
        configuration.update(attr_names.extract_options!)

        validates_each(attr_names,configuration) do |record, attr_name, value|
          # The check for an existing value should be run from a class that
          # isn't abstract. This means working down from the current class
          # (self), to the first non-abstract class. Since classes don't know
          # their subclasses, we have to build the hierarchy between self and
          # the record's class.
          class_hierarchy = [record.class]
          while class_hierarchy.first != self
            class_hierarchy.insert(0, class_hierarchy.first.superclass)
          end

          # Now we can work our way down the tree to the first non-abstract
          # class (which has a database table to query from).
          finder_class = class_hierarchy.detect { |klass| !klass.abstract_class? }

          if value.nil? || (configuration[:case_sensitive] || !finder_class.columns_hash[attr_name.to_s].text?)
            condition_sql = "#{record.class.quoted_table_name}.#{attr_name} #{attribute_condition(value)}"
            condition_params = [value]
          else
            # sqlite has case sensitive SELECT query, while MySQL/Postgresql don't.
            # Hence, this is needed only for sqlite.
            condition_sql = "LOWER(#{record.class.quoted_table_name}.#{attr_name}) #{attribute_condition(value)}"
            condition_params = [value.downcase]
          end

          if scope = configuration[:scope]
            Array(scope).map do |scope_item|
              scope_value = record.send(scope_item)
              condition_sql << " AND #{record.class.quoted_table_name}.#{scope_item} #{attribute_condition(scope_value)}"
              condition_params << scope_value
            end
          end

          unless record.new_record?
            condition_sql << " AND #{record.class.quoted_table_name}.#{record.class.primary_key} <> ?"
            condition_params << record.send(:id)
          end

          results = finder_class.with_exclusive_scope do
            connection.select_all(
              construct_finder_sql(
                :select     => attr_name,
                :from       => finder_class.quoted_table_name,
                :conditions => [condition_sql, *condition_params]
              )
            )
          end

          unless results.length.zero?
            found = true

            # As MySQL/Postgres don't have case sensitive SELECT queries, we try to find duplicate
            # column in ruby when case sensitive option
            if configuration[:case_sensitive] && finder_class.columns_hash[attr_name.to_s].text?
              found = results.any? { |a| a[attr_name.to_s] == value }
            end

            record.errors.add(attr_name, configuration[:message]) if found
          end
        end
      end

Defined in activemodel/lib/active_model/validations/uniqueness.rb line 37 · View on GitHub · Improve this page · Find usages on GitHub

Defined in ActiveModel::Validations::ClassMethods

Type at least 2 characters to search.

↑↓ navigate · open · esc close