diff options
Diffstat (limited to 'activerecord/lib/active_record')
13 files changed, 84 insertions, 85 deletions
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 01d7886406..1e7e939097 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -312,8 +312,8 @@ module ActiveRecord #:nodoc: include NestedAttributes include Aggregations include Transactions - include NoTouching include TouchLater + include NoTouching include Reflection include Serialization include Store 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/no_touching.rb b/activerecord/lib/active_record/no_touching.rb index edb5066fa0..4059020e25 100644 --- a/activerecord/lib/active_record/no_touching.rb +++ b/activerecord/lib/active_record/no_touching.rb @@ -45,6 +45,10 @@ module ActiveRecord NoTouching.applied_to?(self.class) end + def touch_later(*) # :nodoc: + super unless no_touching? + end + def touch(*) # :nodoc: super unless no_touching? end diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index ecf3700aab..a4962879ab 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -117,7 +117,10 @@ module ActiveRecord end if has_include?(column_name) - construct_relation_for_association_calculations.calculate(operation, column_name) + relation = construct_relation_for_association_calculations + relation = relation.distinct if operation.to_s.downcase == "count" + + relation.calculate(operation, column_name) else perform_calculation(operation, column_name) end @@ -198,11 +201,6 @@ module ActiveRecord if operation == "count" column_name ||= select_for_count - - unless arel.ast.grep(Arel::Nodes::OuterJoin).empty? - distinct = true - end - column_name = primary_key if column_name == :all && distinct distinct = nil if column_name =~ /\s*DISTINCT[\s(]+/i end diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index d46b4e0683..ff43def901 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -103,7 +103,7 @@ module ActiveRecord # Same as #take but raises ActiveRecord::RecordNotFound if no record # is found. Note that #take! accepts no arguments. def take! - take or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql(@klass.arel_engine)}]") + take || raise_record_not_found_exception! end # Find the first record (or first N records if a parameter is supplied). @@ -126,7 +126,7 @@ module ActiveRecord # Same as #first but raises ActiveRecord::RecordNotFound if no record # is found. Note that #first! accepts no arguments. def first! - find_nth! 0 + first || raise_record_not_found_exception! end # Find the last record (or last N records if a parameter is supplied). @@ -165,7 +165,7 @@ module ActiveRecord # Same as #last but raises ActiveRecord::RecordNotFound if no record # is found. Note that #last! accepts no arguments. def last! - last or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql(@klass.arel_engine)}]") + last || raise_record_not_found_exception! end # Find the second record. @@ -181,7 +181,7 @@ module ActiveRecord # Same as #second but raises ActiveRecord::RecordNotFound if no record # is found. def second! - find_nth! 1 + second || raise_record_not_found_exception! end # Find the third record. @@ -197,7 +197,7 @@ module ActiveRecord # Same as #third but raises ActiveRecord::RecordNotFound if no record # is found. def third! - find_nth! 2 + third || raise_record_not_found_exception! end # Find the fourth record. @@ -213,7 +213,7 @@ module ActiveRecord # Same as #fourth but raises ActiveRecord::RecordNotFound if no record # is found. def fourth! - find_nth! 3 + fourth || raise_record_not_found_exception! end # Find the fifth record. @@ -229,7 +229,7 @@ module ActiveRecord # Same as #fifth but raises ActiveRecord::RecordNotFound if no record # is found. def fifth! - find_nth! 4 + fifth || raise_record_not_found_exception! end # Find the forty-second record. Also known as accessing "the reddit". @@ -245,7 +245,7 @@ module ActiveRecord # Same as #forty_two but raises ActiveRecord::RecordNotFound if no record # is found. def forty_two! - find_nth! 41 + forty_two || raise_record_not_found_exception! end # Find the third-to-last record. @@ -261,7 +261,7 @@ module ActiveRecord # Same as #third_to_last but raises ActiveRecord::RecordNotFound if no record # is found. def third_to_last! - find_nth_from_last 3 or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql(@klass.arel_engine)}]") + third_to_last || raise_record_not_found_exception! end # Find the second-to-last record. @@ -277,7 +277,7 @@ module ActiveRecord # Same as #second_to_last but raises ActiveRecord::RecordNotFound if no record # is found. def second_to_last! - find_nth_from_last 2 or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql(@klass.arel_engine)}]") + second_to_last || raise_record_not_found_exception! end # Returns true if a record exists in the table that matches the +id+ or @@ -345,12 +345,16 @@ module ActiveRecord # of results obtained should be provided in the +result_size+ argument and # the expected number of results should be provided in the +expected_size+ # argument. - def raise_record_not_found_exception!(ids, result_size, expected_size) #:nodoc: + def raise_record_not_found_exception!(ids = nil, result_size = nil, expected_size = nil) # :nodoc: conditions = arel.where_sql(@klass.arel_engine) conditions = " [#{conditions}]" if conditions name = @klass.name - if Array(ids).size == 1 + if ids.nil? + error = "Couldn't find #{name}" + error << " with#{conditions}" if conditions + raise RecordNotFound, error + elsif Array(ids).size == 1 error = "Couldn't find #{name} with '#{primary_key}'=#{ids}#{conditions}" raise RecordNotFound.new(error, name, primary_key, ids) else @@ -526,10 +530,6 @@ module ActiveRecord @offsets[offset_index + index] ||= find_nth_with_limit(index, 1).first end - def find_nth!(index) - find_nth(index) or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql(@klass.arel_engine)}]") - end - def find_nth_with_limit(index, limit) if loaded? records[index, limit] diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index 0c8282de8e..29422bf131 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) @@ -74,7 +76,7 @@ module ActiveRecord return ["1=0"] if attributes.empty? attributes.flat_map do |key, value| - if value.is_a?(Hash) + if value.is_a?(Hash) && !table.has_column?(key) associated_predicate_builder(key).expand_from_hash(value) else build(table.arel_attribute(key), value) @@ -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) + when value.is_a?(Hash) && !table.has_column?(column_name) + 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/sanitization.rb b/activerecord/lib/active_record/sanitization.rb index 6617008344..c40e98715e 100644 --- a/activerecord/lib/active_record/sanitization.rb +++ b/activerecord/lib/active_record/sanitization.rb @@ -5,13 +5,6 @@ module ActiveRecord extend ActiveSupport::Concern module ClassMethods - # Used to sanitize objects before they're used in an SQL SELECT statement. - # Delegates to {connection.quote}[rdoc-ref:ConnectionAdapters::Quoting#quote]. - def sanitize(object) # :nodoc: - connection.quote(object) - end - alias_method :quote_value, :sanitize - protected # Accepts an array or string of SQL conditions and sanitizes @@ -216,7 +209,7 @@ module ActiveRecord # TODO: Deprecate this def quoted_id # :nodoc: - self.class.quote_value(@attributes[self.class.primary_key].value_for_database) + self.class.connection.quote(@attributes[self.class.primary_key].value_for_database) end end end diff --git a/activerecord/lib/active_record/scoping/named.rb b/activerecord/lib/active_record/scoping/named.rb index 46ee3540bd..094c0e9c6f 100644 --- a/activerecord/lib/active_record/scoping/named.rb +++ b/activerecord/lib/active_record/scoping/named.rb @@ -174,7 +174,7 @@ module ActiveRecord protected def valid_scope_name?(name) - if respond_to?(name, true) + if respond_to?(name, true) && logger logger.warn "Creating scope :#{name}. " \ "Overwriting existing method #{self.name}.#{name}." end diff --git a/activerecord/lib/active_record/table_metadata.rb b/activerecord/lib/active_record/table_metadata.rb index e8d6a144f9..0ca880e635 100644 --- a/activerecord/lib/active_record/table_metadata.rb +++ b/activerecord/lib/active_record/table_metadata.rb @@ -37,6 +37,10 @@ module ActiveRecord end end + def has_column?(column_name) + klass && klass.columns_hash.key?(column_name.to_s) + end + def associated_with?(association_name) klass && klass._reflect_on_association(association_name) end 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) |