aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib')
-rw-r--r--activerecord/lib/active_record/associations.rb24
-rw-r--r--activerecord/lib/active_record/associations/association_scope.rb16
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb40
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb5
-rw-r--r--activerecord/lib/active_record/associations/singular_association.rb10
-rw-r--r--activerecord/lib/active_record/attribute_methods/dirty.rb18
-rw-r--r--activerecord/lib/active_record/attribute_methods/read.rb3
-rw-r--r--activerecord/lib/active_record/attribute_methods/serialization.rb24
-rw-r--r--activerecord/lib/active_record/attribute_methods/write.rb3
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb15
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb8
-rw-r--r--activerecord/lib/active_record/core.rb20
-rw-r--r--activerecord/lib/active_record/errors.rb4
-rw-r--r--activerecord/lib/active_record/locking/pessimistic.rb2
-rw-r--r--activerecord/lib/active_record/migration/compatibility.rb2
-rw-r--r--activerecord/lib/active_record/reflection.rb37
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb6
-rw-r--r--activerecord/lib/active_record/relation/merger.rb22
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/association_query_value.rb4
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb19
-rw-r--r--activerecord/lib/active_record/relation/where_clause.rb21
-rw-r--r--activerecord/lib/active_record/statement_cache.rb26
-rw-r--r--activerecord/lib/active_record/table_metadata.rb2
-rw-r--r--activerecord/lib/active_record/tasks/postgresql_database_tasks.rb4
-rw-r--r--activerecord/lib/active_record/transactions.rb6
-rw-r--r--activerecord/lib/rails/generators/active_record/migration/migration_generator.rb2
30 files changed, 207 insertions, 148 deletions
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 840f71bef2..a61c0336db 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -349,6 +349,7 @@ module ActiveRecord
# build_other(attributes={}) | X | | X
# create_other(attributes={}) | X | | X
# create_other!(attributes={}) | X | | X
+ # reload_other | X | X | X
#
# === Collection associations (one-to-many / many-to-many)
# | | | has_many
@@ -378,6 +379,7 @@ module ActiveRecord
# others.exists? | X | X | X
# others.distinct | X | X | X
# others.reset | X | X | X
+ # others.reload | X | X | X
#
# === Overriding generated methods
#
@@ -1190,8 +1192,8 @@ module ActiveRecord
# <tt>has_many :clients</tt> would add among others <tt>clients.empty?</tt>.
#
# [collection]
- # Returns an array of all the associated objects.
- # An empty array is returned if none are found.
+ # Returns a Relation of all the associated objects.
+ # An empty Relation is returned if none are found.
# [collection<<(object, ...)]
# Adds one or more objects to the collection by setting their foreign keys to the collection's primary key.
# Note that this operation instantly fires update SQL without waiting for the save or update call on the
@@ -1248,6 +1250,9 @@ module ActiveRecord
# [collection.create!(attributes = {})]
# Does the same as <tt>collection.create</tt>, but raises ActiveRecord::RecordInvalid
# if the record is invalid.
+ # [collection.reload]
+ # Returns a Relation of all of the associated objects, forcing a database read.
+ # An empty Relation is returned if none are found.
#
# === Example
#
@@ -1267,6 +1272,7 @@ module ActiveRecord
# * <tt>Firm#clients.build</tt> (similar to <tt>Client.new("firm_id" => id)</tt>)
# * <tt>Firm#clients.create</tt> (similar to <tt>c = Client.new("firm_id" => id); c.save; c</tt>)
# * <tt>Firm#clients.create!</tt> (similar to <tt>c = Client.new("firm_id" => id); c.save!</tt>)
+ # * <tt>Firm#clients.reload</tt>
# The declaration can also include an +options+ hash to specialize the behavior of the association.
#
# === Scopes
@@ -1426,6 +1432,8 @@ module ActiveRecord
# [create_association!(attributes = {})]
# Does the same as <tt>create_association</tt>, but raises ActiveRecord::RecordInvalid
# if the record is invalid.
+ # [reload_association]
+ # Returns the associated object, forcing a database read.
#
# === Example
#
@@ -1435,6 +1443,7 @@ module ActiveRecord
# * <tt>Account#build_beneficiary</tt> (similar to <tt>Beneficiary.new("account_id" => id)</tt>)
# * <tt>Account#create_beneficiary</tt> (similar to <tt>b = Beneficiary.new("account_id" => id); b.save; b</tt>)
# * <tt>Account#create_beneficiary!</tt> (similar to <tt>b = Beneficiary.new("account_id" => id); b.save!; b</tt>)
+ # * <tt>Account#reload_beneficiary</tt>
#
# === Scopes
#
@@ -1555,6 +1564,8 @@ module ActiveRecord
# [create_association!(attributes = {})]
# Does the same as <tt>create_association</tt>, but raises ActiveRecord::RecordInvalid
# if the record is invalid.
+ # [reload_association]
+ # Returns the associated object, forcing a database read.
#
# === Example
#
@@ -1564,6 +1575,7 @@ module ActiveRecord
# * <tt>Post#build_author</tt> (similar to <tt>post.author = Author.new</tt>)
# * <tt>Post#create_author</tt> (similar to <tt>post.author = Author.new; post.author.save; post.author</tt>)
# * <tt>Post#create_author!</tt> (similar to <tt>post.author = Author.new; post.author.save!; post.author</tt>)
+ # * <tt>Post#reload_author</tt>
# The declaration can also include an +options+ hash to specialize the behavior of the association.
#
# === Scopes
@@ -1704,8 +1716,8 @@ module ActiveRecord
# <tt>has_and_belongs_to_many :categories</tt> would add among others <tt>categories.empty?</tt>.
#
# [collection]
- # Returns an array of all the associated objects.
- # An empty array is returned if none are found.
+ # Returns a Relation of all the associated objects.
+ # An empty Relation is returned if none are found.
# [collection<<(object, ...)]
# Adds one or more objects to the collection by creating associations in the join table
# (<tt>collection.push</tt> and <tt>collection.concat</tt> are aliases to this method).
@@ -1743,6 +1755,9 @@ module ActiveRecord
# Returns a new object of the collection type that has been instantiated
# with +attributes+, linked to this object through the join table, and that has already been
# saved (if it passed the validation).
+ # [collection.reload]
+ # Returns a Relation of all of the associated objects, forcing a database read.
+ # An empty Relation is returned if none are found.
#
# === Example
#
@@ -1761,6 +1776,7 @@ module ActiveRecord
# * <tt>Developer#projects.exists?(...)</tt>
# * <tt>Developer#projects.build</tt> (similar to <tt>Project.new("developer_id" => id)</tt>)
# * <tt>Developer#projects.create</tt> (similar to <tt>c = Project.new("developer_id" => id); c.save; c</tt>)
+ # * <tt>Developer#projects.reload</tt>
# The declaration may include an +options+ hash to specialize the behavior of the association.
#
# === Scopes
diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb
index 9b0b50977d..3d79e540b8 100644
--- a/activerecord/lib/active_record/associations/association_scope.rb
+++ b/activerecord/lib/active_record/associations/association_scope.rb
@@ -68,11 +68,11 @@ module ActiveRecord
foreign_key = join_keys.foreign_key
value = transform_value(owner[foreign_key])
- scope = scope.where(table.name => { key => value })
+ scope = apply_scope(scope, table, key, value)
if reflection.type
polymorphic_type = transform_value(owner.class.base_class.name)
- scope = scope.where(table.name => { reflection.type => polymorphic_type })
+ scope = apply_scope(scope, table, reflection.type, polymorphic_type)
end
scope
@@ -91,10 +91,10 @@ module ActiveRecord
if reflection.type
value = transform_value(next_reflection.klass.base_class.name)
- scope = scope.where(table.name => { reflection.type => value })
+ scope = apply_scope(scope, table, reflection.type, value)
end
- scope = scope.joins(join(foreign_table, constraint))
+ scope.joins!(join(foreign_table, constraint))
end
class ReflectionProxy < SimpleDelegator # :nodoc:
@@ -165,6 +165,14 @@ module ActiveRecord
scope
end
+ def apply_scope(scope, table, key, value)
+ if scope.table == table
+ scope.where!(key => value)
+ else
+ scope.where!(table.name => { key => value })
+ end
+ end
+
def eval_scope(reflection, table, scope, owner)
reflection.build_scope(table).instance_exec(owner, &scope)
end
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index ed2e6d1ae4..38e7ba39d3 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -59,7 +59,9 @@ module ActiveRecord
r.send(reflection.association_primary_key)
end.values_at(*ids).compact
if records.size != ids.size
- klass.all.raise_record_not_found_exception!(ids, records.size, ids.size, reflection.association_primary_key)
+ found_ids = records.map { |record| record.send(reflection.association_primary_key) }
+ not_found_ids = ids - found_ids
+ klass.all.raise_record_not_found_exception!(ids, records.size, ids.size, reflection.association_primary_key, not_found_ids)
else
replace(records)
end
@@ -72,23 +74,19 @@ module ActiveRecord
end
def find(*args)
- if block_given?
- load_target.find(*args) { |*block_args| yield(*block_args) }
- else
- if options[:inverse_of] && loaded?
- args_flatten = args.flatten
- raise RecordNotFound, "Couldn't find #{scope.klass.name} without an ID" if args_flatten.blank?
- result = find_by_scan(*args)
-
- result_size = Array(result).size
- if !result || result_size != args_flatten.size
- scope.raise_record_not_found_exception!(args_flatten, result_size, args_flatten.size)
- else
- result
- end
+ if options[:inverse_of] && loaded?
+ args_flatten = args.flatten
+ raise RecordNotFound, "Couldn't find #{scope.klass.name} without an ID" if args_flatten.blank?
+ result = find_by_scan(*args)
+
+ result_size = Array(result).size
+ if !result || result_size != args_flatten.size
+ scope.raise_record_not_found_exception!(args_flatten, result_size, args_flatten.size)
else
- scope.find(*args)
+ result
end
+ else
+ scope.find(*args)
end
end
@@ -304,15 +302,13 @@ module ActiveRecord
return scope.to_a if skip_statement_cache?(scope)
conn = klass.connection
- sc = reflection.association_scope_cache(conn, owner) do
- StatementCache.create(conn) { |params|
- as = AssociationScope.create { params.bind }
- target_scope.merge!(as.scope(self))
- }
+ sc = reflection.association_scope_cache(conn, owner) do |params|
+ as = AssociationScope.create { params.bind }
+ target_scope.merge!(as.scope(self))
end
binds = AssociationScope.get_bind_values(owner, reflection.chain)
- sc.execute(binds, klass, conn) do |record|
+ sc.execute(binds, conn) do |record|
set_inverse_instance(record)
end
end
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb
index 0678b07699..412e89255d 100644
--- a/activerecord/lib/active_record/associations/collection_proxy.rb
+++ b/activerecord/lib/active_record/associations/collection_proxy.rb
@@ -135,8 +135,9 @@ module ActiveRecord
# # #<Pet id: 2, name: "Spook", person_id: 1>,
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
# # ]
- def find(*args, &block)
- @association.find(*args, &block)
+ def find(*args)
+ return super if block_given?
+ @association.find(*args)
end
##
diff --git a/activerecord/lib/active_record/associations/singular_association.rb b/activerecord/lib/active_record/associations/singular_association.rb
index c1eee3c630..441bd715e4 100644
--- a/activerecord/lib/active_record/associations/singular_association.rb
+++ b/activerecord/lib/active_record/associations/singular_association.rb
@@ -41,15 +41,13 @@ module ActiveRecord
return scope.take if skip_statement_cache?(scope)
conn = klass.connection
- sc = reflection.association_scope_cache(conn, owner) do
- StatementCache.create(conn) { |params|
- as = AssociationScope.create { params.bind }
- target_scope.merge!(as.scope(self)).limit(1)
- }
+ sc = reflection.association_scope_cache(conn, owner) do |params|
+ as = AssociationScope.create { params.bind }
+ target_scope.merge!(as.scope(self)).limit(1)
end
binds = AssociationScope.get_bind_values(owner, reflection.chain)
- sc.execute(binds, klass, conn) do |record|
+ sc.execute(binds, conn) do |record|
set_inverse_instance record
end.first
rescue ::RangeError
diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb
index 5efe051125..48d33e6744 100644
--- a/activerecord/lib/active_record/attribute_methods/dirty.rb
+++ b/activerecord/lib/active_record/attribute_methods/dirty.rb
@@ -5,7 +5,7 @@ require_relative "../attribute_mutation_tracker"
module ActiveRecord
module AttributeMethods
- module Dirty # :nodoc:
+ module Dirty
extend ActiveSupport::Concern
include ActiveModel::Dirty
@@ -47,7 +47,7 @@ module ActiveRecord
clear_mutation_trackers
end
- def changes_applied
+ def changes_applied # :nodoc:
@mutations_before_last_save = mutation_tracker
@mutations_from_database = AttributeMutationTracker.new(@attributes)
@changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
@@ -55,27 +55,27 @@ module ActiveRecord
clear_mutation_trackers
end
- def clear_changes_information
+ def clear_changes_information # :nodoc:
@mutations_before_last_save = nil
@changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
forget_attribute_assignments
clear_mutation_trackers
end
- def write_attribute_without_type_cast(attr_name, *)
+ def write_attribute_without_type_cast(attr_name, *) # :nodoc:
result = super
clear_attribute_change(attr_name)
result
end
- def clear_attribute_changes(attr_names)
+ def clear_attribute_changes(attr_names) # :nodoc:
super
attr_names.each do |attr_name|
clear_attribute_change(attr_name)
end
end
- def changed_attributes
+ def changed_attributes # :nodoc:
# This should only be set by methods which will call changed_attributes
# multiple times when it is known that the computed value cannot change.
if defined?(@cached_changed_attributes)
@@ -85,17 +85,17 @@ module ActiveRecord
end
end
- def changes
+ def changes # :nodoc:
cache_changed_attributes do
super
end
end
- def previous_changes
+ def previous_changes # :nodoc:
mutations_before_last_save.changes
end
- def attribute_changed_in_place?(attr_name)
+ def attribute_changed_in_place?(attr_name) # :nodoc:
mutation_tracker.changed_in_place?(attr_name)
end
diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb
index 6adaeb0121..615b2fa701 100644
--- a/activerecord/lib/active_record/attribute_methods/read.rb
+++ b/activerecord/lib/active_record/attribute_methods/read.rb
@@ -31,9 +31,11 @@ module ActiveRecord
temp_method = "__temp__#{safe_name}"
ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name
+ sync_with_transaction_state = "sync_with_transaction_state" if name == primary_key
generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
def #{temp_method}
+ #{sync_with_transaction_state}
name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name}
_read_attribute(name) { |n| missing_attribute(n, caller) }
end
@@ -57,6 +59,7 @@ module ActiveRecord
end
name = self.class.primary_key if name == "id".freeze && self.class.primary_key
+ sync_with_transaction_state if name == self.class.primary_key
_read_attribute(name, &block)
end
diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb
index 6ed45d8737..acd47629dd 100644
--- a/activerecord/lib/active_record/attribute_methods/serialization.rb
+++ b/activerecord/lib/active_record/attribute_methods/serialization.rb
@@ -5,6 +5,16 @@ module ActiveRecord
module Serialization
extend ActiveSupport::Concern
+ class ColumnNotSerializableError < StandardError
+ def initialize(name, type)
+ super <<-EOS.strip_heredoc
+ Column `#{name}` of type #{type.class} does not support `serialize` feature.
+ Usually it means that you are trying to use `serialize`
+ on a column that already implements serialization natively.
+ EOS
+ end
+ end
+
module ClassMethods
# If you have an attribute that needs to be saved to the database as an
# object, and retrieved as the same object, then specify the name of that
@@ -60,9 +70,23 @@ module ActiveRecord
end
decorate_attribute_type(attr_name, :serialize) do |type|
+ if type_incompatible_with_serialize?(type)
+ raise ColumnNotSerializableError.new(attr_name, type)
+ end
+
Type::Serialized.new(type, coder)
end
end
+
+ private
+
+ def type_incompatible_with_serialize?(type)
+ type.is_a?(ActiveRecord::Type::Json) ||
+ (
+ defined?(ActiveRecord::ConnectionAdapters::PostgreSQL) &&
+ type.is_a?(ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array)
+ )
+ end
end
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb
index fa01f832ac..62c5ce059b 100644
--- a/activerecord/lib/active_record/attribute_methods/write.rb
+++ b/activerecord/lib/active_record/attribute_methods/write.rb
@@ -15,10 +15,12 @@ module ActiveRecord
def define_method_attribute=(name)
safe_name = name.unpack("h*".freeze).first
ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name
+ sync_with_transaction_state = "sync_with_transaction_state" if name == primary_key
generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
def __temp__#{safe_name}=(value)
name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name}
+ #{sync_with_transaction_state}
_write_attribute(name, value)
end
alias_method #{(name + '=').inspect}, :__temp__#{safe_name}=
@@ -38,6 +40,7 @@ module ActiveRecord
end
name = self.class.primary_key if name == "id".freeze && self.class.primary_key
+ sync_with_transaction_state if name == self.class.primary_key
_write_attribute(name, value)
end
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 c11b7b012f..0759f4d2b3 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -270,7 +270,7 @@ module ActiveRecord
# Connections must be leased while holding the main pool mutex. This is
# an internal subclass that also +.leases+ returned connections while
# still in queue's critical section (queue synchronizes with the same
- # +@lock+ as the main pool) so that a returned connection is already
+ # <tt>@lock</tt> as the main pool) so that a returned connection is already
# leased and there is no need to re-enter synchronized block.
class ConnectionLeasingQueue < Queue # :nodoc:
include BiasableQueue
@@ -326,8 +326,6 @@ module ActiveRecord
@spec = spec
@checkout_timeout = (spec.config[:checkout_timeout] && spec.config[:checkout_timeout].to_f) || 5
- @reaper = Reaper.new(self, (spec.config[:reaping_frequency] && spec.config[:reaping_frequency].to_f))
- @reaper.run
# default max pool size to 5
@size = (spec.config[:pool] && spec.config[:pool].to_i) || 5
@@ -340,7 +338,7 @@ module ActiveRecord
# then that +thread+ does indeed own that +conn+. However, an absence of a such
# mapping does not mean that the +thread+ doesn't own the said connection. In
# that case +conn.owner+ attr should be consulted.
- # Access and modification of +@thread_cached_conns+ does not require
+ # Access and modification of <tt>@thread_cached_conns</tt> does not require
# synchronization.
@thread_cached_conns = Concurrent::Map.new(initial_capacity: @size)
@@ -357,6 +355,9 @@ module ActiveRecord
@available = ConnectionLeasingQueue.new self
@lock_thread = false
+
+ @reaper = Reaper.new(self, spec.config[:reaping_frequency] && spec.config[:reaping_frequency].to_f)
+ @reaper.run
end
def lock_thread=(lock_thread)
@@ -736,10 +737,10 @@ module ActiveRecord
# Implementation detail: the connection returned by +acquire_connection+
# will already be "+connection.lease+ -ed" to the current thread.
def acquire_connection(checkout_timeout)
- # NOTE: we rely on +@available.poll+ and +try_to_checkout_new_connection+ to
+ # NOTE: we rely on <tt>@available.poll</tt> and +try_to_checkout_new_connection+ to
# +conn.lease+ the returned connection (and to do this in a +synchronized+
# section). This is not the cleanest implementation, as ideally we would
- # <tt>synchronize { conn.lease }</tt> in this method, but by leaving it to +@available.poll+
+ # <tt>synchronize { conn.lease }</tt> in this method, but by leaving it to <tt>@available.poll</tt>
# and +try_to_checkout_new_connection+ we can piggyback on +synchronize+ sections
# of the said methods and avoid an additional +synchronize+ overhead.
if conn = @available.poll || try_to_checkout_new_connection
@@ -763,7 +764,7 @@ module ActiveRecord
end
end
- # If the pool is not at a +@size+ limit, establish new connection. Connecting
+ # If the pool is not at a <tt>@size</tt> limit, establish new connection. Connecting
# to the DB is done outside main synchronized section.
#--
# Implementation constraint: a newly established connection returned by this
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
index 00e54edac0..5febb5a59f 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -227,7 +227,7 @@ module ActiveRecord
# You should consult the documentation for your database to understand the
# semantics of these different levels:
#
- # * http://www.postgresql.org/docs/current/static/transaction-iso.html
+ # * https://www.postgresql.org/docs/current/static/transaction-iso.html
# * https://dev.mysql.com/doc/refman/5.7/en/set-transaction.html
#
# An ActiveRecord::TransactionIsolationError will be raised if:
@@ -465,12 +465,12 @@ module ActiveRecord
@binds = []
end
- def << str
+ def <<(str)
@parts << str
self
end
- def add_bind obj
+ def add_bind(obj)
@binds << obj
@parts << Arel::Nodes::BindParam.new(1)
self
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
index ecf5201d12..41d93c4322 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true
+require "concurrent/map"
+
module ActiveRecord
module ConnectionAdapters # :nodoc:
module QueryCache
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 e21f93856e..3c9b25e411 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -780,7 +780,7 @@ module ActiveRecord
def rename_index(table_name, old_name, new_name)
validate_index_length!(table_name, new_name)
- # this is a naive implementation; some DBs may support this more efficiently (Postgres, for instance)
+ # this is a naive implementation; some DBs may support this more efficiently (PostgreSQL, for instance)
old_index_def = indexes(table_name).detect { |i| i.name == old_name }
return unless old_index_def
add_index(table_name, old_index_def.columns, name: new_name, unique: old_index_def.unique)
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 32b01b4a31..780e642f21 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -224,7 +224,7 @@ module ActiveRecord
# Sets the schema search path to a string of comma-separated schema names.
# Names beginning with $ have to be quoted (e.g. $user => '$user').
- # See: http://www.postgresql.org/docs/current/static/ddl-schemas.html
+ # See: https://www.postgresql.org/docs/current/static/ddl-schemas.html
#
# This should be not be called manually but set in database.yml.
def schema_search_path=(schema_csv)
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index bb02d2d38b..3b4439fc46 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -64,11 +64,11 @@ module ActiveRecord
# defaults to true.
#
# Any further options are used as connection parameters to libpq. See
- # http://www.postgresql.org/docs/current/static/libpq-connect.html for the
+ # https://www.postgresql.org/docs/current/static/libpq-connect.html for the
# list of parameters.
#
# In addition, default connection parameters of libpq can be set per environment variables.
- # See http://www.postgresql.org/docs/current/static/libpq-envars.html .
+ # See https://www.postgresql.org/docs/current/static/libpq-envars.html .
class PostgreSQLAdapter < AbstractAdapter
ADAPTER_NAME = "PostgreSQL".freeze
@@ -393,7 +393,7 @@ module ActiveRecord
private
- # See http://www.postgresql.org/docs/current/static/errcodes-appendix.html
+ # See https://www.postgresql.org/docs/current/static/errcodes-appendix.html
VALUE_LIMIT_VIOLATION = "22001"
NUMERIC_VALUE_OUT_OF_RANGE = "22003"
NOT_NULL_VIOLATION = "23502"
@@ -713,7 +713,7 @@ module ActiveRecord
end
# SET statements from :variables config hash
- # http://www.postgresql.org/docs/current/static/sql-set.html
+ # https://www.postgresql.org/docs/current/static/sql-set.html
variables = @config[:variables] || {}
variables.map do |k, v|
if v == ":default" || v == :default
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index 8b97dbe5bf..a84e62ab11 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -2,6 +2,7 @@
require "active_support/core_ext/hash/indifferent_access"
require "active_support/core_ext/string/filters"
+require "concurrent/map"
module ActiveRecord
module Core
@@ -142,14 +143,14 @@ module ActiveRecord
self.default_connection_handler = ConnectionAdapters::ConnectionHandler.new
end
- module ClassMethods
+ module ClassMethods # :nodoc:
def allocate
define_attribute_methods
super
end
def initialize_find_by_cache # :nodoc:
- @find_by_statement_cache = { true => {}.extend(Mutex_m), false => {}.extend(Mutex_m) }
+ @find_by_statement_cache = { true => Concurrent::Map.new, false => Concurrent::Map.new }
end
def inherited(child_class) # :nodoc:
@@ -168,8 +169,7 @@ module ActiveRecord
id = ids.first
- return super if id.kind_of?(Array) ||
- id.is_a?(ActiveRecord::Base)
+ return super if StatementCache.unsupported_value?(id)
key = primary_key
@@ -177,7 +177,7 @@ module ActiveRecord
where(key => params.bind).limit(1)
}
- record = statement.execute([id], self, connection).first
+ record = statement.execute([id], connection).first
unless record
raise RecordNotFound.new("Couldn't find #{name} with '#{primary_key}'=#{id}",
name, primary_key, id)
@@ -194,7 +194,7 @@ module ActiveRecord
hash = args.first
return super if !(Hash === hash) || hash.values.any? { |v|
- v.nil? || Array === v || Hash === v || Relation === v || Base === v
+ StatementCache.unsupported_value?(v)
}
# We can't cache Post.find_by(author: david) ...yet
@@ -209,7 +209,7 @@ module ActiveRecord
where(wheres).limit(1)
}
begin
- statement.execute(hash.values, self, connection).first
+ statement.execute(hash.values, connection).first
rescue TypeError
raise ActiveRecord::StatementInvalid
rescue ::RangeError
@@ -251,7 +251,7 @@ module ActiveRecord
end
end
- # Overwrite the default class equality method to provide support for association proxies.
+ # Overwrite the default class equality method to provide support for decorated models.
def ===(object)
object.is_a?(self)
end
@@ -282,9 +282,7 @@ module ActiveRecord
def cached_find_by_statement(key, &block)
cache = @find_by_statement_cache[connection.prepared_statements]
- cache[key] || cache.synchronize {
- cache[key] ||= StatementCache.create(connection, &block)
- }
+ cache.compute_if_absent(key) { StatementCache.create(connection, &block) }
end
def relation
diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb
index 9feba840b3..e790760292 100644
--- a/activerecord/lib/active_record/errors.rb
+++ b/activerecord/lib/active_record/errors.rb
@@ -169,7 +169,7 @@ module ActiveRecord
class NoDatabaseError < StatementInvalid
end
- # Raised when Postgres returns 'cached plan must not change result type' and
+ # Raised when PostgreSQL returns 'cached plan must not change result type' and
# we cannot retry gracefully (e.g. inside a transaction)
class PreparedStatementCacheExpired < StatementInvalid
end
@@ -315,7 +315,7 @@ module ActiveRecord
#
# See the following:
#
- # * http://www.postgresql.org/docs/current/static/transaction-iso.html
+ # * https://www.postgresql.org/docs/current/static/transaction-iso.html
# * https://dev.mysql.com/doc/refman/5.7/en/error-messages-server.html#error_er_lock_deadlock
class TransactionRollbackError < StatementInvalid
end
diff --git a/activerecord/lib/active_record/locking/pessimistic.rb b/activerecord/lib/active_record/locking/pessimistic.rb
index 8d69bf7d3d..e939a24ad5 100644
--- a/activerecord/lib/active_record/locking/pessimistic.rb
+++ b/activerecord/lib/active_record/locking/pessimistic.rb
@@ -54,7 +54,7 @@ module ActiveRecord
#
# Database-specific information on row locking:
# MySQL: http://dev.mysql.com/doc/refman/5.7/en/innodb-locking-reads.html
- # PostgreSQL: http://www.postgresql.org/docs/current/interactive/sql-select.html#SQL-FOR-UPDATE-SHARE
+ # PostgreSQL: https://www.postgresql.org/docs/current/interactive/sql-select.html#SQL-FOR-UPDATE-SHARE
module Pessimistic
# Obtain a row lock on this record. Reloads the record to obtain the requested
# lock. Pass an SQL locking clause to append the end of the SELECT statement
diff --git a/activerecord/lib/active_record/migration/compatibility.rb b/activerecord/lib/active_record/migration/compatibility.rb
index d6595c9355..784292f3f9 100644
--- a/activerecord/lib/active_record/migration/compatibility.rb
+++ b/activerecord/lib/active_record/migration/compatibility.rb
@@ -39,7 +39,7 @@ module ActiveRecord
end
end
- # Since 5.1 Postgres adapter uses bigserial type for primary
+ # Since 5.1 PostgreSQL adapter uses bigserial type for primary
# keys by default and MySQL uses bigint. This compat layer makes old migrations utilize
# serial/int type instead -- the way it used to work before 5.1.
unless options.key?(:id)
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index efe56454d0..d2b85e168b 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -1,8 +1,8 @@
# frozen_string_literal: true
-require "thread"
require "active_support/core_ext/string/filters"
require "active_support/deprecation"
+require "concurrent/map"
module ActiveRecord
# = Active Record Reflection
@@ -305,13 +305,17 @@ module ActiveRecord
end
def get_join_keys(association_klass)
- JoinKeys.new(join_pk(association_klass), join_fk)
+ JoinKeys.new(join_pk(association_klass), join_foreign_key)
end
def build_scope(table, predicate_builder = predicate_builder(table))
Relation.create(klass, table, predicate_builder)
end
+ def join_foreign_key
+ active_record_primary_key
+ end
+
protected
def actual_source_reflection # FIXME: this is a horrible name
self
@@ -325,10 +329,6 @@ module ActiveRecord
def join_pk(_)
foreign_key
end
-
- def join_fk
- active_record_primary_key
- end
end
# Base class for AggregateReflection and AssociationReflection. Objects of
@@ -443,8 +443,7 @@ module ActiveRecord
@type = options[:as] && (options[:foreign_type] || "#{options[:as]}_type")
@foreign_type = options[:foreign_type] || "#{name}_type"
@constructable = calculate_constructable(macro, options)
- @association_scope_cache = {}
- @scope_lock = Mutex.new
+ @association_scope_cache = Concurrent::Map.new
if options[:class_name] && options[:class_name].class == Class
ActiveSupport::Deprecation.warn(<<-MSG.squish)
@@ -458,14 +457,12 @@ module ActiveRecord
end
end
- def association_scope_cache(conn, owner)
+ def association_scope_cache(conn, owner, &block)
key = conn.prepared_statements
if polymorphic?
key = [key, owner._read_attribute(@foreign_type)]
end
- @association_scope_cache[key] ||= @scope_lock.synchronize {
- @association_scope_cache[key] ||= yield
- }
+ @association_scope_cache.compute_if_absent(key) { StatementCache.create(conn, &block) }
end
def constructable? # :nodoc:
@@ -757,16 +754,16 @@ module ActiveRecord
owner[foreign_key]
end
+ def join_foreign_key
+ foreign_key
+ end
+
private
def calculate_constructable(macro, options)
!polymorphic?
end
- def join_fk
- foreign_key
- end
-
def join_pk(klass)
polymorphic? ? association_primary_key(klass) : association_primary_key
end
@@ -1076,10 +1073,6 @@ module ActiveRecord
@reflection.scope
end
- def table_name
- @reflection.table_name
- end
-
def plural_name
@reflection.plural_name
end
@@ -1115,10 +1108,6 @@ module ActiveRecord
@association.klass
end
- def table_name
- klass.table_name
- end
-
def constraints
@reflection.constraints
end
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 626c50470e..5da9573052 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -330,7 +330,7 @@ module ActiveRecord
# of results obtained should be provided in the +result_size+ argument and
# the expected number of results should be provided in the +expected_size+
# argument.
- def raise_record_not_found_exception!(ids = nil, result_size = nil, expected_size = nil, key = primary_key) # :nodoc:
+ def raise_record_not_found_exception!(ids = nil, result_size = nil, expected_size = nil, key = primary_key, not_found_ids = nil) # :nodoc:
conditions = arel.where_sql(@klass)
conditions = " [#{conditions}]" if conditions
name = @klass.name
@@ -344,8 +344,8 @@ module ActiveRecord
raise RecordNotFound.new(error, name, key, ids)
else
error = "Couldn't find all #{name.pluralize} with '#{key}': ".dup
- error << "(#{ids.join(", ")})#{conditions} (found #{result_size} results, but was looking for #{expected_size})"
-
+ error << "(#{ids.join(", ")})#{conditions} (found #{result_size} results, but was looking for #{expected_size})."
+ error << " Couldn't find #{name.pluralize(not_found_ids.size)} with #{key.to_s.pluralize(not_found_ids.size)} #{not_found_ids.join(', ')}." if not_found_ids
raise RecordNotFound.new(error, name, primary_key, ids)
end
end
diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb
index 182f654897..03824ffff9 100644
--- a/activerecord/lib/active_record/relation/merger.rb
+++ b/activerecord/lib/active_record/relation/merger.rb
@@ -135,19 +135,17 @@ module ActiveRecord
if other.reordering_value
# override any order specified in the original relation
relation.reorder! other.order_values
- elsif other.order_values
+ elsif other.order_values.any?
# merge in order_values from relation
relation.order! other.order_values
end
- relation.extend(*other.extending_values) unless other.extending_values.blank?
+ extensions = other.extensions - relation.extensions
+ relation.extending!(*extensions) if extensions.any?
end
def merge_single_values
- if relation.from_clause.empty?
- relation.from_clause = other.from_clause
- end
- relation.lock_value ||= other.lock_value
+ relation.lock_value ||= other.lock_value if other.lock_value
unless other.create_with_value.blank?
relation.create_with_value = (relation.create_with_value || {}).merge(other.create_with_value)
@@ -155,11 +153,15 @@ module ActiveRecord
end
def merge_clauses
- CLAUSE_METHODS.each do |method|
- clause = relation.get_value(method)
- other_clause = other.get_value(method)
- relation.set_value(method, clause.merge(other_clause))
+ if relation.from_clause.empty? && !other.from_clause.empty?
+ relation.from_clause = other.from_clause
end
+
+ where_clause = relation.where_clause.merge(other.where_clause)
+ relation.where_clause = where_clause unless where_clause.empty?
+
+ having_clause = relation.having_clause.merge(other.having_clause)
+ relation.having_clause = having_clause unless having_clause.empty?
end
end
end
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 e64d9fdf2a..0255a65bfe 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
@@ -9,7 +9,7 @@ module ActiveRecord
end
def queries
- [associated_table.association_foreign_key.to_s => ids]
+ [associated_table.association_join_foreign_key.to_s => ids]
end
# TODO Change this to private once we've dropped Ruby 2.2 support.
@@ -30,7 +30,7 @@ module ActiveRecord
end
def primary_key
- associated_table.association_primary_key
+ associated_table.association_join_keys.key
end
def convert_to_id(value)
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index f7fe968b55..bdc5c27328 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -902,16 +902,17 @@ module ActiveRecord
@arel ||= build_arel
end
- # Returns a relation value with a given name
- def get_value(name) # :nodoc:
- @values[name] || default_value_for(name)
- end
+ protected
+ # Returns a relation value with a given name
+ def get_value(name) # :nodoc:
+ @values[name] || default_value_for(name)
+ end
- # Sets the relation value with the given name
- def set_value(name, value) # :nodoc:
- assert_mutability!
- @values[name] = value
- end
+ # Sets the relation value with the given name
+ def set_value(name, value) # :nodoc:
+ assert_mutability!
+ @values[name] = value
+ end
private
diff --git a/activerecord/lib/active_record/relation/where_clause.rb b/activerecord/lib/active_record/relation/where_clause.rb
index ef2bca9155..752bb38481 100644
--- a/activerecord/lib/active_record/relation/where_clause.rb
+++ b/activerecord/lib/active_record/relation/where_clause.rb
@@ -15,6 +15,12 @@ module ActiveRecord
)
end
+ def -(other)
+ WhereClause.new(
+ predicates - other.predicates,
+ )
+ end
+
def merge(other)
WhereClause.new(
predicates_unreferenced_by(other) + other.predicates,
@@ -26,14 +32,17 @@ module ActiveRecord
end
def or(other)
- if empty?
- self
- elsif other.empty?
- other
+ left = self - other
+ common = self - left
+ right = other - common
+
+ if left.empty? || right.empty?
+ common
else
- WhereClause.new(
- [ast.or(other.ast)],
+ or_clause = WhereClause.new(
+ [left.ast.or(right.ast)],
)
+ common + or_clause
end
end
diff --git a/activerecord/lib/active_record/statement_cache.rb b/activerecord/lib/active_record/statement_cache.rb
index 64657089b5..59acd63a0f 100644
--- a/activerecord/lib/active_record/statement_cache.rb
+++ b/activerecord/lib/active_record/statement_cache.rb
@@ -11,7 +11,7 @@ module ActiveRecord
# The cached statement is executed by using the
# {connection.execute}[rdoc-ref:ConnectionAdapters::DatabaseStatements#execute] method:
#
- # cache.execute([], Book, Book.connection)
+ # cache.execute([], Book.connection)
#
# The relation returned by the block is cached, and for each
# {execute}[rdoc-ref:ConnectionAdapters::DatabaseStatements#execute]
@@ -26,7 +26,7 @@ module ActiveRecord
#
# And pass the bind values as the first argument of +execute+ call.
#
- # cache.execute(["my book"], Book, Book.connection)
+ # cache.execute(["my book"], Book.connection)
class StatementCache # :nodoc:
class Substitute; end # :nodoc:
@@ -87,27 +87,35 @@ module ActiveRecord
end
end
- attr_reader :bind_map, :query_builder
-
def self.create(connection, block = Proc.new)
relation = block.call Params.new
query_builder, binds = connection.cacheable_query(self, relation.arel)
bind_map = BindMap.new(binds)
- new query_builder, bind_map
+ new(query_builder, bind_map, relation.klass)
end
- def initialize(query_builder, bind_map)
+ def initialize(query_builder, bind_map, klass)
@query_builder = query_builder
- @bind_map = bind_map
+ @bind_map = bind_map
+ @klass = klass
end
- def execute(params, klass, connection, &block)
+ def execute(params, connection, &block)
bind_values = bind_map.bind params
sql = query_builder.sql_for bind_values, connection
klass.find_by_sql(sql, bind_values, preparable: true, &block)
end
- alias :call :execute
+
+ def self.unsupported_value?(value)
+ case value
+ when NilClass, Array, Range, Hash, Relation, Base then true
+ end
+ end
+
+ protected
+
+ attr_reader :query_builder, :bind_map, :klass
end
end
diff --git a/activerecord/lib/active_record/table_metadata.rb b/activerecord/lib/active_record/table_metadata.rb
index 71351449e1..a5187efc84 100644
--- a/activerecord/lib/active_record/table_metadata.rb
+++ b/activerecord/lib/active_record/table_metadata.rb
@@ -2,7 +2,7 @@
module ActiveRecord
class TableMetadata # :nodoc:
- delegate :foreign_type, :foreign_key, to: :association, prefix: true
+ delegate :foreign_type, :foreign_key, :join_keys, :join_foreign_key, to: :association, prefix: true
delegate :association_primary_key, to: :association
def initialize(klass, arel_table, association = nil)
diff --git a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
index a2e74efc2b..5681ccdd23 100644
--- a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
@@ -22,7 +22,7 @@ module ActiveRecord
configuration.merge("encoding" => encoding)
establish_connection configuration
rescue ActiveRecord::StatementInvalid => error
- if /database .* already exists/.match?(error.message)
+ if error.cause.is_a?(PG::DuplicateDatabase)
raise DatabaseAlreadyExists
else
raise
@@ -136,7 +136,7 @@ module ActiveRecord
ensure
tempfile.close
end
- FileUtils.mv(tempfile.path, filename)
+ FileUtils.cp(tempfile.path, filename)
end
end
end
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index 3e8a0789df..b2f5e39e09 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -432,8 +432,8 @@ module ActiveRecord
@new_record = restore_state[:new_record]
@destroyed = restore_state[:destroyed]
pk = self.class.primary_key
- if pk && read_attribute(pk) != restore_state[:id]
- write_attribute(pk, restore_state[:id])
+ if pk && _read_attribute(pk) != restore_state[:id]
+ _write_attribute(pk, restore_state[:id])
end
freeze if restore_state[:frozen?]
end
@@ -472,7 +472,7 @@ module ActiveRecord
# if it's associated with a transaction, then the state of the Active Record
# object will be updated to reflect the current state of the transaction.
#
- # The +@transaction_state+ variable stores the states of the associated
+ # The <tt>@transaction_state</tt> variable stores the states of the associated
# transaction. This relies on the fact that a transaction can only be in
# one rollback or commit (otherwise a list of states would be required).
# Each Active Record object inside of a transaction carries that transaction's
diff --git a/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb b/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb
index 0174c7ea31..856fcc5897 100644
--- a/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb
+++ b/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb
@@ -28,7 +28,7 @@ module ActiveRecord
def set_local_assigns!
@migration_template = "migration.rb"
case file_name
- when /^(add|remove)_.*_(?:to|from)_(.*)/
+ when /^(add)_.*_to_(.*)/, /^(remove)_.*?_from_(.*)/
@migration_action = $1
@table_name = normalize_table_name($2)
when /join_table/