diff options
Diffstat (limited to 'activerecord/lib')
20 files changed, 94 insertions, 126 deletions
diff --git a/activerecord/lib/active_record/associations/alias_tracker.rb b/activerecord/lib/active_record/associations/alias_tracker.rb index 0c3234ed24..f9c9f8afda 100644 --- a/activerecord/lib/active_record/associations/alias_tracker.rb +++ b/activerecord/lib/active_record/associations/alias_tracker.rb @@ -56,11 +56,11 @@ module ActiveRecord @connection = connection end - def aliased_table_for(table_name, aliased_name) + def aliased_table_for(table_name, aliased_name, **table_options) if aliases[table_name].zero? # If it's zero, we can have our table_name aliases[table_name] = 1 - Arel::Table.new(table_name) + Arel::Table.new(table_name, table_options) else # Otherwise, we need to use an alias aliased_name = connection.table_alias_for(aliased_name) @@ -73,7 +73,7 @@ module ActiveRecord else aliased_name end - Arel::Table.new(table_name).alias(table_alias) + Arel::Table.new(table_name, table_options).alias(table_alias) end end diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb index 0ac10531e5..53f65920e1 100644 --- a/activerecord/lib/active_record/associations/association_scope.rb +++ b/activerecord/lib/active_record/associations/association_scope.rb @@ -66,7 +66,8 @@ module ActiveRecord chain.map do |reflection| alias_tracker.aliased_table_for( table_name_for(reflection, klass, refl), - table_alias_for(reflection, refl, reflection != refl) + table_alias_for(reflection, refl, reflection != refl), + type_caster: klass.type_caster, ) end end diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb index e1c01cfe06..dc42b19a83 100644 --- a/activerecord/lib/active_record/associations/collection_proxy.rb +++ b/activerecord/lib/active_record/associations/collection_proxy.rb @@ -29,6 +29,7 @@ module ActiveRecord # instantiation of the actual post records. class CollectionProxy < Relation delegate(*(ActiveRecord::Calculations.public_instance_methods - [:count]), to: :scope) + delegate :find_nth, to: :scope def initialize(klass, association) #:nodoc: @association = association diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb index cf63430a97..66e997c3c8 100644 --- a/activerecord/lib/active_record/associations/join_dependency.rb +++ b/activerecord/lib/active_record/associations/join_dependency.rb @@ -94,7 +94,7 @@ module ActiveRecord # def initialize(base, associations, joins) @alias_tracker = AliasTracker.create(base.connection, joins) - @alias_tracker.aliased_table_for(base.table_name, base.table_name) # Updates the count for base.table_name to 1 + @alias_tracker.aliased_table_for(base.table_name, base.table_name, type_caster: base.type_caster) # Updates the count for base.table_name to 1 tree = self.class.make_tree associations @join_root = JoinBase.new base, build(tree, base) @join_root.children.each { |child| construct_tables! @join_root, child } @@ -186,9 +186,13 @@ module ActiveRecord def table_aliases_for(parent, node) node.reflection.chain.map { |reflection| + if reflection.klass + type_caster = reflection.klass.type_caster + end alias_tracker.aliased_table_for( reflection.table_name, - table_alias_for(reflection, parent, reflection != node.reflection) + table_alias_for(reflection, parent, reflection != node.reflection), + type_caster: type_caster, ) } end @@ -257,6 +261,7 @@ module ActiveRecord construct(model, node, row, rs, seen, model_cache, aliases) else model = construct_model(ar_parent, node, row, model_cache, id, aliases) + model.readonly! seen[parent.base_klass][primary_id][node.base_klass][id] = model construct(model, node, row, rs, seen, model_cache, aliases) end diff --git a/activerecord/lib/active_record/attributes.rb b/activerecord/lib/active_record/attributes.rb index 08f274fd42..aafb990bc1 100644 --- a/activerecord/lib/active_record/attributes.rb +++ b/activerecord/lib/active_record/attributes.rb @@ -122,6 +122,7 @@ module ActiveRecord end def clear_caches_calculated_from_columns + @arel_table = nil @attributes_builder = nil @column_names = nil @column_types = nil diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 954d22f1d5..bb01231bca 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -22,6 +22,7 @@ require 'active_record/log_subscriber' require 'active_record/explain_subscriber' require 'active_record/relation/delegation' require 'active_record/attributes' +require 'active_record/type_caster' module ActiveRecord #:nodoc: # = Active Record diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index cb53fb0d44..38b2d632d2 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -235,7 +235,7 @@ module ActiveRecord # scope :published_and_commented, -> { published.and(self.arel_table[:comments_count].gt(0)) } # end def arel_table # :nodoc: - @arel_table ||= Arel::Table.new(table_name) + @arel_table ||= Arel::Table.new(table_name, type_caster: type_caster) end # Returns the Arel engine. @@ -252,6 +252,10 @@ module ActiveRecord @predicate_builder ||= PredicateBuilder.new(table_metadata) end + def type_caster # :nodoc: + TypeCaster::Map.new(self) + end + private def relation # :nodoc: diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb index 8a532402ba..b91e9ac137 100644 --- a/activerecord/lib/active_record/inheritance.rb +++ b/activerecord/lib/active_record/inheritance.rb @@ -193,7 +193,6 @@ module ActiveRecord def type_condition(table = arel_table) sti_column = table[inheritance_column] sti_names = ([self] + descendants).map(&:sti_name) - sti_names.map! { |v| Arel::Nodes::Quoted.new(v) } # FIXME: Remove this when type casting in Arel is removed (5.1) sti_column.in(sti_names) end diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb index d76dbb43d6..641512d323 100644 --- a/activerecord/lib/active_record/model_schema.rb +++ b/activerecord/lib/active_record/model_schema.rb @@ -298,6 +298,7 @@ module ActiveRecord connection.schema_cache.clear_table_cache!(table_name) if table_exists? @arel_engine = nil + @arel_table = nil @column_names = nil @column_types = nil @content_columns = nil diff --git a/activerecord/lib/active_record/no_touching.rb b/activerecord/lib/active_record/no_touching.rb index dbf4564ae5..edb5066fa0 100644 --- a/activerecord/lib/active_record/no_touching.rb +++ b/activerecord/lib/active_record/no_touching.rb @@ -45,7 +45,7 @@ module ActiveRecord NoTouching.applied_to?(self.class) end - def touch(*) + def touch(*) # :nodoc: super unless no_touching? end end diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb index f7b2167ae8..4f0502ae75 100644 --- a/activerecord/lib/active_record/relation/batches.rb +++ b/activerecord/lib/active_record/relation/batches.rb @@ -52,12 +52,7 @@ module ActiveRecord end else enum_for :find_each, options do - # FIXME: Remove this when type casting is removed from Arel - # (Rails 5.1). We can pass start directly instead. - if options[:start] - quoted_start = Arel::Nodes::Quoted.new(options[:start]) - end - options[:start] ? where(table[primary_key].gteq(quoted_start)).size : size + options[:start] ? where(table[primary_key].gteq(options[:start])).size : size end end end @@ -107,15 +102,9 @@ module ActiveRecord start = options[:start] batch_size = options[:batch_size] || 1000 - if start - # FIXME: Remove this when type casting is removed from Arel - # (Rails 5.1). We can pass start directly instead. - quoted_start = Arel::Nodes::Quoted.new(start) - end - unless block_given? return to_enum(:find_in_batches, options) do - total = start ? where(table[primary_key].gteq(quoted_start)).size : size + total = start ? where(table[primary_key].gteq(start)).size : size (total - 1).div(batch_size) + 1 end end @@ -125,7 +114,7 @@ module ActiveRecord end relation = relation.reorder(batch_order).limit(batch_size) - records = start ? relation.where(table[primary_key].gteq(quoted_start)).to_a : relation.to_a + records = start ? relation.where(table[primary_key].gteq(start)).to_a : relation.to_a while records.any? records_size = records.size @@ -136,11 +125,7 @@ module ActiveRecord break if records_size < batch_size - # FIXME: Remove this when type casting is removed from Arel - # (Rails 5.1). We can pass the offset directly instead. - quoted_offset = Arel::Nodes::Quoted.new(primary_key_offset) - - records = relation.where(table[primary_key].gt(quoted_offset)).to_a + records = relation.where(table[primary_key].gt(primary_key_offset)).to_a end end diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index ad8dddb9a4..567efce8ae 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -8,7 +8,7 @@ module ActiveRecord require 'active_record/relation/predicate_builder/range_handler' require 'active_record/relation/predicate_builder/relation_handler' - delegate :resolve_column_aliases, :type_cast_for_database, to: :table + delegate :resolve_column_aliases, to: :table def initialize(table) @table = table diff --git a/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb index 7c90563d96..4b5f5773a0 100644 --- a/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb +++ b/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb @@ -19,12 +19,7 @@ module ActiveRecord case values.length when 0 then NullPredicate when 1 then predicate_builder.build(attribute, values.first) - else - attribute_name = attribute.name - casted_values = values.map do |v| - predicate_builder.type_cast_for_database(attribute_name, v) - end - attribute.in(casted_values) + else attribute.in(values) end unless nils.empty? diff --git a/activerecord/lib/active_record/relation/predicate_builder/basic_object_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/basic_object_handler.rb index 57a8b63001..6cec75dc0a 100644 --- a/activerecord/lib/active_record/relation/predicate_builder/basic_object_handler.rb +++ b/activerecord/lib/active_record/relation/predicate_builder/basic_object_handler.rb @@ -6,7 +6,6 @@ module ActiveRecord end def call(attribute, value) - value = predicate_builder.type_cast_for_database(attribute.name, value) attribute.eq(value) end diff --git a/activerecord/lib/active_record/relation/predicate_builder/range_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/range_handler.rb index a6638738fa..1b3849e3ad 100644 --- a/activerecord/lib/active_record/relation/predicate_builder/range_handler.rb +++ b/activerecord/lib/active_record/relation/predicate_builder/range_handler.rb @@ -6,11 +6,6 @@ module ActiveRecord end def call(attribute, value) - value = QuotedRange.new( - predicate_builder.type_cast_for_database(attribute.name, value.begin), - predicate_builder.type_cast_for_database(attribute.name, value.end), - value.exclude_end?, - ) attribute.between(value) end @@ -18,16 +13,5 @@ module ActiveRecord attr_reader :predicate_builder end - - class QuotedRange # :nodoc: - attr_reader :begin, :end, :exclude_end - alias_method :exclude_end?, :exclude_end - - def initialize(begin_val, end_val, exclude) - @begin = begin_val - @end = end_val - @exclude_end = exclude - end - end end end diff --git a/activerecord/lib/active_record/table_metadata.rb b/activerecord/lib/active_record/table_metadata.rb index e60bf55021..11e33e8dfe 100644 --- a/activerecord/lib/active_record/table_metadata.rb +++ b/activerecord/lib/active_record/table_metadata.rb @@ -8,12 +8,6 @@ module ActiveRecord @association = association end - def type_cast_for_database(attribute_name, value) - return value if value.is_a?(Arel::Nodes::BindParam) || klass.nil? - type = klass.type_for_attribute(attribute_name.to_s) - Arel::Nodes::Quoted.new(type.type_cast_for_database(value)) - end - def resolve_column_aliases(hash) hash = hash.dup hash.keys.grep(Symbol) do |key| @@ -35,17 +29,17 @@ module ActiveRecord def associated_table(table_name) return self if table_name == arel_table.name - arel_table = Arel::Table.new(table_name) association = klass._reflect_on_association(table_name) if association && !association.polymorphic? association_klass = association.klass - end - - if association - TableMetadata.new(association_klass, arel_table, association) + arel_table = association_klass.arel_table else - ConnectionAdapterTable.new(klass.connection, arel_table) + type_caster = TypeCaster::Connection.new(klass.connection, table_name) + association_klass = nil + arel_table = Arel::Table.new(table_name, type_caster: type_caster) end + + TableMetadata.new(association_klass, arel_table, association) end def polymorphic_association? @@ -56,65 +50,4 @@ module ActiveRecord attr_reader :klass, :arel_table, :association end - - # FIXME: We want to get rid of this class. The connection adapter does not - # have sufficient knowledge about types, as they could be provided by or - # overriden by the ActiveRecord::Base subclass. The case where you reach this - # class is if you do a query like: - # - # Liquid.joins(molecules: :electrons) - # .where("molecules.name" => "something", "electrons.name" => "something") - # - # Since we don't know that we can get to electrons through molecules - class ConnectionAdapterTable # :nodoc: - def initialize(connection, arel_table) - @connection = connection - @arel_table = arel_table - end - - def type_cast_for_database(attribute_name, value) - return value if value.is_a?(Arel::Nodes::BindParam) - type = type_for(attribute_name) - Arel::Nodes::Quoted.new(type.type_cast_for_database(value)) - end - - def resolve_column_aliases(hash) - hash - end - - def arel_attribute(column_name) - arel_table[column_name] - end - - def associated_with?(*) - false - end - - def associated_table(table_name) - arel_table = Arel::Table.new(table_name) - ConnectionAdapterTable.new(klass.connection, arel_table) - end - - def polymorphic_association? - false - end - - protected - - attr_reader :connection, :arel_table - - private - - def type_for(attribute_name) - if connection.schema_cache.table_exists?(arel_table.name) - column_for(attribute_name).cast_type - else - Type::Value.new - end - end - - def column_for(attribute_name) - connection.schema_cache.columns_hash(arel_table.name)[attribute_name.to_s] - end - end end diff --git a/activerecord/lib/active_record/type_caster.rb b/activerecord/lib/active_record/type_caster.rb new file mode 100644 index 0000000000..63ba10c289 --- /dev/null +++ b/activerecord/lib/active_record/type_caster.rb @@ -0,0 +1,7 @@ +require 'active_record/type_caster/map' +require 'active_record/type_caster/connection' + +module ActiveRecord + module TypeCaster + end +end diff --git a/activerecord/lib/active_record/type_caster/connection.rb b/activerecord/lib/active_record/type_caster/connection.rb new file mode 100644 index 0000000000..9e4a130b40 --- /dev/null +++ b/activerecord/lib/active_record/type_caster/connection.rb @@ -0,0 +1,34 @@ +module ActiveRecord + module TypeCaster + class Connection + def initialize(connection, table_name) + @connection = connection + @table_name = table_name + end + + def type_cast_for_database(attribute_name, value) + return value if value.is_a?(Arel::Nodes::BindParam) + type = type_for(attribute_name) + type.type_cast_for_database(value) + end + + protected + + attr_reader :connection, :table_name + + private + + def type_for(attribute_name) + if connection.schema_cache.table_exists?(table_name) + column_for(attribute_name).cast_type + else + Type::Value.new + end + end + + def column_for(attribute_name) + connection.schema_cache.columns_hash(table_name)[attribute_name.to_s] + end + end + end +end diff --git a/activerecord/lib/active_record/type_caster/map.rb b/activerecord/lib/active_record/type_caster/map.rb new file mode 100644 index 0000000000..03c9e8ff83 --- /dev/null +++ b/activerecord/lib/active_record/type_caster/map.rb @@ -0,0 +1,19 @@ +module ActiveRecord + module TypeCaster + class Map + def initialize(types) + @types = types + end + + def type_cast_for_database(attr_name, value) + return value if value.is_a?(Arel::Nodes::BindParam) + type = types.type_for_attribute(attr_name.to_s) + type.type_cast_for_database(value) + end + + protected + + attr_reader :types + end + end +end diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb index c4ff2e3ef3..9ff2ad9c6b 100644 --- a/activerecord/lib/active_record/validations/uniqueness.rb +++ b/activerecord/lib/active_record/validations/uniqueness.rb @@ -64,7 +64,6 @@ module ActiveRecord value = value.to_s[0, column.limit] end - # FIXME: Remove this when type casting is removed from Arel (Rails 5.1) value = Arel::Nodes::Quoted.new(value) comparison = if !options[:case_sensitive] && value && column.text? |