From 3a1f6fe7b4a70bf0698b0684dd48ac712c6883b6 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Sat, 2 Jul 2016 10:14:53 +0900 Subject: Extract `PredicateBuilder::CaseSensitiveHandler` Currently uniqueness validator is coupled with building Arel ASTs. This commit extracts `PredicateBuilder::CaseSensitiveHandler` for decouple the building Arel ASTs. --- .../connection_adapters/abstract_adapter.rb | 12 ++++---- .../connection_adapters/abstract_mysql_adapter.rb | 7 ++--- .../active_record/relation/predicate_builder.rb | 28 +++++++++++++------ .../predicate_builder/case_sensitive_handler.rb | 21 ++++++++++++++ .../active_record/relation/where_clause_factory.rb | 2 +- .../lib/active_record/validations/uniqueness.rb | 32 +--------------------- 6 files changed, 51 insertions(+), 51 deletions(-) create mode 100644 activerecord/lib/active_record/relation/predicate_builder/case_sensitive_handler.rb (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 0c7197a002..4dde525ebc 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -433,16 +433,16 @@ module ActiveRecord @connection end - def case_sensitive_comparison(table, attribute, column, value) - table[attribute].eq(Arel::Nodes::BindParam.new) + def case_sensitive_comparison(attribute, column, value) # :nodoc: + attribute.eq(value) end - def case_insensitive_comparison(table, attribute, column, value) + def case_insensitive_comparison(attribute, column, value) # :nodoc: if can_perform_case_insensitive_comparison_for?(column) - table[attribute].lower.eq(table.lower(Arel::Nodes::BindParam.new)) - else - table[attribute].eq(Arel::Nodes::BindParam.new) + value = attribute.relation.lower(value) + attribute = attribute.lower end + attribute.eq(value) end def can_perform_case_insensitive_comparison_for?(column) diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb index 5900919e88..e7efc4caf7 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -613,12 +613,11 @@ module ActiveRecord SQL end - def case_sensitive_comparison(table, attribute, column, value) + def case_sensitive_comparison(attribute, column, value) # :nodoc: if column.collation && !column.case_sensitive? - table[attribute].eq(Arel::Nodes::Bin.new(Arel::Nodes::BindParam.new)) - else - super + value = Arel::Nodes::Bin.new(value) end + attribute.eq(value) end def can_perform_case_insensitive_comparison_for?(column) diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index 0c8282de8e..e874fab4bb 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -4,6 +4,7 @@ module ActiveRecord require "active_record/relation/predicate_builder/association_query_handler" require "active_record/relation/predicate_builder/base_handler" require "active_record/relation/predicate_builder/basic_object_handler" + require "active_record/relation/predicate_builder/case_sensitive_handler" require "active_record/relation/predicate_builder/class_handler" require "active_record/relation/predicate_builder/polymorphic_array_handler" require "active_record/relation/predicate_builder/range_handler" @@ -16,6 +17,7 @@ module ActiveRecord @handlers = [] register_handler(BasicObject, BasicObjectHandler.new) + register_handler(CaseSensitiveHandler::Value, CaseSensitiveHandler.new) register_handler(Class, ClassHandler.new(self)) register_handler(Base, BaseHandler.new(self)) register_handler(Range, RangeHandler.new) @@ -31,9 +33,9 @@ module ActiveRecord expand_from_hash(attributes) end - def create_binds(attributes) + def create_binds(attributes, options) attributes = convert_dot_notation_to_hash(attributes) - create_binds_for_hash(attributes) + create_binds_for_hash(attributes, options) end def self.references(attributes) @@ -82,14 +84,14 @@ module ActiveRecord end end - def create_binds_for_hash(attributes) + def create_binds_for_hash(attributes, options) result = attributes.dup binds = [] attributes.each do |column_name, value| case when value.is_a?(Hash) - attrs, bvs = associated_predicate_builder(column_name).create_binds_for_hash(value) + attrs, bvs = associated_predicate_builder(column_name).create_binds_for_hash(value, options) result[column_name] = attrs binds += bvs next @@ -108,11 +110,15 @@ module ActiveRecord end result[column_name] = RangeHandler::RangeWithBinds.new(first, last, value.exclude_end?) - else - if can_be_bound?(column_name, value) - result[column_name] = Arel::Nodes::BindParam.new - binds << build_bind_param(column_name, value) - end + when can_be_bound?(column_name, value) + result[column_name] = + if perform_case_sensitive?(options) + CaseSensitiveHandler::Value.new( + Arel::Nodes::BindParam.new, table, options[:case_sensitive]) + else + Arel::Nodes::BindParam.new + end + binds << build_bind_param(column_name, value) end # Find the foreign key when using queries such as: @@ -164,6 +170,10 @@ module ActiveRecord end end + def perform_case_sensitive?(options) + options.key?(:case_sensitive) + end + def build_bind_param(column_name, value) Relation::QueryAttribute.new(column_name.to_s, value, table.type(column_name)) end diff --git a/activerecord/lib/active_record/relation/predicate_builder/case_sensitive_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/case_sensitive_handler.rb new file mode 100644 index 0000000000..acf0bbd829 --- /dev/null +++ b/activerecord/lib/active_record/relation/predicate_builder/case_sensitive_handler.rb @@ -0,0 +1,21 @@ +module ActiveRecord + class PredicateBuilder + class CaseSensitiveHandler # :nodoc: + def call(attribute, value) + value.call(attribute) + end + + class Value < Struct.new(:value, :table, :case_sensitive?) # :nodoc: + def call(attribute) + klass = table.send(:klass) + column = klass.column_for_attribute(attribute.name) + if case_sensitive? + klass.connection.case_sensitive_comparison(attribute, column, value) + else + klass.connection.case_insensitive_comparison(attribute, column, value) + end + end + end + end + end +end diff --git a/activerecord/lib/active_record/relation/where_clause_factory.rb b/activerecord/lib/active_record/relation/where_clause_factory.rb index dc00149130..122ab04c00 100644 --- a/activerecord/lib/active_record/relation/where_clause_factory.rb +++ b/activerecord/lib/active_record/relation/where_clause_factory.rb @@ -17,7 +17,7 @@ module ActiveRecord attributes = klass.send(:expand_hash_conditions_for_aggregates, attributes) attributes.stringify_keys! - attributes, binds = predicate_builder.create_binds(attributes) + attributes, binds = predicate_builder.create_binds(attributes, other.last || {}) parts = predicate_builder.build_from_hash(attributes) when Arel::Nodes::Node diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb index 8c4930a81d..08c4b01439 100644 --- a/activerecord/lib/active_record/validations/uniqueness.rb +++ b/activerecord/lib/active_record/validations/uniqueness.rb @@ -50,37 +50,7 @@ module ActiveRecord end def build_relation(klass, attribute, value) # :nodoc: - if reflection = klass._reflect_on_association(attribute) - attribute = reflection.foreign_key - value = value.attributes[reflection.klass.primary_key] unless value.nil? - end - - if value.nil? - return klass.unscoped.where!(attribute => value) - end - - # the attribute may be an aliased attribute - if klass.attribute_alias?(attribute) - attribute = klass.attribute_alias(attribute) - end - - attribute_name = attribute.to_s - - table = klass.arel_table - column = klass.columns_hash[attribute_name] - cast_type = klass.type_for_attribute(attribute_name) - - comparison = if !options[:case_sensitive] - # 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.tap do |scope| - parts = [comparison] - binds = [Relation::QueryAttribute.new(attribute_name, value, cast_type)] - scope.where_clause += Relation::WhereClause.new(parts, binds) - end + klass.unscoped.where!({ attribute => value }, options) end def scope_relation(record, relation) -- cgit v1.2.3