aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib')
-rw-r--r--activerecord/lib/active_record.rb2
-rw-r--r--activerecord/lib/active_record/associations.rb30
-rw-r--r--activerecord/lib/active_record/associations/alias_tracker.rb12
-rw-r--r--activerecord/lib/active_record/associations/belongs_to_association.rb18
-rw-r--r--activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb7
-rw-r--r--activerecord/lib/active_record/associations/builder/association.rb4
-rw-r--r--activerecord/lib/active_record/associations/builder/belongs_to.rb10
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb4
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb21
-rw-r--r--activerecord/lib/active_record/associations/has_one_through_association.rb11
-rw-r--r--activerecord/lib/active_record/associations/preloader.rb2
-rw-r--r--activerecord/lib/active_record/associations/through_association.rb31
-rw-r--r--activerecord/lib/active_record/attribute_methods/dirty.rb6
-rw-r--r--activerecord/lib/active_record/attributes.rb2
-rw-r--r--activerecord/lib/active_record/callbacks.rb4
-rw-r--r--activerecord/lib/active_record/collection_cache_key.rb9
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb1
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb15
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb18
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/decimal.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb10
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb55
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb10
-rw-r--r--activerecord/lib/active_record/connection_adapters/schema_cache.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb26
-rw-r--r--activerecord/lib/active_record/core.rb7
-rw-r--r--activerecord/lib/active_record/inheritance.rb7
-rw-r--r--activerecord/lib/active_record/log_subscriber.rb41
-rw-r--r--activerecord/lib/active_record/migration.rb260
-rw-r--r--activerecord/lib/active_record/migration/compatibility.rb8
-rw-r--r--activerecord/lib/active_record/model_schema.rb32
-rw-r--r--activerecord/lib/active_record/persistence.rb11
-rw-r--r--activerecord/lib/active_record/railtie.rb1
-rw-r--r--activerecord/lib/active_record/railties/databases.rake19
-rw-r--r--activerecord/lib/active_record/reflection.rb17
-rw-r--r--activerecord/lib/active_record/relation.rb42
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb18
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb60
-rw-r--r--activerecord/lib/active_record/relation/merger.rb26
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/association_query_value.rb2
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb2
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb38
-rw-r--r--activerecord/lib/active_record/relation/where_clause_factory.rb2
-rw-r--r--activerecord/lib/active_record/sanitization.rb249
-rw-r--r--activerecord/lib/active_record/schema.rb2
-rw-r--r--activerecord/lib/active_record/schema_dumper.rb2
-rw-r--r--activerecord/lib/active_record/table_metadata.rb3
-rw-r--r--activerecord/lib/active_record/tasks/database_tasks.rb8
52 files changed, 657 insertions, 526 deletions
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index 5de6503144..b4377ad6be 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
#--
-# Copyright (c) 2004-2017 David Heinemeier Hansson
+# Copyright (c) 2004-2018 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 661605d3e5..b1cad0d0a4 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -1061,12 +1061,6 @@ module ActiveRecord
# belongs_to :dungeon, inverse_of: :evil_wizard
# end
#
- # There are limitations to <tt>:inverse_of</tt> support:
- #
- # * does not work with <tt>:through</tt> associations.
- # * does not work with <tt>:polymorphic</tt> associations.
- # * inverse associations for #belongs_to associations #has_many are ignored.
- #
# For more information, see the documentation for the +:inverse_of+ option.
#
# == Deleting from associations
@@ -1279,6 +1273,9 @@ module ActiveRecord
# Specify the foreign key used for the association. By default this is guessed to be the name
# of this class in lower-case and "_id" suffixed. So a Person class that makes a #has_many
# association will use "person_id" as the default <tt>:foreign_key</tt>.
+ #
+ # If you are going to modify the association (rather than just read from it), then it is
+ # a good idea to set the <tt>:inverse_of</tt> option.
# [:foreign_type]
# Specify the column used to store the associated object's type, if this is a polymorphic
# association. By default this is guessed to be the name of the polymorphic association
@@ -1352,8 +1349,7 @@ module ActiveRecord
# <tt>:autosave</tt> to <tt>true</tt>.
# [:inverse_of]
# Specifies the name of the #belongs_to association on the associated object
- # that is the inverse of this #has_many association. Does not work in combination
- # with <tt>:through</tt> or <tt>:as</tt> options.
+ # that is the inverse of this #has_many association.
# See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail.
# [:extend]
# Specifies a module or array of modules that will be extended into the association object returned.
@@ -1449,6 +1445,9 @@ module ActiveRecord
# Specify the foreign key used for the association. By default this is guessed to be the name
# of this class in lower-case and "_id" suffixed. So a Person class that makes a #has_one association
# will use "person_id" as the default <tt>:foreign_key</tt>.
+ #
+ # If you are going to modify the association (rather than just read from it), then it is
+ # a good idea to set the <tt>:inverse_of</tt> option.
# [:foreign_type]
# Specify the column used to store the associated object's type, if this is a polymorphic
# association. By default this is guessed to be the name of the polymorphic association
@@ -1464,6 +1463,9 @@ module ActiveRecord
# <tt>:primary_key</tt>, and <tt>:foreign_key</tt> are ignored, as the association uses the
# source reflection. You can only use a <tt>:through</tt> query through a #has_one
# or #belongs_to association on the join model.
+ #
+ # If you are going to modify the association (rather than just read from it), then it is
+ # a good idea to set the <tt>:inverse_of</tt> option.
# [:source]
# Specifies the source association name used by #has_one <tt>:through</tt> queries.
# Only use it if the name cannot be inferred from the association.
@@ -1484,8 +1486,7 @@ module ActiveRecord
# <tt>:autosave</tt> to <tt>true</tt>.
# [:inverse_of]
# Specifies the name of the #belongs_to association on the associated object
- # that is the inverse of this #has_one association. Does not work in combination
- # with <tt>:through</tt> or <tt>:as</tt> options.
+ # that is the inverse of this #has_one association.
# See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail.
# [:required]
# When set to +true+, the association will also have its presence validated.
@@ -1570,6 +1571,9 @@ module ActiveRecord
# association will use "person_id" as the default <tt>:foreign_key</tt>. Similarly,
# <tt>belongs_to :favorite_person, class_name: "Person"</tt> will use a foreign key
# of "favorite_person_id".
+ #
+ # If you are going to modify the association (rather than just read from it), then it is
+ # a good idea to set the <tt>:inverse_of</tt> option.
# [:foreign_type]
# Specify the column used to store the associated object's type, if this is a polymorphic
# association. By default this is guessed to be the name of the association with a "_type"
@@ -1619,8 +1623,7 @@ module ActiveRecord
# +after_commit+ and +after_rollback+ callbacks are executed.
# [:inverse_of]
# Specifies the name of the #has_one or #has_many association on the associated
- # object that is the inverse of this #belongs_to association. Does not work in
- # combination with the <tt>:polymorphic</tt> options.
+ # object that is the inverse of this #belongs_to association.
# See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail.
# [:optional]
# When set to +true+, the association will not have its presence validated.
@@ -1789,6 +1792,9 @@ module ActiveRecord
# of this class in lower-case and "_id" suffixed. So a Person class that makes
# a #has_and_belongs_to_many association to Project will use "person_id" as the
# default <tt>:foreign_key</tt>.
+ #
+ # If you are going to modify the association (rather than just read from it), then it is
+ # a good idea to set the <tt>:inverse_of</tt> option.
# [:association_foreign_key]
# Specify the foreign key used for the association on the receiving side of the association.
# By default this is guessed to be the name of the associated class in lower-case and "_id" suffixed.
diff --git a/activerecord/lib/active_record/associations/alias_tracker.rb b/activerecord/lib/active_record/associations/alias_tracker.rb
index 14881cfe17..4f3893588e 100644
--- a/activerecord/lib/active_record/associations/alias_tracker.rb
+++ b/activerecord/lib/active_record/associations/alias_tracker.rb
@@ -30,20 +30,12 @@ module ActiveRecord
join.left.scan(
/JOIN(?:\s+\w+)?\s+(?:\S+\s+)?(?:#{quoted_name}|#{name})\sON/i
).size
- elsif join.respond_to? :left
+ elsif join.is_a?(Arel::Nodes::Join)
join.left.name == name ? 1 : 0
elsif join.is_a?(Hash)
join.fetch(name, 0)
else
- # this branch is reached by two tests:
- #
- # activerecord/test/cases/associations/cascaded_eager_loading_test.rb:37
- # with :posts
- #
- # activerecord/test/cases/associations/eager_test.rb:1133
- # with :comments
- #
- 0
+ raise ArgumentError, "joins list should be initialized by list of Arel::Nodes::Join"
end
end
diff --git a/activerecord/lib/active_record/associations/belongs_to_association.rb b/activerecord/lib/active_record/associations/belongs_to_association.rb
index ba54cd8f49..bd2012df84 100644
--- a/activerecord/lib/active_record/associations/belongs_to_association.rb
+++ b/activerecord/lib/active_record/associations/belongs_to_association.rb
@@ -12,17 +12,20 @@ module ActiveRecord
if record
raise_on_type_mismatch!(record)
update_counters_on_replace(record)
- replace_keys(record)
set_inverse_instance(record)
@updated = true
else
decrement_counters
- remove_keys
end
self.target = record
end
+ def target=(record)
+ replace_keys(record)
+ super
+ end
+
def default(&block)
writer(owner.instance_exec(&block)) if reader.nil?
end
@@ -49,9 +52,9 @@ module ActiveRecord
def update_counters(by)
if require_counter_update? && foreign_key_present?
if target && !stale_target?
- target.increment!(reflection.counter_cache_column, by)
+ target.increment!(reflection.counter_cache_column, by, touch: reflection.options[:touch])
else
- klass.update_counters(target_id, reflection.counter_cache_column => by)
+ klass.update_counters(target_id, reflection.counter_cache_column => by, touch: reflection.options[:touch])
end
end
end
@@ -78,11 +81,8 @@ module ActiveRecord
end
def replace_keys(record)
- owner[reflection.foreign_key] = record._read_attribute(reflection.association_primary_key(record.class))
- end
-
- def remove_keys
- owner[reflection.foreign_key] = nil
+ owner[reflection.foreign_key] = record ?
+ record._read_attribute(reflection.association_primary_key(record.class)) : nil
end
def foreign_key_present?
diff --git a/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb b/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb
index 4ce3474bd5..55d789c66a 100644
--- a/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb
+++ b/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb
@@ -13,12 +13,7 @@ module ActiveRecord
def replace_keys(record)
super
- owner[reflection.foreign_type] = record.class.base_class.name
- end
-
- def remove_keys
- super
- owner[reflection.foreign_type] = nil
+ owner[reflection.foreign_type] = record ? record.class.base_class.name : nil
end
def different_target?(record)
diff --git a/activerecord/lib/active_record/associations/builder/association.rb b/activerecord/lib/active_record/associations/builder/association.rb
index ca3032d967..7c69cd65ee 100644
--- a/activerecord/lib/active_record/associations/builder/association.rb
+++ b/activerecord/lib/active_record/associations/builder/association.rb
@@ -104,8 +104,8 @@ module ActiveRecord::Associations::Builder # :nodoc:
def self.define_readers(mixin, name)
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
- def #{name}(*args)
- association(:#{name}).reader(*args)
+ def #{name}
+ association(:#{name}).reader
end
CODE
end
diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb
index 9904ee4bed..c161454c1a 100644
--- a/activerecord/lib/active_record/associations/builder/belongs_to.rb
+++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb
@@ -114,9 +114,13 @@ module ActiveRecord::Associations::Builder # :nodoc:
BelongsTo.touch_record(record, record.send(changes_method), foreign_key, n, touch, belongs_to_touch_method)
}}
- model.after_save callback.(:saved_changes), if: :saved_changes?
- model.after_touch callback.(:changes_to_save)
- model.after_destroy callback.(:changes_to_save)
+ unless reflection.counter_cache_column
+ model.after_create callback.(:saved_changes), if: :saved_changes?
+ model.after_destroy callback.(:changes_to_save)
+ end
+
+ model.after_update callback.(:saved_changes), if: :saved_changes?
+ model.after_touch callback.(:changes_to_save)
end
def self.add_default_callbacks(model, reflection)
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index 921237a735..de8afc9ccb 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -52,11 +52,11 @@ module ActiveRecord
# Implements the ids writer method, e.g. foo.item_ids= for Foo.has_many :items
def ids_writer(ids)
- pk_type = reflection.association_primary_key_type
+ primary_key = reflection.association_primary_key
+ pk_type = klass.type_for_attribute(primary_key.to_s)
ids = Array(ids).reject(&:blank?)
ids.map! { |i| pk_type.cast(i) }
- primary_key = reflection.association_primary_key
records = klass.where(primary_key => ids).index_by do |r|
r.public_send(primary_key)
end.values_at(*ids).compact
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index 07c7f28d2d..cf85a87fa7 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -63,7 +63,7 @@ module ActiveRecord
count = if reflection.has_cached_counter?
owner._read_attribute(reflection.counter_cache_column).to_i
else
- scope.count
+ scope.count(:all)
end
# If there's nothing in the database and @target has no new records
diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb
index adbf52b87c..59929b8c4e 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -8,9 +8,7 @@ module ActiveRecord
def initialize(owner, reflection)
super
-
- @through_records = {}
- @through_association = nil
+ @through_records = {}
end
def concat(*records)
@@ -50,11 +48,6 @@ module ActiveRecord
end
private
-
- def through_association
- @through_association ||= owner.association(through_reflection.name)
- end
-
# The through record (built with build_record) is temporarily cached
# so that it may be reused if insert_record is subsequently called.
#
@@ -140,21 +133,15 @@ module ActiveRecord
scope = through_association.scope
scope.where! construct_join_attributes(*records)
+ scope = scope.where(through_scope_attributes)
case method
when :destroy
if scope.klass.primary_key
- count = scope.destroy_all.length
+ count = scope.destroy_all.count(&:destroyed?)
else
scope.each(&:_run_destroy_callbacks)
-
- arel = scope.arel
-
- stmt = Arel::DeleteManager.new
- stmt.from scope.klass.arel_table
- stmt.wheres = arel.constraints
-
- count = scope.klass.connection.delete(stmt, "SQL")
+ count = scope.delete_all
end
when :nullify
count = scope.update_all(source_reflection.foreign_key => nil)
diff --git a/activerecord/lib/active_record/associations/has_one_through_association.rb b/activerecord/lib/active_record/associations/has_one_through_association.rb
index 36746f9115..491282adf7 100644
--- a/activerecord/lib/active_record/associations/has_one_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_through_association.rb
@@ -6,17 +6,16 @@ module ActiveRecord
class HasOneThroughAssociation < HasOneAssociation #:nodoc:
include ThroughAssociation
- def replace(record)
- create_through_record(record)
+ def replace(record, save = true)
+ create_through_record(record, save)
self.target = record
end
private
-
- def create_through_record(record)
+ def create_through_record(record, save)
ensure_not_nested
- through_proxy = owner.association(through_reflection.name)
+ through_proxy = through_association
through_record = through_proxy.load_target
if through_record && !record
@@ -30,7 +29,7 @@ module ActiveRecord
if through_record
through_record.update(attributes)
- elsif owner.new_record?
+ elsif owner.new_record? || !save
through_proxy.build(attributes)
else
through_proxy.create(attributes)
diff --git a/activerecord/lib/active_record/associations/preloader.rb b/activerecord/lib/active_record/associations/preloader.rb
index e1087be9b3..59320431ee 100644
--- a/activerecord/lib/active_record/associations/preloader.rb
+++ b/activerecord/lib/active_record/associations/preloader.rb
@@ -83,7 +83,7 @@ module ActiveRecord
# { author: :avatar }
# [ :books, { author: :avatar } ]
def preload(records, associations, preload_scope = nil)
- records = records.compact
+ records = Array.wrap(records).compact
if records.empty?
[]
diff --git a/activerecord/lib/active_record/associations/through_association.rb b/activerecord/lib/active_record/associations/through_association.rb
index bce2a95ce1..5afb0bc068 100644
--- a/activerecord/lib/active_record/associations/through_association.rb
+++ b/activerecord/lib/active_record/associations/through_association.rb
@@ -4,9 +4,24 @@ module ActiveRecord
module Associations
# = Active Record Through Association
module ThroughAssociation #:nodoc:
- delegate :source_reflection, :through_reflection, to: :reflection
+ delegate :source_reflection, to: :reflection
private
+ def through_reflection
+ @through_reflection ||= begin
+ refl = reflection.through_reflection
+
+ while refl.through_reflection?
+ refl = refl.through_reflection
+ end
+
+ refl
+ end
+ end
+
+ def through_association
+ @through_association ||= owner.association(through_reflection.name)
+ end
# We merge in these scopes for two reasons:
#
@@ -38,24 +53,22 @@ module ActiveRecord
def construct_join_attributes(*records)
ensure_mutable
- if source_reflection.association_primary_key(reflection.klass) == reflection.klass.primary_key
+ association_primary_key = source_reflection.association_primary_key(reflection.klass)
+
+ if association_primary_key == reflection.klass.primary_key && !options[:source_type]
join_attributes = { source_reflection.name => records }
else
join_attributes = {
- source_reflection.foreign_key =>
- records.map { |record|
- record.send(source_reflection.association_primary_key(reflection.klass))
- }
+ source_reflection.foreign_key => records.map(&association_primary_key.to_sym)
}
end
if options[:source_type]
- join_attributes[source_reflection.foreign_type] =
- records.map { |record| record.class.base_class.name }
+ join_attributes[source_reflection.foreign_type] = [ options[:source_type] ]
end
if records.count == 1
- Hash[join_attributes.map { |k, v| [k, v.first] }]
+ join_attributes.transform_values!(&:first)
else
join_attributes
end
diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb
index 3de6fe566d..df4c79b0f6 100644
--- a/activerecord/lib/active_record/attribute_methods/dirty.rb
+++ b/activerecord/lib/active_record/attribute_methods/dirty.rb
@@ -32,9 +32,7 @@ module ActiveRecord
# <tt>reload</tt> the record and clears changed attributes.
def reload(*)
super.tap do
- @previously_changed = ActiveSupport::HashWithIndifferentAccess.new
@mutations_before_last_save = nil
- @attributes_changed_by_setter = ActiveSupport::HashWithIndifferentAccess.new
@mutations_from_database = nil
end
end
@@ -114,12 +112,12 @@ module ActiveRecord
# Alias for +changed+
def changed_attribute_names_to_save
- changes_to_save.keys
+ mutations_from_database.changed_attribute_names
end
# Alias for +changed_attributes+
def attributes_in_database
- changes_to_save.transform_values(&:first)
+ mutations_from_database.changed_values
end
private
diff --git a/activerecord/lib/active_record/attributes.rb b/activerecord/lib/active_record/attributes.rb
index 0b7c9398a8..35150889d9 100644
--- a/activerecord/lib/active_record/attributes.rb
+++ b/activerecord/lib/active_record/attributes.rb
@@ -57,7 +57,7 @@ module ActiveRecord
# store_listing = StoreListing.new(price_in_cents: '10.1')
#
# # before
- # store_listing.price_in_cents # => BigDecimal.new(10.1)
+ # store_listing.price_in_cents # => BigDecimal(10.1)
#
# class StoreListing < ActiveRecord::Base
# attribute :price_in_cents, :integer
diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb
index 9ab2780760..9dbdf845bd 100644
--- a/activerecord/lib/active_record/callbacks.rb
+++ b/activerecord/lib/active_record/callbacks.rb
@@ -232,7 +232,7 @@ module ActiveRecord
#
# For example:
#
- # class Topic
+ # class Topic < ActiveRecord::Base
# has_many :children
#
# after_save :log_children
@@ -257,7 +257,7 @@ module ActiveRecord
#
# For example:
#
- # class Topic
+ # class Topic < ActiveRecord::Base
# has_many :children
#
# after_commit :log_children
diff --git a/activerecord/lib/active_record/collection_cache_key.rb b/activerecord/lib/active_record/collection_cache_key.rb
index 88b398ad45..0520591f4f 100644
--- a/activerecord/lib/active_record/collection_cache_key.rb
+++ b/activerecord/lib/active_record/collection_cache_key.rb
@@ -3,11 +3,11 @@
module ActiveRecord
module CollectionCacheKey
def collection_cache_key(collection = all, timestamp_column = :updated_at) # :nodoc:
- query_signature = Digest::MD5.hexdigest(collection.to_sql)
+ query_signature = ActiveSupport::Digest.hexdigest(collection.to_sql)
key = "#{collection.model_name.cache_key}/query-#{query_signature}"
- if collection.loaded?
- size = collection.size
+ if collection.loaded? || collection.distinct_value
+ size = collection.records.size
if size > 0
timestamp = collection.max_by(&timestamp_column)._read_attribute(timestamp_column)
end
@@ -20,8 +20,7 @@ module ActiveRecord
select_values = "COUNT(*) AS #{connection.quote_column_name("size")}, MAX(%s) AS timestamp"
if collection.has_limit_or_offset?
- query = collection.spawn
- query.select_values = [column]
+ query = collection.select(column)
subquery_alias = "subquery_for_cache_key"
subquery_column = "#{subquery_alias}.#{timestamp_column}"
subquery = query.arel.as(subquery_alias)
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
index 9849f9d5d7..c730584902 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -1005,9 +1005,7 @@ module ActiveRecord
def retrieve_connection(spec_name) #:nodoc:
pool = retrieve_connection_pool(spec_name)
raise ConnectionNotEstablished, "No connection pool with '#{spec_name}' found." unless pool
- conn = pool.connection
- raise ConnectionNotEstablished, "No connection for '#{spec_name}' in connection pool" unless conn
- conn
+ pool.connection
end
# Returns true if a connection that's accessible to this class has
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
index bd05fb8f6e..4a191d337c 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
@@ -95,6 +95,7 @@ module ActiveRecord
if options_sql = options[:options]
create_sql << " #{options_sql}"
end
+ create_sql
end
def column_options(o)
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
index 4f58b0242c..c32a234be4 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -1049,8 +1049,8 @@ module ActiveRecord
sm_table = quote_table_name(ActiveRecord::SchemaMigration.table_name)
migrated = ActiveRecord::SchemaMigration.all_versions.map(&:to_i)
- versions = ActiveRecord::Migrator.migration_files(migrations_paths).map do |file|
- ActiveRecord::Migrator.parse_migration_filename(file).first.to_i
+ versions = migration_context.migration_files.map do |file|
+ migration_context.parse_migration_filename(file).first.to_i
end
unless migrated.include?(version)
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index fc80d332f9..7bd54f777e 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -119,6 +119,14 @@ module ActiveRecord
end
end
+ def migrations_paths
+ @config[:migrations_paths] || Migrator.migrations_paths
+ end
+
+ def migration_context
+ MigrationContext.new(migrations_paths)
+ end
+
class Version
include Comparable
@@ -537,12 +545,7 @@ module ActiveRecord
end
def extract_limit(sql_type)
- case sql_type
- when /^bigint/i
- 8
- when /\((.*)\)/
- $1.to_i
- end
+ $1.to_i if sql_type =~ /\((.*)\)/
end
def translate_exception_class(e, sql)
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 479131caad..b394a8969f 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -117,11 +117,11 @@ module ActiveRecord
end
def get_advisory_lock(lock_name, timeout = 0) # :nodoc:
- query_value("SELECT GET_LOCK(#{quote(lock_name)}, #{timeout})") == 1
+ query_value("SELECT GET_LOCK(#{quote(lock_name.to_s)}, #{timeout})") == 1
end
def release_advisory_lock(lock_name) # :nodoc:
- query_value("SELECT RELEASE_LOCK(#{quote(lock_name)})") == 1
+ query_value("SELECT RELEASE_LOCK(#{quote(lock_name.to_s)})") == 1
end
def native_database_types
@@ -249,7 +249,7 @@ module ActiveRecord
# create_database 'matt_development', charset: :big5
def create_database(name, options = {})
if options[:collation]
- execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET #{quote_table_name(options[:charset] || 'utf8')} COLLATE #{quote_table_name(options[:collation])}"
+ execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT COLLATE #{quote_table_name(options[:collation])}"
else
execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET #{quote_table_name(options[:charset] || 'utf8')}"
end
@@ -292,10 +292,6 @@ module ActiveRecord
SQL
end
- def create_table(table_name, **options) #:nodoc:
- super(table_name, options: "ENGINE=InnoDB", **options)
- end
-
def bulk_change_table(table_name, operations) #:nodoc:
sqls = operations.flat_map do |command, args|
table, arguments = args.shift, args
@@ -403,12 +399,13 @@ module ActiveRecord
fk.constraint_name AS 'name',
rc.update_rule AS 'on_update',
rc.delete_rule AS 'on_delete'
- FROM information_schema.key_column_usage fk
- JOIN information_schema.referential_constraints rc
+ FROM information_schema.referential_constraints rc
+ JOIN information_schema.key_column_usage fk
USING (constraint_schema, constraint_name)
WHERE fk.referenced_column_name IS NOT NULL
AND fk.table_schema = #{scope[:schema]}
AND fk.table_name = #{scope[:name]}
+ AND rc.constraint_schema = #{scope[:schema]}
AND rc.table_name = #{scope[:name]}
SQL
@@ -716,7 +713,8 @@ module ActiveRecord
# to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on'
subselect.distinct unless select.limit || select.offset || select.orders.any?
- Arel::SelectManager.new(subselect.as("__active_record_temp")).project(Arel.sql(key.name))
+ key_name = quote_column_name(key.name)
+ Arel::SelectManager.new(subselect.as("__active_record_temp")).project(Arel.sql(key_name))
end
def supports_rename_index?
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/decimal.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/decimal.rb
index 879dba7afd..e7d33855c4 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/decimal.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/decimal.rb
@@ -6,7 +6,7 @@ module ActiveRecord
module OID # :nodoc:
class Decimal < Type::Decimal # :nodoc:
def infinity(options = {})
- BigDecimal.new("Infinity") * (options[:negative] ? -1 : 1)
+ BigDecimal("Infinity") * (options[:negative] ? -1 : 1)
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb
index a89aa5ea09..6edb7cfd3c 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb
@@ -60,7 +60,7 @@ module ActiveRecord
end
def type_cast_single_for_database(value)
- infinity?(value) ? "" : @subtype.serialize(value)
+ infinity?(value) ? value : @subtype.serialize(value)
end
def extract_bounds(value)
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
index 9fdeab06c1..e75202b0be 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
@@ -138,7 +138,7 @@ module ActiveRecord
end
def encode_range(range)
- "[#{type_cast(range.first)},#{type_cast(range.last)}#{range.exclude_end? ? ')' : ']'}"
+ "[#{type_cast_range_value(range.first)},#{type_cast_range_value(range.last)}#{range.exclude_end? ? ')' : ']'}"
end
def determine_encoding_of_strings_in_array(value)
@@ -154,6 +154,14 @@ module ActiveRecord
else _type_cast(values)
end
end
+
+ def type_cast_range_value(value)
+ infinity?(value) ? "" : type_cast(value)
+ end
+
+ def infinity?(value)
+ value.respond_to?(:infinite?) && value.infinite?
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
index bf5fbb30e1..b9d2f4da39 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -38,7 +38,7 @@ module ActiveRecord
" TABLESPACE = \"#{value}\""
when :connection_limit
" CONNECTION LIMIT = #{value}"
- else
+ else
""
end
end
@@ -364,6 +364,31 @@ module ActiveRecord
SQL
end
+ def bulk_change_table(table_name, operations)
+ sql_fragments = []
+ non_combinable_operations = []
+
+ operations.each do |command, args|
+ table, arguments = args.shift, args
+ method = :"#{command}_for_alter"
+
+ if respond_to?(method, true)
+ sqls, procs = Array(send(method, table, *arguments)).partition { |v| v.is_a?(String) }
+ sql_fragments << sqls
+ non_combinable_operations << procs if procs.present?
+ else
+ execute "ALTER TABLE #{quote_table_name(table_name)} #{sql_fragments.join(", ")}" unless sql_fragments.empty?
+ non_combinable_operations.each(&:call)
+ sql_fragments = []
+ non_combinable_operations = []
+ send(command, table, *arguments)
+ end
+ end
+
+ execute "ALTER TABLE #{quote_table_name(table_name)} #{sql_fragments.join(", ")}" unless sql_fragments.empty?
+ non_combinable_operations.each(&:call)
+ end
+
# Renames a table.
# Also renames a table's primary key sequence if the sequence name exists and
# matches the Active Record default.
@@ -394,7 +419,7 @@ module ActiveRecord
def change_column(table_name, column_name, type, options = {}) #:nodoc:
clear_cache!
- sqls, procs = change_column_for_alter(table_name, column_name, type, options)
+ sqls, procs = Array(change_column_for_alter(table_name, column_name, type, options)).partition { |v| v.is_a?(String) }
execute "ALTER TABLE #{quote_table_name(table_name)} #{sqls.join(", ")}"
procs.each(&:call)
end
@@ -502,6 +527,14 @@ module ActiveRecord
end
end
+ def foreign_tables
+ query_values(data_source_sql(type: "FOREIGN TABLE"), "SCHEMA")
+ end
+
+ def foreign_table_exists?(table_name)
+ query_values(data_source_sql(table_name, type: "FOREIGN TABLE"), "SCHEMA").any? if table_name.present?
+ end
+
# Maps logical Rails types to PostgreSQL-specific data types.
def type_to_sql(type, limit: nil, precision: nil, scale: nil, array: nil, **) # :nodoc:
sql = \
@@ -665,12 +698,10 @@ module ActiveRecord
def change_column_for_alter(table_name, column_name, type, options = {})
sqls = [change_column_sql(table_name, column_name, type, options)]
- procs = []
sqls << change_column_default_for_alter(table_name, column_name, options[:default]) if options.key?(:default)
sqls << change_column_null_for_alter(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
- procs << Proc.new { change_column_comment(table_name, column_name, options[:comment]) } if options.key?(:comment)
-
- [sqls, procs]
+ sqls << Proc.new { change_column_comment(table_name, column_name, options[:comment]) } if options.key?(:comment)
+ sqls
end
@@ -694,6 +725,14 @@ module ActiveRecord
"ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL"
end
+ def add_timestamps_for_alter(table_name, options = {})
+ [add_column_for_alter(table_name, :created_at, :datetime, options), add_column_for_alter(table_name, :updated_at, :datetime, options)]
+ end
+
+ def remove_timestamps_for_alter(table_name, options = {})
+ [remove_column_for_alter(table_name, :updated_at), remove_column_for_alter(table_name, :created_at)]
+ end
+
def add_index_opclass(quoted_columns, **options)
opclasses = options_for_index_columns(options[:opclass])
quoted_columns.each do |name, column|
@@ -708,7 +747,7 @@ module ActiveRecord
def data_source_sql(name = nil, type: nil)
scope = quoted_scope(name, type: type)
- scope[:type] ||= "'r','v','m'" # (r)elation/table, (v)iew, (m)aterialized view
+ scope[:type] ||= "'r','v','m','f'" # (r)elation/table, (v)iew, (m)aterialized view, (f)oreign table
sql = "SELECT c.relname FROM pg_class c LEFT JOIN pg_namespace n ON n.oid = c.relnamespace".dup
sql << " WHERE n.nspname = #{scope[:schema]}"
@@ -725,6 +764,8 @@ module ActiveRecord
"'r'"
when "VIEW"
"'v','m'"
+ when "FOREIGN TABLE"
+ "'f'"
end
scope = {}
scope[:schema] = schema ? quote(schema) : "ANY (current_schemas(false))"
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 23fc69d649..ddc5a91286 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
# Make sure we're using pg high enough for type casts and Ruby 2.2+ compatibility
-gem "pg", "~> 0.18"
+gem "pg", ">= 0.18", "< 2.0"
require "pg"
require "active_record/connection_adapters/abstract_adapter"
@@ -122,6 +122,10 @@ module ActiveRecord
include PostgreSQL::SchemaStatements
include PostgreSQL::DatabaseStatements
+ def supports_bulk_alter?
+ true
+ end
+
def supports_index_sort_order?
true
end
@@ -314,6 +318,10 @@ module ActiveRecord
postgresql_version >= 90300
end
+ def supports_foreign_tables?
+ postgresql_version >= 90300
+ end
+
def supports_pgcrypto_uuid?
postgresql_version >= 90400
end
diff --git a/activerecord/lib/active_record/connection_adapters/schema_cache.rb b/activerecord/lib/active_record/connection_adapters/schema_cache.rb
index f34b6733da..c29cf1f9a1 100644
--- a/activerecord/lib/active_record/connection_adapters/schema_cache.rb
+++ b/activerecord/lib/active_record/connection_adapters/schema_cache.rb
@@ -28,7 +28,7 @@ module ActiveRecord
coder["columns_hash"] = @columns_hash
coder["primary_keys"] = @primary_keys
coder["data_sources"] = @data_sources
- coder["version"] = ActiveRecord::Migrator.current_version
+ coder["version"] = connection.migration_context.current_version
end
def init_with(coder)
@@ -100,7 +100,7 @@ module ActiveRecord
def marshal_dump
# if we get current version during initialization, it happens stack over flow.
- @version = ActiveRecord::Migrator.current_version
+ @version = connection.migration_context.current_version
[@version, @columns, @columns_hash, @primary_keys, @data_sources]
end
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index c72db15ce3..9c69d8f13b 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -60,7 +60,7 @@ module ActiveRecord
include SQLite3::SchemaStatements
NATIVE_DATABASE_TYPES = {
- primary_key: "INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL",
+ primary_key: "integer PRIMARY KEY AUTOINCREMENT NOT NULL",
string: { name: "varchar" },
text: { name: "text" },
integer: { name: "integer" },
@@ -101,7 +101,7 @@ module ActiveRecord
def initialize(connection, logger, connection_options, config)
super(connection, logger, config)
- @active = nil
+ @active = true
@statements = StatementPool.new(self.class.type_cast_config_to_integer(config[:statement_limit]))
configure_connection
@@ -144,7 +144,7 @@ module ActiveRecord
end
def active?
- @active != false
+ @active
end
# Disconnects from the database if already connected. Otherwise, this
@@ -290,19 +290,18 @@ module ActiveRecord
rename_table_indexes(table_name, new_name)
end
- # See: https://www.sqlite.org/lang_altertable.html
- # SQLite has an additional restriction on the ALTER TABLE statement
- def valid_alter_table_type?(type)
- type.to_sym != :primary_key
+ def valid_alter_table_type?(type, options = {})
+ !invalid_alter_table_type?(type, options)
end
+ deprecate :valid_alter_table_type?
def add_column(table_name, column_name, type, options = {}) #:nodoc:
- if valid_alter_table_type?(type)
- super(table_name, column_name, type, options)
- else
+ if invalid_alter_table_type?(type, options)
alter_table(table_name) do |definition|
definition.column(column_name, type, options)
end
+ else
+ super
end
end
@@ -386,6 +385,12 @@ module ActiveRecord
end
alias column_definitions table_structure
+ # See: https://www.sqlite.org/lang_altertable.html
+ # SQLite has an additional restriction on the ALTER TABLE statement
+ def invalid_alter_table_type?(type, options)
+ type.to_sym == :primary_key || options[:primary_key]
+ end
+
def alter_table(table_name, options = {})
altered_table_name = "a#{table_name}"
caller = lambda { |definition| yield definition if block_given? }
@@ -452,6 +457,7 @@ module ActiveRecord
# index name can't be the same
opts = { name: name.gsub(/(^|_)(#{from})_/, "\\1#{to}_"), internal: true }
opts[:unique] = true if index.unique
+ opts[:where] = index.where if index.where
add_index(to, columns, opts)
end
end
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index 481159e501..88810cb328 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -18,6 +18,13 @@ module ActiveRecord
mattr_accessor :logger, instance_writer: false
##
+ # :singleton-method:
+ #
+ # Specifies if the methods calling database queries should be logged below
+ # their relevant queries. Defaults to false.
+ mattr_accessor :verbose_query_logs, instance_writer: false, default: false
+
+ ##
# Contains the database configuration - as is typically stored in config/database.yml -
# as a Hash.
#
diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb
index 0715c11473..f3fe610c09 100644
--- a/activerecord/lib/active_record/inheritance.rb
+++ b/activerecord/lib/active_record/inheritance.rb
@@ -47,14 +47,13 @@ module ActiveRecord
# Determines if one of the attributes passed in is the inheritance column,
# and if the inheritance column is attr accessible, it initializes an
# instance of the given subclass instead of the base class.
- def new(*args, &block)
+ def new(attributes = nil, &block)
if abstract_class? || self == Base
raise NotImplementedError, "#{self} is an abstract class and cannot be instantiated."
end
- attrs = args.first
if has_attribute?(inheritance_column)
- subclass = subclass_from_attributes(attrs)
+ subclass = subclass_from_attributes(attributes)
if subclass.nil? && base_class == self
subclass = subclass_from_attributes(column_defaults)
@@ -62,7 +61,7 @@ module ActiveRecord
end
if subclass && subclass != self
- subclass.new(*args, &block)
+ subclass.new(attributes, &block)
else
super
end
diff --git a/activerecord/lib/active_record/log_subscriber.rb b/activerecord/lib/active_record/log_subscriber.rb
index 405f3a30c6..9234029c22 100644
--- a/activerecord/lib/active_record/log_subscriber.rb
+++ b/activerecord/lib/active_record/log_subscriber.rb
@@ -90,6 +90,47 @@ module ActiveRecord
def logger
ActiveRecord::Base.logger
end
+
+ def debug(progname = nil, &block)
+ return unless super
+
+ if ActiveRecord::Base.verbose_query_logs
+ log_query_source
+ end
+ end
+
+ def log_query_source
+ source_line, line_number = extract_callstack(caller_locations)
+
+ if source_line
+ if defined?(::Rails.root)
+ app_root = "#{::Rails.root.to_s}/".freeze
+ source_line = source_line.sub(app_root, "")
+ end
+
+ logger.debug(" ↳ #{ source_line }:#{ line_number }")
+ end
+ end
+
+ def extract_callstack(callstack)
+ line = callstack.find do |frame|
+ frame.absolute_path && !ignored_callstack(frame.absolute_path)
+ end
+
+ offending_line = line || callstack.first
+
+ [
+ offending_line.path,
+ offending_line.lineno
+ ]
+ end
+
+ RAILS_GEM_ROOT = File.expand_path("../../../..", __FILE__) + "/"
+
+ def ignored_callstack(path)
+ path.start_with?(RAILS_GEM_ROOT) ||
+ path.start_with?(RbConfig::CONFIG["rubylibdir"])
+ end
end
end
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index 5c10d4ff24..798328233f 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -3,6 +3,7 @@
require "set"
require "zlib"
require "active_support/core_ext/module/attribute_accessors"
+require "active_record/tasks/database_tasks"
module ActiveRecord
class MigrationError < ActiveRecordError#:nodoc:
@@ -550,7 +551,7 @@ module ActiveRecord
end
def call(env)
- mtime = ActiveRecord::Migrator.last_migration.mtime.to_i
+ mtime = ActiveRecord::Base.connection.migration_context.last_migration.mtime.to_i
if @last_check < mtime
ActiveRecord::Migration.check_pending!(connection)
@last_check = mtime
@@ -575,11 +576,11 @@ module ActiveRecord
# Raises <tt>ActiveRecord::PendingMigrationError</tt> error if any migrations are pending.
def check_pending!(connection = Base.connection)
- raise ActiveRecord::PendingMigrationError if ActiveRecord::Migrator.needs_migration?(connection)
+ raise ActiveRecord::PendingMigrationError if connection.migration_context.needs_migration?
end
def load_schema_if_pending!
- if ActiveRecord::Migrator.needs_migration? || !ActiveRecord::Migrator.any_migrations?
+ if Base.connection.migration_context.needs_migration? || !Base.connection.migration_context.any_migrations?
# Roundtrip to Rake to allow plugins to hook into database initialization.
root = defined?(ENGINE_ROOT) ? ENGINE_ROOT : Rails.root
FileUtils.cd(root) do
@@ -876,10 +877,10 @@ module ActiveRecord
FileUtils.mkdir_p(destination) unless File.exist?(destination)
- destination_migrations = ActiveRecord::Migrator.migrations(destination)
+ destination_migrations = ActiveRecord::MigrationContext.new(destination).migrations
last = destination_migrations.last
sources.each do |scope, path|
- source_migrations = ActiveRecord::Migrator.migrations(path)
+ source_migrations = ActiveRecord::MigrationContext.new(path).migrations
source_migrations.each do |migration|
source = File.binread(migration.filename)
@@ -997,132 +998,147 @@ module ActiveRecord
end
end
- class Migrator#:nodoc:
- class << self
- attr_writer :migrations_paths
- alias :migrations_path= :migrations_paths=
-
- def migrate(migrations_paths, target_version = nil, &block)
- case
- when target_version.nil?
- up(migrations_paths, target_version, &block)
- when current_version == 0 && target_version == 0
- []
- when current_version > target_version
- down(migrations_paths, target_version, &block)
- else
- up(migrations_paths, target_version, &block)
- end
- end
+ class MigrationContext # :nodoc:
+ attr_reader :migrations_paths
- def rollback(migrations_paths, steps = 1)
- move(:down, migrations_paths, steps)
- end
+ def initialize(migrations_paths)
+ @migrations_paths = migrations_paths
+ end
- def forward(migrations_paths, steps = 1)
- move(:up, migrations_paths, steps)
+ def migrate(target_version = nil, &block)
+ case
+ when target_version.nil?
+ up(target_version, &block)
+ when current_version == 0 && target_version == 0
+ []
+ when current_version > target_version
+ down(target_version, &block)
+ else
+ up(target_version, &block)
end
+ end
- def up(migrations_paths, target_version = nil)
- migrations = migrations(migrations_paths)
- migrations.select! { |m| yield m } if block_given?
+ def rollback(steps = 1)
+ move(:down, steps)
+ end
- new(:up, migrations, target_version).migrate
+ def forward(steps = 1)
+ move(:up, steps)
+ end
+
+ def up(target_version = nil)
+ selected_migrations = if block_given?
+ migrations.select { |m| yield m }
+ else
+ migrations
end
- def down(migrations_paths, target_version = nil)
- migrations = migrations(migrations_paths)
- migrations.select! { |m| yield m } if block_given?
+ Migrator.new(:up, selected_migrations, target_version).migrate
+ end
- new(:down, migrations, target_version).migrate
+ def down(target_version = nil)
+ selected_migrations = if block_given?
+ migrations.select { |m| yield m }
+ else
+ migrations
end
- def run(direction, migrations_paths, target_version)
- new(direction, migrations(migrations_paths), target_version).run
- end
+ Migrator.new(:down, selected_migrations, target_version).migrate
+ end
- def open(migrations_paths)
- new(:up, migrations(migrations_paths), nil)
- end
+ def run(direction, target_version)
+ Migrator.new(direction, migrations, target_version).run
+ end
- def get_all_versions
- if SchemaMigration.table_exists?
- SchemaMigration.all_versions.map(&:to_i)
- else
- []
- end
- end
+ def open
+ Migrator.new(:up, migrations, nil)
+ end
- def current_version(connection = nil)
- get_all_versions.max || 0
- rescue ActiveRecord::NoDatabaseError
+ def get_all_versions
+ if SchemaMigration.table_exists?
+ SchemaMigration.all_versions.map(&:to_i)
+ else
+ []
end
+ end
- def needs_migration?(connection = nil)
- (migrations(migrations_paths).collect(&:version) - get_all_versions).size > 0
- end
+ def current_version
+ get_all_versions.max || 0
+ rescue ActiveRecord::NoDatabaseError
+ end
- def any_migrations?
- migrations(migrations_paths).any?
- end
+ def needs_migration?
+ (migrations.collect(&:version) - get_all_versions).size > 0
+ end
- def last_migration #:nodoc:
- migrations(migrations_paths).last || NullMigration.new
- end
+ def any_migrations?
+ migrations.any?
+ end
- def migrations_paths
- @migrations_paths ||= ["db/migrate"]
- # just to not break things if someone uses: migrations_path = some_string
- Array(@migrations_paths)
- end
+ def last_migration #:nodoc:
+ migrations.last || NullMigration.new
+ end
- def parse_migration_filename(filename) # :nodoc:
- File.basename(filename).scan(Migration::MigrationFilenameRegexp).first
+ def parse_migration_filename(filename) # :nodoc:
+ File.basename(filename).scan(Migration::MigrationFilenameRegexp).first
+ end
+
+ def migrations
+ migrations = migration_files.map do |file|
+ version, name, scope = parse_migration_filename(file)
+ raise IllegalMigrationNameError.new(file) unless version
+ version = version.to_i
+ name = name.camelize
+
+ MigrationProxy.new(name, version, file, scope)
end
- def migrations(paths)
- paths = Array(paths)
+ migrations.sort_by(&:version)
+ end
- migrations = migration_files(paths).map do |file|
- version, name, scope = parse_migration_filename(file)
- raise IllegalMigrationNameError.new(file) unless version
- version = version.to_i
- name = name.camelize
+ def migrations_status
+ db_list = ActiveRecord::SchemaMigration.normalized_versions
- MigrationProxy.new(name, version, file, scope)
- end
+ file_list = migration_files.map do |file|
+ version, name, scope = parse_migration_filename(file)
+ raise IllegalMigrationNameError.new(file) unless version
+ version = ActiveRecord::SchemaMigration.normalize_migration_number(version)
+ status = db_list.delete(version) ? "up" : "down"
+ [status, version, (name + scope).humanize]
+ end.compact
- migrations.sort_by(&:version)
+ db_list.map! do |version|
+ ["up", version, "********** NO FILE **********"]
end
- def migrations_status(paths)
- paths = Array(paths)
-
- db_list = ActiveRecord::SchemaMigration.normalized_versions
+ (db_list + file_list).sort_by { |_, version, _| version }
+ end
- file_list = migration_files(paths).map do |file|
- version, name, scope = parse_migration_filename(file)
- raise IllegalMigrationNameError.new(file) unless version
- version = ActiveRecord::SchemaMigration.normalize_migration_number(version)
- status = db_list.delete(version) ? "up" : "down"
- [status, version, (name + scope).humanize]
- end.compact
+ def migration_files
+ paths = Array(migrations_paths)
+ Dir[*paths.flat_map { |path| "#{path}/**/[0-9]*_*.rb" }]
+ end
- db_list.map! do |version|
- ["up", version, "********** NO FILE **********"]
- end
+ def current_environment
+ ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
+ end
- (db_list + file_list).sort_by { |_, version, _| version }
- end
+ def protected_environment?
+ ActiveRecord::Base.protected_environments.include?(last_stored_environment) if last_stored_environment
+ end
- def migration_files(paths)
- Dir[*paths.flat_map { |path| "#{path}/**/[0-9]*_*.rb" }]
- end
+ def last_stored_environment
+ return nil if current_version == 0
+ raise NoEnvironmentInSchemaError unless ActiveRecord::InternalMetadata.table_exists?
- private
+ environment = ActiveRecord::InternalMetadata[:environment]
+ raise NoEnvironmentInSchemaError unless environment
+ environment
+ end
- def move(direction, migrations_paths, steps)
- migrator = new(direction, migrations(migrations_paths))
+ private
+ def move(direction, steps)
+ migrator = Migrator.new(direction, migrations)
if current_version != 0 && !migrator.current_migration
raise UnknownMigrationVersionError.new(current_version)
@@ -1137,10 +1153,29 @@ module ActiveRecord
finish = migrator.migrations[start_index + steps]
version = finish ? finish.version : 0
- send(direction, migrations_paths, version)
+ send(direction, version)
+ end
+ end
+
+ class Migrator # :nodoc:
+ class << self
+ attr_accessor :migrations_paths
+
+ def migrations_path=(path)
+ ActiveSupport::Deprecation.warn \
+ "ActiveRecord::Migrator.migrations_paths= is now deprecated and will be removed in Rails 6.0." \
+ "You can set the `migrations_paths` on the `connection` instead through the `database.yml`."
+ self.migrations_paths = [path]
+ end
+
+ # For cases where a table doesn't exist like loading from schema cache
+ def current_version
+ MigrationContext.new(migrations_paths).current_version
end
end
+ self.migrations_paths = ["db/migrate"]
+
def initialize(direction, migrations, target_version = nil)
@direction = direction
@target_version = target_version
@@ -1203,7 +1238,7 @@ module ActiveRecord
end
def load_migrated
- @migrated_versions = Set.new(self.class.get_all_versions)
+ @migrated_versions = Set.new(Base.connection.migration_context.get_all_versions)
end
private
@@ -1235,7 +1270,7 @@ module ActiveRecord
# Stores the current environment in the database.
def record_environment
return if down?
- ActiveRecord::InternalMetadata[:environment] = ActiveRecord::Migrator.current_environment
+ ActiveRecord::InternalMetadata[:environment] = ActiveRecord::Base.connection.migration_context.current_environment
end
def ran?(migration)
@@ -1277,10 +1312,10 @@ module ActiveRecord
end
def validate(migrations)
- name , = migrations.group_by(&:name).find { |_, v| v.length > 1 }
+ name, = migrations.group_by(&:name).find { |_, v| v.length > 1 }
raise DuplicateMigrationNameError.new(name) if name
- version , = migrations.group_by(&:version).find { |_, v| v.length > 1 }
+ version, = migrations.group_by(&:version).find { |_, v| v.length > 1 }
raise DuplicateMigrationVersionError.new(version) if version
end
@@ -1294,23 +1329,6 @@ module ActiveRecord
end
end
- def self.last_stored_environment
- return nil if current_version == 0
- raise NoEnvironmentInSchemaError unless ActiveRecord::InternalMetadata.table_exists?
-
- environment = ActiveRecord::InternalMetadata[:environment]
- raise NoEnvironmentInSchemaError unless environment
- environment
- end
-
- def self.current_environment
- ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
- end
-
- def self.protected_environment?
- ActiveRecord::Base.protected_environments.include?(last_stored_environment) if last_stored_environment
- end
-
def up?
@direction == :up
end
diff --git a/activerecord/lib/active_record/migration/compatibility.rb b/activerecord/lib/active_record/migration/compatibility.rb
index bd8c054c28..7ae8073478 100644
--- a/activerecord/lib/active_record/migration/compatibility.rb
+++ b/activerecord/lib/active_record/migration/compatibility.rb
@@ -28,6 +28,14 @@ module ActiveRecord
super
end
end
+
+ def create_table(table_name, options = {})
+ if adapter_name == "Mysql2"
+ super(table_name, options: "ENGINE=InnoDB", **options)
+ else
+ super
+ end
+ end
end
class V5_0 < V5_1
diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb
index 1941d3d5ea..fa7537c1a3 100644
--- a/activerecord/lib/active_record/model_schema.rb
+++ b/activerecord/lib/active_record/model_schema.rb
@@ -87,19 +87,6 @@ module ActiveRecord
# Sets the name of the internal metadata table.
##
- # :singleton-method: protected_environments
- # :call-seq: protected_environments
- #
- # The array of names of environments where destructive actions should be prohibited. By default,
- # the value is <tt>["production"]</tt>.
-
- ##
- # :singleton-method: protected_environments=
- # :call-seq: protected_environments=(environments)
- #
- # Sets an array of names of environments where destructive actions should be prohibited.
-
- ##
# :singleton-method: pluralize_table_names
# :call-seq: pluralize_table_names
#
@@ -122,9 +109,9 @@ module ActiveRecord
class_attribute :table_name_suffix, instance_writer: false, default: ""
class_attribute :schema_migrations_table_name, instance_accessor: false, default: "schema_migrations"
class_attribute :internal_metadata_table_name, instance_accessor: false, default: "ar_internal_metadata"
- class_attribute :protected_environments, instance_accessor: false, default: [ "production" ]
class_attribute :pluralize_table_names, instance_writer: false, default: true
+ self.protected_environments = ["production"]
self.inheritance_column = "type"
self.ignored_columns = [].freeze
@@ -238,6 +225,21 @@ module ActiveRecord
(parents.detect { |p| p.respond_to?(:table_name_suffix) } || self).table_name_suffix
end
+ # The array of names of environments where destructive actions should be prohibited. By default,
+ # the value is <tt>["production"]</tt>.
+ def protected_environments
+ if defined?(@protected_environments)
+ @protected_environments
+ else
+ superclass.protected_environments
+ end
+ end
+
+ # Sets an array of names of environments where destructive actions should be prohibited.
+ def protected_environments=(environments)
+ @protected_environments = environments.map(&:to_s)
+ end
+
# Defines the name of the table column which will store the class name on single-table
# inheritance situations.
#
@@ -423,7 +425,7 @@ module ActiveRecord
# end
def reset_column_information
connection.clear_cache!
- undefine_attribute_methods
+ ([self] + descendants).each(&:undefine_attribute_methods)
connection.schema_cache.clear_data_source_cache!(table_name)
reload_schema_from_cache
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index a13b0d0181..a45d011d75 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -359,10 +359,10 @@ module ActiveRecord
# Any change to the attributes on either instance will affect both instances.
# If you want to change the sti column as well, use #becomes! instead.
def becomes(klass)
- became = klass.new
+ became = klass.allocate
+ became.send(:initialize)
became.instance_variable_set("@attributes", @attributes)
became.instance_variable_set("@mutations_from_database", @mutations_from_database) if defined?(@mutations_from_database)
- became.instance_variable_set("@changed_attributes", attributes_changed_by_setter)
became.instance_variable_set("@new_record", new_record?)
became.instance_variable_set("@destroyed", destroyed?)
became.errors.copy!(errors)
@@ -402,11 +402,7 @@ module ActiveRecord
verify_readonly_attribute(name)
public_send("#{name}=", value)
- if has_changes_to_save?
- save(validate: false)
- else
- true
- end
+ save(validate: false)
end
# Updates the attributes of the model from the passed-in hash and saves the
@@ -698,6 +694,7 @@ module ActiveRecord
def create_or_update(*args, &block)
_raise_readonly_record_error if readonly?
+ return false if destroyed?
result = new_record? ? _create_record(&block) : _update_record(*args, &block)
result != false
end
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index 4538ed6a5f..7b4b59ac4b 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -91,6 +91,7 @@ module ActiveRecord
if File.file?(filename)
current_version = ActiveRecord::Migrator.current_version
+
next if current_version.nil?
cache = YAML.load(File.read(filename))
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index fce3e1c5cf..2e55713311 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -6,7 +6,7 @@ db_namespace = namespace :db do
desc "Set the environment value for the database"
task "environment:set" => :load_config do
ActiveRecord::InternalMetadata.create_table
- ActiveRecord::InternalMetadata[:environment] = ActiveRecord::Migrator.current_environment
+ ActiveRecord::InternalMetadata[:environment] = ActiveRecord::Base.connection.migration_context.current_environment
end
task check_protected_environments: :load_config do
@@ -99,9 +99,8 @@ db_namespace = namespace :db do
ActiveRecord::Tasks::DatabaseTasks.check_target_version
- ActiveRecord::Migrator.run(
+ ActiveRecord::Base.connection.migration_context.run(
:up,
- ActiveRecord::Tasks::DatabaseTasks.migrations_paths,
ActiveRecord::Tasks::DatabaseTasks.target_version
)
db_namespace["_dump"].invoke
@@ -113,9 +112,8 @@ db_namespace = namespace :db do
ActiveRecord::Tasks::DatabaseTasks.check_target_version
- ActiveRecord::Migrator.run(
+ ActiveRecord::Base.connection.migration_context.run(
:down,
- ActiveRecord::Tasks::DatabaseTasks.migrations_paths,
ActiveRecord::Tasks::DatabaseTasks.target_version
)
db_namespace["_dump"].invoke
@@ -131,8 +129,7 @@ db_namespace = namespace :db do
puts "\ndatabase: #{ActiveRecord::Base.connection_config[:database]}\n\n"
puts "#{'Status'.center(8)} #{'Migration ID'.ljust(14)} Migration Name"
puts "-" * 50
- paths = ActiveRecord::Tasks::DatabaseTasks.migrations_paths
- ActiveRecord::Migrator.migrations_status(paths).each do |status, version, name|
+ ActiveRecord::Base.connection.migration_context.migrations_status.each do |status, version, name|
puts "#{status.center(8)} #{version.ljust(14)} #{name}"
end
puts
@@ -142,14 +139,14 @@ db_namespace = namespace :db do
desc "Rolls the schema back to the previous version (specify steps w/ STEP=n)."
task rollback: :load_config do
step = ENV["STEP"] ? ENV["STEP"].to_i : 1
- ActiveRecord::Migrator.rollback(ActiveRecord::Tasks::DatabaseTasks.migrations_paths, step)
+ ActiveRecord::Base.connection.migration_context.rollback(step)
db_namespace["_dump"].invoke
end
# desc 'Pushes the schema to the next version (specify steps w/ STEP=n).'
task forward: :load_config do
step = ENV["STEP"] ? ENV["STEP"].to_i : 1
- ActiveRecord::Migrator.forward(ActiveRecord::Tasks::DatabaseTasks.migrations_paths, step)
+ ActiveRecord::Base.connection.migration_context.forward(step)
db_namespace["_dump"].invoke
end
@@ -172,12 +169,12 @@ db_namespace = namespace :db do
desc "Retrieves the current schema version number"
task version: :load_config do
- puts "Current version: #{ActiveRecord::Migrator.current_version}"
+ puts "Current version: #{ActiveRecord::Base.connection.migration_context.current_version}"
end
# desc "Raises an error if there are pending migrations"
task abort_if_pending_migrations: :load_config do
- pending_migrations = ActiveRecord::Migrator.open(ActiveRecord::Tasks::DatabaseTasks.migrations_paths).pending_migrations
+ pending_migrations = ActiveRecord::Base.connection.migration_context.open.pending_migrations
if pending_migrations.any?
puts "You have #{pending_migrations.size} pending #{pending_migrations.size > 1 ? 'migrations:' : 'migration:'}"
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 87bfd75bca..c28e31a3da 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -34,7 +34,8 @@ module ActiveRecord
def self.add_reflection(ar, name, reflection)
ar.clear_reflections_cache
- ar._reflections = ar._reflections.merge(name.to_s => reflection)
+ name = name.to_s
+ ar._reflections = ar._reflections.except(name).merge!(name => reflection)
end
def self.add_aggregate_reflection(ar, name, reflection)
@@ -293,7 +294,7 @@ module ActiveRecord
Relation.create(klass, table, predicate_builder)
end
- def join_primary_key(_)
+ def join_primary_key(*)
foreign_key
end
@@ -458,10 +459,6 @@ module ActiveRecord
options[:primary_key] || primary_key(klass || self.klass)
end
- def association_primary_key_type
- klass.type_for_attribute(association_primary_key.to_s)
- end
-
def active_record_primary_key
@active_record_primary_key ||= options[:primary_key] || primary_key(active_record)
end
@@ -567,7 +564,7 @@ module ActiveRecord
end
VALID_AUTOMATIC_INVERSE_MACROS = [:has_many, :has_one, :belongs_to]
- INVALID_AUTOMATIC_INVERSE_OPTIONS = [:conditions, :through, :foreign_key]
+ INVALID_AUTOMATIC_INVERSE_OPTIONS = [:through, :foreign_key]
def add_as_source(seed)
seed
@@ -722,7 +719,7 @@ module ActiveRecord
end
end
- def join_primary_key(klass)
+ def join_primary_key(klass = nil)
polymorphic? ? association_primary_key(klass) : association_primary_key
end
@@ -859,10 +856,6 @@ module ActiveRecord
actual_source_reflection.options[:primary_key] || primary_key(klass || self.klass)
end
- def association_primary_key_type
- klass.type_for_attribute(association_primary_key.to_s)
- end
-
# Gets an array of possible <tt>:through</tt> source reflection names in both singular and plural form.
#
# class Post < ActiveRecord::Base
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 081ef5771f..10be583ef4 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -10,7 +10,7 @@ module ActiveRecord
SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :reordering,
:reverse_order, :distinct, :create_with, :skip_query_cache]
CLAUSE_METHODS = [:where, :having, :from]
- INVALID_METHODS_FOR_DELETE_ALL = [:limit, :distinct, :offset, :group, :having]
+ INVALID_METHODS_FOR_DELETE_ALL = [:distinct, :group, :having]
VALUE_METHODS = MULTI_VALUE_METHODS + SINGLE_VALUE_METHODS + CLAUSE_METHODS
@@ -52,8 +52,8 @@ module ActiveRecord
#
# user = users.new { |user| user.name = 'Oscar' }
# user.name # => Oscar
- def new(*args, &block)
- scoping { @klass.new(*args, &block) }
+ def new(attributes = nil, &block)
+ scoping { klass.new(scope_for_create(attributes), &block) }
end
alias build new
@@ -77,8 +77,12 @@ module ActiveRecord
#
# users.create(name: nil) # validation on name
# # => #<User id: nil, name: nil, ...>
- def create(*args, &block)
- scoping { @klass.create(*args, &block) }
+ def create(attributes = nil, &block)
+ if attributes.is_a?(Array)
+ attributes.collect { |attr| create(attr, &block) }
+ else
+ scoping { klass.create(scope_for_create(attributes), &block) }
+ end
end
# Similar to #create, but calls
@@ -87,8 +91,12 @@ module ActiveRecord
#
# Expects arguments in the same format as
# {ActiveRecord::Base.create!}[rdoc-ref:Persistence::ClassMethods#create!].
- def create!(*args, &block)
- scoping { @klass.create!(*args, &block) }
+ def create!(attributes = nil, &block)
+ if attributes.is_a?(Array)
+ attributes.collect { |attr| create!(attr, &block) }
+ else
+ scoping { klass.create!(scope_for_create(attributes), &block) }
+ end
end
def first_or_create(attributes = nil, &block) # :nodoc:
@@ -306,10 +314,10 @@ module ActiveRecord
stmt = Arel::UpdateManager.new
- stmt.set Arel.sql(@klass.send(:sanitize_sql_for_assignment, updates))
+ stmt.set Arel.sql(@klass.sanitize_sql_for_assignment(updates))
stmt.table(table)
- if has_join_values?
+ if has_join_values? || offset_value
@klass.connection.join_to_update(stmt, arel, arel_attribute(primary_key))
else
stmt.key = arel_attribute(primary_key)
@@ -357,8 +365,8 @@ module ActiveRecord
#
# If an invalid method is supplied, #delete_all raises an ActiveRecordError:
#
- # Post.limit(100).delete_all
- # # => ActiveRecord::ActiveRecordError: delete_all doesn't support limit
+ # Post.distinct.delete_all
+ # # => ActiveRecord::ActiveRecordError: delete_all doesn't support distinct
def delete_all
invalid_methods = INVALID_METHODS_FOR_DELETE_ALL.select do |method|
value = get_value(method)
@@ -376,7 +384,7 @@ module ActiveRecord
stmt = Arel::DeleteManager.new
stmt.from(table)
- if has_join_values?
+ if has_join_values? || has_limit_or_offset?
@klass.connection.join_to_delete(stmt, arel, arel_attribute(primary_key))
else
stmt.wheres = arel.constraints
@@ -422,7 +430,7 @@ module ActiveRecord
relation = self
if eager_loading?
- find_with_associations { |rel, _| relation = rel }
+ apply_join_dependency { |rel, _| relation = rel }
end
conn = klass.connection
@@ -440,8 +448,10 @@ module ActiveRecord
where_clause.to_h(relation_table_name)
end
- def scope_for_create
- where_values_hash.merge!(create_with_value.stringify_keys)
+ def scope_for_create(attributes = nil)
+ scope = where_values_hash.merge!(create_with_value.stringify_keys)
+ scope.merge!(attributes) if attributes
+ scope
end
# Returns true if relation needs eager loading.
@@ -523,7 +533,7 @@ module ActiveRecord
skip_query_cache_if_necessary do
@records =
if eager_loading?
- find_with_associations do |relation, join_dependency|
+ apply_join_dependency do |relation, join_dependency|
if ActiveRecord::NullRelation === relation
[]
else
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index d49472fc70..cb0b06cfdc 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -217,7 +217,7 @@ module ActiveRecord
if operation == "count"
column_name ||= select_for_count
if column_name == :all
- if distinct && (group_values.any? || !(has_limit_or_offset? && order_values.any?))
+ if distinct && (group_values.any? || select_values.empty? && order_values.empty?)
column_name = primary_key
end
elsif column_name =~ /\s*DISTINCT[\s(]+/i
@@ -249,7 +249,7 @@ module ActiveRecord
def execute_simple_calculation(operation, column_name, distinct) #:nodoc:
column_alias = column_name
- if operation == "count" && has_limit_or_offset?
+ if operation == "count" && (column_name == :all && distinct || has_limit_or_offset?)
# Shortcut when limit is zero.
return 0 if limit_value == 0
@@ -391,14 +391,12 @@ module ActiveRecord
end
def build_count_subquery(relation, column_name, distinct)
- relation.select_values = [
- if column_name == :all
- distinct ? table[Arel.star] : Arel.sql(FinderMethods::ONE_AS_ONE)
- else
- column_alias = Arel.sql("count_column")
- aggregate_column(column_name).as(column_alias)
- end
- ]
+ if column_name == :all
+ relation.select_values = [ Arel.sql(FinderMethods::ONE_AS_ONE) ] unless distinct
+ else
+ column_alias = Arel.sql("count_column")
+ relation.select_values = [ aggregate_column(column_name).as(column_alias) ]
+ end
subquery = relation.arel.as(Arel.sql("subquery_for_count"))
select_value = operation_over_aggregate_column(column_alias || Arel.star, "count", false)
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index ff06ecbee1..5f959af5dc 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -148,7 +148,7 @@ module ActiveRecord
#
# [#<Person id:4>, #<Person id:3>, #<Person id:2>]
def last(limit = nil)
- return find_last(limit) if loaded? || limit_value
+ return find_last(limit) if loaded? || has_limit_or_offset?
result = ordered_relation.limit(limit)
result = result.reverse_order!
@@ -313,7 +313,7 @@ module ActiveRecord
return false if !conditions || limit_value == 0
if eager_loading?
- relation = apply_join_dependency(construct_join_dependency(eager_loading: false))
+ relation = apply_join_dependency(eager_loading: false)
return relation.exists?(conditions)
end
@@ -358,24 +358,6 @@ module ActiveRecord
offset_value || 0
end
- def find_with_associations
- # NOTE: the JoinDependency constructed here needs to know about
- # any joins already present in `self`, so pass them in
- #
- # failing to do so means that in cases like activerecord/test/cases/associations/inner_join_association_test.rb:136
- # incorrect SQL is generated. In that case, the join dependency for
- # SpecialCategorizations is constructed without knowledge of the
- # preexisting join in joins_values to categorizations (by way of
- # the `has_many :through` for categories).
- #
- join_dependency = construct_join_dependency
-
- relation = apply_join_dependency(join_dependency)
- relation._select!(join_dependency.aliases.columns)
-
- yield relation, join_dependency
- end
-
def construct_relation_for_exists(conditions)
relation = except(:select, :distinct, :order)._select!(ONE_AS_ONE).limit!(1)
@@ -391,22 +373,29 @@ module ActiveRecord
def construct_join_dependency(eager_loading: true)
including = eager_load_values + includes_values
+ joins = joins_values.select { |join| join.is_a?(Arel::Nodes::Join) }
ActiveRecord::Associations::JoinDependency.new(
- klass, table, including, alias_tracker(joins_values), eager_loading: eager_loading
+ klass, table, including, alias_tracker(joins), eager_loading: eager_loading
)
end
- def apply_join_dependency(join_dependency = construct_join_dependency)
+ def apply_join_dependency(eager_loading: true)
+ join_dependency = construct_join_dependency(eager_loading: eager_loading)
relation = except(:includes, :eager_load, :preload).joins!(join_dependency)
- if using_limitable_reflections?(join_dependency.reflections)
- relation
- else
- if relation.limit_value
+ if eager_loading && !using_limitable_reflections?(join_dependency.reflections)
+ if has_limit_or_offset?
limited_ids = limited_ids_for(relation)
limited_ids.empty? ? relation.none! : relation.where!(primary_key => limited_ids)
end
- relation.except(:limit, :offset)
+ relation.limit_value = relation.offset_value = nil
+ end
+
+ if block_given?
+ relation._select!(join_dependency.aliases.columns)
+ yield relation, join_dependency
+ else
+ relation
end
end
@@ -532,7 +521,11 @@ module ActiveRecord
else
relation = ordered_relation
- if limit_value.nil? || index < limit_value
+ if limit_value
+ limit = [limit_value - index, limit].min
+ end
+
+ if limit > 0
relation = relation.offset(offset_index + index) unless index.zero?
relation.limit(limit).to_a
else
@@ -547,12 +540,11 @@ module ActiveRecord
else
relation = ordered_relation
- relation.to_a[-index]
- # TODO: can be made more performant on large result sets by
- # for instance, last(index)[-index] (which would require
- # refactoring the last(n) finder method to make test suite pass),
- # or by using a combination of reverse_order, limit, and offset,
- # e.g., reverse_order.offset(index-1).first
+ if equal?(relation) || has_limit_or_offset?
+ relation.records[-index]
+ else
+ relation.last(index)[-index]
+ end
end
end
diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb
index b736b21525..ebdd4144bb 100644
--- a/activerecord/lib/active_record/relation/merger.rb
+++ b/activerecord/lib/active_record/relation/merger.rb
@@ -52,7 +52,7 @@ module ActiveRecord
NORMAL_VALUES = Relation::VALUE_METHODS -
Relation::CLAUSE_METHODS -
- [:includes, :preload, :joins, :order, :reverse_order, :lock, :create_with, :reordering] # :nodoc:
+ [:includes, :preload, :joins, :left_outer_joins, :order, :reverse_order, :lock, :create_with, :reordering] # :nodoc:
def normal_values
NORMAL_VALUES
@@ -79,6 +79,7 @@ module ActiveRecord
merge_clauses
merge_preloads
merge_joins
+ merge_outer_joins
relation
end
@@ -129,6 +130,29 @@ module ActiveRecord
end
end
+ def merge_outer_joins
+ return if other.left_outer_joins_values.blank?
+
+ if other.klass == relation.klass
+ relation.left_outer_joins!(*other.left_outer_joins_values)
+ else
+ alias_tracker = nil
+ joins_dependency = other.left_outer_joins_values.map do |join|
+ case join
+ when Hash, Symbol, Array
+ alias_tracker ||= other.alias_tracker
+ ActiveRecord::Associations::JoinDependency.new(
+ other.klass, other.table, join, alias_tracker
+ )
+ else
+ join
+ end
+ end
+
+ relation.left_outer_joins!(*joins_dependency)
+ end
+ end
+
def merge_multi_values
if other.reordering_value
# override any order specified in the original relation
diff --git a/activerecord/lib/active_record/relation/predicate_builder/association_query_value.rb b/activerecord/lib/active_record/relation/predicate_builder/association_query_value.rb
index 0255a65bfe..28c7483c95 100644
--- a/activerecord/lib/active_record/relation/predicate_builder/association_query_value.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder/association_query_value.rb
@@ -30,7 +30,7 @@ module ActiveRecord
end
def primary_key
- associated_table.association_join_keys.key
+ associated_table.association_join_primary_key
end
def convert_to_id(value)
diff --git a/activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb b/activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb
index b87b5c36dd..e8e2f2c626 100644
--- a/activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb
@@ -29,7 +29,7 @@ module ActiveRecord
end
def primary_key(value)
- associated_table.association_primary_key(base_class(value))
+ associated_table.association_join_primary_key(base_class(value))
end
def base_class(value)
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 749223422f..86882c7ce7 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -327,8 +327,8 @@ module ActiveRecord
end
VALID_UNSCOPING_VALUES = Set.new([:where, :select, :group, :order, :lock,
- :limit, :offset, :joins, :includes, :from,
- :readonly, :having])
+ :limit, :offset, :joins, :left_outer_joins,
+ :includes, :from, :readonly, :having])
# Removes an unwanted relation that is already defined on a chain of relations.
# This is useful when passing around chains of relations and would like to
@@ -375,10 +375,11 @@ module ActiveRecord
args.each do |scope|
case scope
when Symbol
+ scope = :left_outer_joins if scope == :left_joins
if !VALID_UNSCOPING_VALUES.include?(scope)
raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}."
end
- set_value(scope, nil)
+ set_value(scope, DEFAULT_VALUES[scope])
when Hash
scope.each do |key, target_value|
if key != :where
@@ -444,15 +445,13 @@ module ActiveRecord
#
def left_outer_joins(*args)
check_if_method_has_arguments!(__callee__, args)
-
- args.compact!
- args.flatten!
-
spawn.left_outer_joins!(*args)
end
alias :left_joins :left_outer_joins
def left_outer_joins!(*args) # :nodoc:
+ args.compact!
+ args.flatten!
self.left_outer_joins_values += args
self
end
@@ -907,7 +906,7 @@ module ActiveRecord
protected
# Returns a relation value with a given name
def get_value(name) # :nodoc:
- @values[name] || default_value_for(name)
+ @values.fetch(name, DEFAULT_VALUES[name])
end
# Sets the relation value with the given name
@@ -980,6 +979,8 @@ module ActiveRecord
case join
when Hash, Symbol, Array
:association_join
+ when ActiveRecord::Associations::JoinDependency
+ :stashed_join
else
raise ArgumentError, "only Hash, Symbol and Array are allowed"
end
@@ -1015,7 +1016,7 @@ module ActiveRecord
join_nodes = buckets[:join_node].uniq
string_joins = buckets[:string_join].map(&:strip).uniq
- join_list = join_nodes + convert_join_strings_to_ast(manager, string_joins)
+ join_list = join_nodes + convert_join_strings_to_ast(string_joins)
alias_tracker = alias_tracker(join_list, aliases)
join_dependency = ActiveRecord::Associations::JoinDependency.new(
@@ -1030,7 +1031,7 @@ module ActiveRecord
alias_tracker.aliases
end
- def convert_join_strings_to_ast(table, joins)
+ def convert_join_strings_to_ast(joins)
joins
.flatten
.reject(&:blank?)
@@ -1040,8 +1041,8 @@ module ActiveRecord
def build_select(arel)
if select_values.any?
arel.project(*arel_columns(select_values.uniq))
- elsif @klass.ignored_columns.any?
- arel.project(*arel_columns(@klass.column_names))
+ elsif klass.ignored_columns.any?
+ arel.project(*klass.column_names.map { |field| arel_attribute(field) })
else
arel.project(table[Arel.star])
end
@@ -1121,7 +1122,7 @@ module ActiveRecord
def preprocess_order_args(order_args)
order_args.map! do |arg|
- klass.send(:sanitize_sql_for_order, arg)
+ klass.sanitize_sql_for_order(arg)
end
order_args.flatten!
@@ -1192,7 +1193,6 @@ module ActiveRecord
DEFAULT_VALUES = {
create_with: FROZEN_EMPTY_HASH,
- readonly: false,
where: Relation::WhereClause.empty,
having: Relation::WhereClause.empty,
from: Relation::FromClause.empty
@@ -1201,15 +1201,5 @@ module ActiveRecord
Relation::MULTI_VALUE_METHODS.each do |value|
DEFAULT_VALUES[value] ||= FROZEN_EMPTY_ARRAY
end
-
- Relation::SINGLE_VALUE_METHODS.each do |value|
- DEFAULT_VALUES[value] = nil if DEFAULT_VALUES[value].nil?
- end
-
- def default_value_for(name)
- DEFAULT_VALUES.fetch(name) do
- raise ArgumentError, "unknown relation value #{name.inspect}"
- 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 1374785354..4ae94f4bfe 100644
--- a/activerecord/lib/active_record/relation/where_clause_factory.rb
+++ b/activerecord/lib/active_record/relation/where_clause_factory.rb
@@ -11,7 +11,7 @@ module ActiveRecord
def build(opts, other)
case opts
when String, Array
- parts = [klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))]
+ parts = [klass.sanitize_sql(other.empty? ? opts : ([opts] + other))]
when Hash
attributes = predicate_builder.resolve_column_aliases(opts)
attributes = klass.send(:expand_hash_conditions_for_aggregates, attributes)
diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb
index 21f8bc7cb2..58da106092 100644
--- a/activerecord/lib/active_record/sanitization.rb
+++ b/activerecord/lib/active_record/sanitization.rb
@@ -5,80 +5,135 @@ module ActiveRecord
extend ActiveSupport::Concern
module ClassMethods
- private
-
- # Accepts an array or string of SQL conditions and sanitizes
- # them into a valid SQL fragment for a WHERE clause.
- #
- # sanitize_sql_for_conditions(["name=? and group_id=?", "foo'bar", 4])
- # # => "name='foo''bar' and group_id=4"
- #
- # sanitize_sql_for_conditions(["name=:name and group_id=:group_id", name: "foo'bar", group_id: 4])
- # # => "name='foo''bar' and group_id='4'"
- #
- # sanitize_sql_for_conditions(["name='%s' and group_id='%s'", "foo'bar", 4])
- # # => "name='foo''bar' and group_id='4'"
- #
- # sanitize_sql_for_conditions("name='foo''bar' and group_id='4'")
- # # => "name='foo''bar' and group_id='4'"
- def sanitize_sql_for_conditions(condition) # :doc:
- return nil if condition.blank?
-
- case condition
- when Array; sanitize_sql_array(condition)
- else condition
- end
+ # Accepts an array or string of SQL conditions and sanitizes
+ # them into a valid SQL fragment for a WHERE clause.
+ #
+ # sanitize_sql_for_conditions(["name=? and group_id=?", "foo'bar", 4])
+ # # => "name='foo''bar' and group_id=4"
+ #
+ # sanitize_sql_for_conditions(["name=:name and group_id=:group_id", name: "foo'bar", group_id: 4])
+ # # => "name='foo''bar' and group_id='4'"
+ #
+ # sanitize_sql_for_conditions(["name='%s' and group_id='%s'", "foo'bar", 4])
+ # # => "name='foo''bar' and group_id='4'"
+ #
+ # sanitize_sql_for_conditions("name='foo''bar' and group_id='4'")
+ # # => "name='foo''bar' and group_id='4'"
+ def sanitize_sql_for_conditions(condition)
+ return nil if condition.blank?
+
+ case condition
+ when Array; sanitize_sql_array(condition)
+ else condition
end
- alias :sanitize_sql :sanitize_sql_for_conditions
-
- # Accepts an array, hash, or string of SQL conditions and sanitizes
- # them into a valid SQL fragment for a SET clause.
- #
- # sanitize_sql_for_assignment(["name=? and group_id=?", nil, 4])
- # # => "name=NULL and group_id=4"
- #
- # sanitize_sql_for_assignment(["name=:name and group_id=:group_id", name: nil, group_id: 4])
- # # => "name=NULL and group_id=4"
- #
- # Post.send(:sanitize_sql_for_assignment, { name: nil, group_id: 4 })
- # # => "`posts`.`name` = NULL, `posts`.`group_id` = 4"
- #
- # sanitize_sql_for_assignment("name=NULL and group_id='4'")
- # # => "name=NULL and group_id='4'"
- def sanitize_sql_for_assignment(assignments, default_table_name = table_name) # :doc:
- case assignments
- when Array; sanitize_sql_array(assignments)
- when Hash; sanitize_sql_hash_for_assignment(assignments, default_table_name)
- else assignments
- end
+ end
+ alias :sanitize_sql :sanitize_sql_for_conditions
+
+ # Accepts an array, hash, or string of SQL conditions and sanitizes
+ # them into a valid SQL fragment for a SET clause.
+ #
+ # sanitize_sql_for_assignment(["name=? and group_id=?", nil, 4])
+ # # => "name=NULL and group_id=4"
+ #
+ # sanitize_sql_for_assignment(["name=:name and group_id=:group_id", name: nil, group_id: 4])
+ # # => "name=NULL and group_id=4"
+ #
+ # Post.sanitize_sql_for_assignment({ name: nil, group_id: 4 })
+ # # => "`posts`.`name` = NULL, `posts`.`group_id` = 4"
+ #
+ # sanitize_sql_for_assignment("name=NULL and group_id='4'")
+ # # => "name=NULL and group_id='4'"
+ def sanitize_sql_for_assignment(assignments, default_table_name = table_name)
+ case assignments
+ when Array; sanitize_sql_array(assignments)
+ when Hash; sanitize_sql_hash_for_assignment(assignments, default_table_name)
+ else assignments
end
-
- # Accepts an array, or string of SQL conditions and sanitizes
- # them into a valid SQL fragment for an ORDER clause.
- #
- # sanitize_sql_for_order(["field(id, ?)", [1,3,2]])
- # # => "field(id, 1,3,2)"
- #
- # sanitize_sql_for_order("id ASC")
- # # => "id ASC"
- def sanitize_sql_for_order(condition) # :doc:
- if condition.is_a?(Array) && condition.first.to_s.include?("?")
- enforce_raw_sql_whitelist([condition.first],
- whitelist: AttributeMethods::ClassMethods::COLUMN_NAME_ORDER_WHITELIST
- )
-
- # Ensure we aren't dealing with a subclass of String that might
- # override methods we use (eg. Arel::Nodes::SqlLiteral).
- if condition.first.kind_of?(String) && !condition.first.instance_of?(String)
- condition = [String.new(condition.first), *condition[1..-1]]
- end
-
- Arel.sql(sanitize_sql_array(condition))
- else
- condition
+ end
+
+ # Accepts an array, or string of SQL conditions and sanitizes
+ # them into a valid SQL fragment for an ORDER clause.
+ #
+ # sanitize_sql_for_order(["field(id, ?)", [1,3,2]])
+ # # => "field(id, 1,3,2)"
+ #
+ # sanitize_sql_for_order("id ASC")
+ # # => "id ASC"
+ def sanitize_sql_for_order(condition)
+ if condition.is_a?(Array) && condition.first.to_s.include?("?")
+ enforce_raw_sql_whitelist([condition.first],
+ whitelist: AttributeMethods::ClassMethods::COLUMN_NAME_ORDER_WHITELIST
+ )
+
+ # Ensure we aren't dealing with a subclass of String that might
+ # override methods we use (eg. Arel::Nodes::SqlLiteral).
+ if condition.first.kind_of?(String) && !condition.first.instance_of?(String)
+ condition = [String.new(condition.first), *condition[1..-1]]
end
+
+ Arel.sql(sanitize_sql_array(condition))
+ else
+ condition
end
+ end
+
+ # Sanitizes a hash of attribute/value pairs into SQL conditions for a SET clause.
+ #
+ # sanitize_sql_hash_for_assignment({ status: nil, group_id: 1 }, "posts")
+ # # => "`posts`.`status` = NULL, `posts`.`group_id` = 1"
+ def sanitize_sql_hash_for_assignment(attrs, table)
+ c = connection
+ attrs.map do |attr, value|
+ type = type_for_attribute(attr.to_s)
+ value = type.serialize(type.cast(value))
+ "#{c.quote_table_name_for_assignment(table, attr)} = #{c.quote(value)}"
+ end.join(", ")
+ end
+
+ # Sanitizes a +string+ so that it is safe to use within an SQL
+ # LIKE statement. This method uses +escape_character+ to escape all occurrences of "\", "_" and "%".
+ #
+ # sanitize_sql_like("100%")
+ # # => "100\\%"
+ #
+ # sanitize_sql_like("snake_cased_string")
+ # # => "snake\\_cased\\_string"
+ #
+ # sanitize_sql_like("100%", "!")
+ # # => "100!%"
+ #
+ # sanitize_sql_like("snake_cased_string", "!")
+ # # => "snake!_cased!_string"
+ def sanitize_sql_like(string, escape_character = "\\")
+ pattern = Regexp.union(escape_character, "%", "_")
+ string.gsub(pattern) { |x| [escape_character, x].join }
+ end
+
+ # Accepts an array of conditions. The array has each value
+ # sanitized and interpolated into the SQL statement.
+ #
+ # sanitize_sql_array(["name=? and group_id=?", "foo'bar", 4])
+ # # => "name='foo''bar' and group_id=4"
+ #
+ # sanitize_sql_array(["name=:name and group_id=:group_id", name: "foo'bar", group_id: 4])
+ # # => "name='foo''bar' and group_id=4"
+ #
+ # sanitize_sql_array(["name='%s' and group_id='%s'", "foo'bar", 4])
+ # # => "name='foo''bar' and group_id='4'"
+ def sanitize_sql_array(ary)
+ statement, *values = ary
+ if values.first.is_a?(Hash) && /:\w+/.match?(statement)
+ replace_named_bind_variables(statement, values.first)
+ elsif statement.include?("?")
+ replace_bind_variables(statement, values)
+ elsif statement.blank?
+ statement
+ else
+ statement % values.collect { |value| connection.quote_string(value.to_s) }
+ end
+ end
+ private
# Accepts a hash of SQL conditions and replaces those attributes
# that correspond to a {#composed_of}[rdoc-ref:Aggregations::ClassMethods#composed_of]
# relationship with their expanded aggregate attribute values.
@@ -113,62 +168,6 @@ module ActiveRecord
expanded_attrs
end
- # Sanitizes a hash of attribute/value pairs into SQL conditions for a SET clause.
- #
- # sanitize_sql_hash_for_assignment({ status: nil, group_id: 1 }, "posts")
- # # => "`posts`.`status` = NULL, `posts`.`group_id` = 1"
- def sanitize_sql_hash_for_assignment(attrs, table) # :doc:
- c = connection
- attrs.map do |attr, value|
- type = type_for_attribute(attr.to_s)
- value = type.serialize(type.cast(value))
- "#{c.quote_table_name_for_assignment(table, attr)} = #{c.quote(value)}"
- end.join(", ")
- end
-
- # Sanitizes a +string+ so that it is safe to use within an SQL
- # LIKE statement. This method uses +escape_character+ to escape all occurrences of "\", "_" and "%".
- #
- # sanitize_sql_like("100%")
- # # => "100\\%"
- #
- # sanitize_sql_like("snake_cased_string")
- # # => "snake\\_cased\\_string"
- #
- # sanitize_sql_like("100%", "!")
- # # => "100!%"
- #
- # sanitize_sql_like("snake_cased_string", "!")
- # # => "snake!_cased!_string"
- def sanitize_sql_like(string, escape_character = "\\") # :doc:
- pattern = Regexp.union(escape_character, "%", "_")
- string.gsub(pattern) { |x| [escape_character, x].join }
- end
-
- # Accepts an array of conditions. The array has each value
- # sanitized and interpolated into the SQL statement.
- #
- # sanitize_sql_array(["name=? and group_id=?", "foo'bar", 4])
- # # => "name='foo''bar' and group_id=4"
- #
- # sanitize_sql_array(["name=:name and group_id=:group_id", name: "foo'bar", group_id: 4])
- # # => "name='foo''bar' and group_id=4"
- #
- # sanitize_sql_array(["name='%s' and group_id='%s'", "foo'bar", 4])
- # # => "name='foo''bar' and group_id='4'"
- def sanitize_sql_array(ary) # :doc:
- statement, *values = ary
- if values.first.is_a?(Hash) && /:\w+/.match?(statement)
- replace_named_bind_variables(statement, values.first)
- elsif statement.include?("?")
- replace_bind_variables(statement, values)
- elsif statement.blank?
- statement
- else
- statement % values.collect { |value| connection.quote_string(value.to_s) }
- end
- end
-
def replace_bind_variables(statement, values)
raise_if_bind_arity_mismatch(statement, statement.count("?"), values.size)
bound = values.dup
diff --git a/activerecord/lib/active_record/schema.rb b/activerecord/lib/active_record/schema.rb
index 1e121f2a09..216359867c 100644
--- a/activerecord/lib/active_record/schema.rb
+++ b/activerecord/lib/active_record/schema.rb
@@ -55,7 +55,7 @@ module ActiveRecord
end
ActiveRecord::InternalMetadata.create_table
- ActiveRecord::InternalMetadata[:environment] = ActiveRecord::Migrator.current_environment
+ ActiveRecord::InternalMetadata[:environment] = connection.migration_context.current_environment
end
private
diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb
index 16ccba6b6c..b8d848b999 100644
--- a/activerecord/lib/active_record/schema_dumper.rb
+++ b/activerecord/lib/active_record/schema_dumper.rb
@@ -44,7 +44,7 @@ module ActiveRecord
def initialize(connection, options = {})
@connection = connection
- @version = Migrator::current_version rescue nil
+ @version = connection.migration_context.current_version rescue nil
@options = options
end
diff --git a/activerecord/lib/active_record/table_metadata.rb b/activerecord/lib/active_record/table_metadata.rb
index a5187efc84..0459cbdc59 100644
--- a/activerecord/lib/active_record/table_metadata.rb
+++ b/activerecord/lib/active_record/table_metadata.rb
@@ -2,8 +2,7 @@
module ActiveRecord
class TableMetadata # :nodoc:
- delegate :foreign_type, :foreign_key, :join_keys, :join_foreign_key, to: :association, prefix: true
- delegate :association_primary_key, to: :association
+ delegate :foreign_type, :foreign_key, :join_primary_key, :join_foreign_key, to: :association, prefix: true
def initialize(klass, arel_table, association = nil)
@klass = klass
diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb
index 4657e51e6d..ed589f9b43 100644
--- a/activerecord/lib/active_record/tasks/database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/database_tasks.rb
@@ -54,10 +54,10 @@ module ActiveRecord
def check_protected_environments!
unless ENV["DISABLE_DATABASE_ENVIRONMENT_CHECK"]
- current = ActiveRecord::Migrator.current_environment
- stored = ActiveRecord::Migrator.last_stored_environment
+ current = ActiveRecord::Base.connection.migration_context.current_environment
+ stored = ActiveRecord::Base.connection.migration_context.last_stored_environment
- if ActiveRecord::Migrator.protected_environment?
+ if ActiveRecord::Base.connection.migration_context.protected_environment?
raise ActiveRecord::ProtectedEnvironmentError.new(stored)
end
@@ -169,7 +169,7 @@ module ActiveRecord
verbose = ENV["VERBOSE"] ? ENV["VERBOSE"] != "false" : true
scope = ENV["SCOPE"]
verbose_was, Migration.verbose = Migration.verbose, verbose
- Migrator.migrate(migrations_paths, target_version) do |migration|
+ Base.connection.migration_context.migrate(target_version) do |migration|
scope.blank? || scope == migration.scope
end
ActiveRecord::Base.clear_cache!