From ad1a24f0e04ccff17df604c7781f4805ace2ec0f Mon Sep 17 00:00:00 2001 From: Yves Senn Date: Sat, 9 Mar 2013 21:22:12 +0100 Subject: [PATCH] Uniqueness validation uses a proc to specify the `:conditions` option. This is a follow up to #5321 and follows the general direction in AR to make things lazy evaluated. --- activerecord/CHANGELOG.md | 9 +++++++++ .../lib/active_record/validations/uniqueness.rb | 10 +++++++--- .../cases/validations/uniqueness_validation_test.rb | 8 +++++++- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 1796f4319f..b089f9177e 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,5 +1,14 @@ ## Rails 4.0.0 (unreleased) ## +* Uniqueness validation allows you to pass `:conditions` to limit + the constraint lookup. + + Example: + + validates_uniqueness_of :title, conditions: -> { where('approved = ?', true) } + + *Mattias Pfeiffer + Yves Senn* + * `connection` is deprecated as an instance method. This allows end-users to have a `connection` method on their models without clashing with ActiveRecord internals. diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb index 1427189851..bf2aa2e959 100644 --- a/activerecord/lib/active_record/validations/uniqueness.rb +++ b/activerecord/lib/active_record/validations/uniqueness.rb @@ -2,6 +2,10 @@ module ActiveRecord module Validations class UniquenessValidator < ActiveModel::EachValidator # :nodoc: def initialize(options) + if options[:conditions] && !options[:conditions].respond_to?(:call) + raise ArgumentError, "#{options[:conditions]} was passed as :conditions but is not callable. " \ + "Pass a callable instead: `conditions: -> { where('approved = ?', true) }`" + end super({ case_sensitive: true }.merge!(options)) end @@ -19,7 +23,7 @@ def validate_each(record, attribute, value) relation = relation.and(table[finder_class.primary_key.to_sym].not_eq(record.id)) if record.persisted? relation = scope_relation(record, table, relation) relation = finder_class.unscoped.where(relation) - relation.merge!(options[:conditions]) if options[:conditions] + relation = relation.merge(options[:conditions]) if options[:conditions] if relation.exists? error_options = options.except(:case_sensitive, :scope, :conditions) @@ -116,7 +120,7 @@ module ClassMethods # of the title attribute: # # class Article < ActiveRecord::Base - # validates_uniqueness_of :title, conditions: where('status != ?', 'archived') + # validates_uniqueness_of :title, conditions: -> { where('status != ?', 'archived') } # end # # When the record is created, a check is performed to make sure that no @@ -132,7 +136,7 @@ module ClassMethods # the uniqueness constraint. # * :conditions - Specify the conditions to be included as a # WHERE SQL fragment to limit the uniqueness constraint lookup - # (e.g. conditions: where('status = ?', 'active')). + # (e.g. conditions: -> { where('status = ?', 'active') }). # * :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 diff --git a/activerecord/test/cases/validations/uniqueness_validation_test.rb b/activerecord/test/cases/validations/uniqueness_validation_test.rb index 46e767af1a..150e3c5461 100644 --- a/activerecord/test/cases/validations/uniqueness_validation_test.rb +++ b/activerecord/test/cases/validations/uniqueness_validation_test.rb @@ -348,7 +348,7 @@ def test_validate_straight_inheritance_uniqueness end def test_validate_uniqueness_with_conditions - Topic.validates_uniqueness_of(:title, :conditions => Topic.where('approved = ?', true)) + Topic.validates_uniqueness_of :title, conditions: -> { where('approved = ?', true) } Topic.create("title" => "I'm a topic", "approved" => true) Topic.create("title" => "I'm an unapproved topic", "approved" => false) @@ -359,6 +359,12 @@ def test_validate_uniqueness_with_conditions assert t4.valid?, "t4 should be valid" end + def test_validate_uniqueness_with_non_callable_conditions_is_not_supported + assert_raises(ArgumentError) { + Topic.validates_uniqueness_of :title, conditions: Topic.where('approved = ?', true) + } + end + def test_validate_uniqueness_with_array_column return skip "Uniqueness on arrays has only been tested in PostgreSQL so far." if !current_adapter? :PostgreSQLAdapter