Merge pull request #9633 from senny/5321_make_it_lazy
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.
This commit is contained in:
commit
bbc87dbc81
@ -1,5 +1,14 @@
|
|||||||
## Rails 4.0.0 (unreleased) ##
|
## 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.
|
* `connection` is deprecated as an instance method.
|
||||||
This allows end-users to have a `connection` method on their models
|
This allows end-users to have a `connection` method on their models
|
||||||
without clashing with Active Record internals.
|
without clashing with Active Record internals.
|
||||||
|
@ -2,6 +2,10 @@ module ActiveRecord
|
|||||||
module Validations
|
module Validations
|
||||||
class UniquenessValidator < ActiveModel::EachValidator # :nodoc:
|
class UniquenessValidator < ActiveModel::EachValidator # :nodoc:
|
||||||
def initialize(options)
|
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))
|
super({ case_sensitive: true }.merge!(options))
|
||||||
end
|
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 = relation.and(table[finder_class.primary_key.to_sym].not_eq(record.id)) if record.persisted?
|
||||||
relation = scope_relation(record, table, relation)
|
relation = scope_relation(record, table, relation)
|
||||||
relation = finder_class.unscoped.where(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?
|
if relation.exists?
|
||||||
error_options = options.except(:case_sensitive, :scope, :conditions)
|
error_options = options.except(:case_sensitive, :scope, :conditions)
|
||||||
@ -116,7 +120,7 @@ module ClassMethods
|
|||||||
# of the title attribute:
|
# of the title attribute:
|
||||||
#
|
#
|
||||||
# class Article < ActiveRecord::Base
|
# class Article < ActiveRecord::Base
|
||||||
# validates_uniqueness_of :title, conditions: where('status != ?', 'archived')
|
# validates_uniqueness_of :title, conditions: -> { where('status != ?', 'archived') }
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# When the record is created, a check is performed to make sure that no
|
# When the record is created, a check is performed to make sure that no
|
||||||
@ -132,7 +136,7 @@ module ClassMethods
|
|||||||
# the uniqueness constraint.
|
# the uniqueness constraint.
|
||||||
# * <tt>:conditions</tt> - Specify the conditions to be included as a
|
# * <tt>:conditions</tt> - Specify the conditions to be included as a
|
||||||
# <tt>WHERE</tt> SQL fragment to limit the uniqueness constraint lookup
|
# <tt>WHERE</tt> SQL fragment to limit the uniqueness constraint lookup
|
||||||
# (e.g. <tt>conditions: where('status = ?', 'active')</tt>).
|
# (e.g. <tt>conditions: -> { where('status = ?', 'active') }</tt>).
|
||||||
# * <tt>:case_sensitive</tt> - Looks for an exact match. Ignored by
|
# * <tt>:case_sensitive</tt> - Looks for an exact match. Ignored by
|
||||||
# non-text columns (+true+ by default).
|
# non-text columns (+true+ by default).
|
||||||
# * <tt>:allow_nil</tt> - If set to +true+, skips this validation if the
|
# * <tt>:allow_nil</tt> - If set to +true+, skips this validation if the
|
||||||
|
@ -348,7 +348,7 @@ def test_validate_straight_inheritance_uniqueness
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_validate_uniqueness_with_conditions
|
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 a topic", "approved" => true)
|
||||||
Topic.create("title" => "I'm an unapproved topic", "approved" => false)
|
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"
|
assert t4.valid?, "t4 should be valid"
|
||||||
end
|
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
|
def test_validate_uniqueness_with_array_column
|
||||||
return skip "Uniqueness on arrays has only been tested in PostgreSQL so far." if !current_adapter? :PostgreSQLAdapter
|
return skip "Uniqueness on arrays has only been tested in PostgreSQL so far." if !current_adapter? :PostgreSQLAdapter
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user