aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record/validations
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib/active_record/validations')
-rw-r--r--activerecord/lib/active_record/validations/associated.rb8
-rw-r--r--activerecord/lib/active_record/validations/length.rb33
-rw-r--r--activerecord/lib/active_record/validations/presence.rb11
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb17
4 files changed, 56 insertions, 13 deletions
diff --git a/activerecord/lib/active_record/validations/associated.rb b/activerecord/lib/active_record/validations/associated.rb
index b4785d3ba4..47ccef31a5 100644
--- a/activerecord/lib/active_record/validations/associated.rb
+++ b/activerecord/lib/active_record/validations/associated.rb
@@ -29,9 +29,11 @@ module ActiveRecord
# Configuration options:
#
# * <tt>:message</tt> - A custom error message (default is: "is invalid").
- # * <tt>:on</tt> - Specifies when this validation is active. Runs in all
- # validation contexts by default (+nil+), other options are <tt>:create</tt>
- # and <tt>:update</tt>.
+ # * <tt>:on</tt> - Specifies the contexts where this validation is active.
+ # Runs in all validation contexts by default (nil). You can pass a symbol
+ # or an array of symbols. (e.g. <tt>on: :create</tt> or
+ # <tt>on: :custom_validation_context</tt> or
+ # <tt>on: [:create, :custom_validation_context]</tt>)
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine
# if the validation should occur (e.g. <tt>if: :allow_validation</tt>,
# or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method,
diff --git a/activerecord/lib/active_record/validations/length.rb b/activerecord/lib/active_record/validations/length.rb
new file mode 100644
index 0000000000..5991fbad8e
--- /dev/null
+++ b/activerecord/lib/active_record/validations/length.rb
@@ -0,0 +1,33 @@
+module ActiveRecord
+ module Validations
+ class LengthValidator < ActiveModel::Validations::LengthValidator # :nodoc:
+ def validate_each(record, attribute, association_or_value)
+ return unless should_validate?(record) || associations_are_dirty?(record)
+ if association_or_value.respond_to?(:loaded?) && association_or_value.loaded?
+ association_or_value = association_or_value.target.reject(&:marked_for_destruction?)
+ end
+ super
+ end
+
+ def associations_are_dirty?(record)
+ attributes.any? do |attribute|
+ value = record.read_attribute_for_validation(attribute)
+ if value.respond_to?(:loaded?) && value.loaded?
+ value.target.any?(&:marked_for_destruction?)
+ else
+ false
+ end
+ end
+ end
+ end
+
+ module ClassMethods
+ # See <tt>ActiveModel::Validation::LengthValidator</tt> for more information.
+ def validates_length_of(*attr_names)
+ validates_with LengthValidator, _merge_attributes(attr_names)
+ end
+
+ alias_method :validates_size_of, :validates_length_of
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/validations/presence.rb b/activerecord/lib/active_record/validations/presence.rb
index e586744818..75d5bd5a35 100644
--- a/activerecord/lib/active_record/validations/presence.rb
+++ b/activerecord/lib/active_record/validations/presence.rb
@@ -2,13 +2,14 @@ module ActiveRecord
module Validations
class PresenceValidator < ActiveModel::Validations::PresenceValidator # :nodoc:
def validate(record)
+ return unless should_validate?(record)
super
attributes.each do |attribute|
next unless record.class._reflect_on_association(attribute)
associated_records = Array.wrap(record.send(attribute))
# Superclass validates presence. Ensure present records aren't about to be destroyed.
- if associated_records.present? && associated_records.all? { |r| r.marked_for_destruction? }
+ if associated_records.present? && associated_records.all?(&:marked_for_destruction?)
record.errors.add(attribute, :blank, options)
end
end
@@ -44,9 +45,11 @@ module ActiveRecord
#
# Configuration options:
# * <tt>:message</tt> - A custom error message (default is: "can't be blank").
- # * <tt>:on</tt> - Specifies when this validation is active. Runs in all
- # validation contexts by default (+nil+), other options are <tt>:create</tt>
- # and <tt>:update</tt>.
+ # * <tt>:on</tt> - Specifies the contexts where this validation is active.
+ # Runs in all validation contexts by default (nil). You can pass a symbol
+ # or an array of symbols. (e.g. <tt>on: :create</tt> or
+ # <tt>on: :custom_validation_context</tt> or
+ # <tt>on: [:create, :custom_validation_context]</tt>)
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine if
# the validation should occur (e.g. <tt>if: :allow_validation</tt>, or
# <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method, proc
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index 2dba4c7b94..9be4b10a55 100644
--- a/activerecord/lib/active_record/validations/uniqueness.rb
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -11,14 +11,14 @@ module ActiveRecord
end
def validate_each(record, attribute, value)
+ return unless should_validate?(record)
finder_class = find_finder_class_for(record)
table = finder_class.arel_table
value = map_enum_attribute(finder_class, attribute, value)
relation = build_relation(finder_class, table, attribute, value)
- relation = relation.and(table[finder_class.primary_key.to_sym].not_eq(record.id)) if record.persisted?
+ relation = relation.where.not(finder_class.primary_key => record.id) if record.persisted?
relation = scope_relation(record, table, relation)
- relation = finder_class.unscoped.where(relation)
relation = relation.merge(options[:conditions]) if options[:conditions]
if relation.exists?
@@ -60,17 +60,22 @@ module ActiveRecord
end
column = klass.columns_hash[attribute_name]
- value = klass.connection.type_cast(value, column)
+ cast_type = klass.type_for_attribute(attribute_name)
+ value = cast_type.serialize(value)
+ value = klass.connection.type_cast(value)
if value.is_a?(String) && column.limit
value = value.to_s[0, column.limit]
end
- if !options[:case_sensitive] && value.is_a?(String)
+ value = Arel::Nodes::Quoted.new(value)
+
+ comparison = if !options[:case_sensitive] && !value.nil?
# will use SQL LOWER function before comparison, unless it detects a case insensitive collation
klass.connection.case_insensitive_comparison(table, attribute, column, value)
else
klass.connection.case_sensitive_comparison(table, attribute, column, value)
end
+ klass.unscoped.where(comparison)
end
def scope_relation(record, table, relation)
@@ -79,9 +84,9 @@ module ActiveRecord
scope_value = record.send(reflection.foreign_key)
scope_item = reflection.foreign_key
else
- scope_value = record.read_attribute(scope_item)
+ scope_value = record._read_attribute(scope_item)
end
- relation = relation.and(table[scope_item].eq(scope_value))
+ relation = relation.where(scope_item => scope_value)
end
relation