aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib')
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb4
-rw-r--r--activerecord/lib/active_record/associations/preloader/association.rb5
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb7
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb52
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb10
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb1
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb72
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb187
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb28
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb11
-rw-r--r--activerecord/lib/active_record/middleware/database_selector.rb24
-rw-r--r--activerecord/lib/active_record/middleware/database_selector/resolver.rb14
-rw-r--r--activerecord/lib/active_record/middleware/database_selector/resolver/session.rb2
-rw-r--r--activerecord/lib/active_record/persistence.rb2
-rw-r--r--activerecord/lib/active_record/railtie.rb2
-rw-r--r--activerecord/lib/active_record/relation.rb24
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb12
-rw-r--r--activerecord/lib/active_record/relation/spawn_methods.rb2
-rw-r--r--activerecord/lib/active_record/scoping.rb15
-rw-r--r--activerecord/lib/active_record/scoping/default.rb9
-rw-r--r--activerecord/lib/active_record/scoping/named.rb3
23 files changed, 286 insertions, 212 deletions
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index 68f53d5c1c..b0c0beac0e 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -347,7 +347,6 @@ module ActiveRecord
add_to_target(record) do
result = insert_record(record, true, raise) {
@_was_loaded = loaded?
- @association_ids = nil
}
end
raise ActiveRecord::Rollback unless result
@@ -384,6 +383,7 @@ module ActiveRecord
delete_records(existing_records, method) if existing_records.any?
@target -= records
+ @association_ids = nil
records.each { |record| callback(:after_remove, record) }
end
@@ -424,7 +424,6 @@ module ActiveRecord
unless owner.new_record?
result &&= insert_record(record, true, raise) {
@_was_loaded = loaded?
- @association_ids = nil
}
end
end
@@ -447,6 +446,7 @@ module ActiveRecord
if index
target[index] = record
elsif @_was_loaded || !loaded?
+ @association_ids = nil
target << record
end
diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb
index d6f7359055..041e62077c 100644
--- a/activerecord/lib/active_record/associations/preloader/association.rb
+++ b/activerecord/lib/active_record/associations/preloader/association.rb
@@ -42,11 +42,10 @@ module ActiveRecord
def associate_records_to_owner(owner, records)
association = owner.association(reflection.name)
- association.loaded!
if reflection.collection?
- association.target.concat(records)
+ association.target = records
else
- association.target = records.first unless records.empty?
+ association.target = records.first
end
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 c8d5f679a8..1c8df3c08a 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -185,7 +185,7 @@ module ActiveRecord
def wait_poll(timeout)
@num_waiting += 1
- t0 = Time.now
+ t0 = Concurrent.monotonic_time
elapsed = 0
loop do
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
@@ -194,7 +194,7 @@ module ActiveRecord
return remove if any?
- elapsed = Time.now - t0
+ elapsed = Concurrent.monotonic_time - t0
if elapsed >= timeout
msg = "could not obtain a connection from the pool within %0.3f seconds (waited %0.3f seconds); all pooled connections were in use" %
[timeout, elapsed]
@@ -686,13 +686,13 @@ module ActiveRecord
end
newly_checked_out = []
- timeout_time = Time.now + (@checkout_timeout * 2)
+ timeout_time = Concurrent.monotonic_time + (@checkout_timeout * 2)
@available.with_a_bias_for(Thread.current) do
loop do
synchronize do
return if collected_conns.size == @connections.size && @now_connecting == 0
- remaining_timeout = timeout_time - Time.now
+ remaining_timeout = timeout_time - Concurrent.monotonic_time
remaining_timeout = 0 if remaining_timeout < 0
conn = checkout_for_exclusive_access(remaining_timeout)
collected_conns << conn
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 2cb0a2a4df..7d20825a75 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
@@ -15,7 +15,7 @@ module ActiveRecord
end
delegate :quote_column_name, :quote_table_name, :quote_default_expression, :type_to_sql,
- :options_include_default?, :supports_indexes_in_create?, :supports_foreign_keys_in_create?, :foreign_key_options,
+ :options_include_default?, :supports_indexes_in_create?, :supports_foreign_keys?, :foreign_key_options,
to: :@conn, private: true
private
@@ -50,7 +50,7 @@ module ActiveRecord
statements.concat(o.indexes.map { |column_name, options| index_in_create(o.name, column_name, options) })
end
- if supports_foreign_keys_in_create?
+ if supports_foreign_keys?
statements.concat(o.foreign_keys.map { |to_table, options| foreign_key_in_create(o.name, to_table, options) })
end
@@ -127,6 +127,9 @@ module ActiveRecord
end
def foreign_key_in_create(from_table, to_table, options)
+ prefix = ActiveRecord::Base.table_name_prefix
+ suffix = ActiveRecord::Base.table_name_suffix
+ to_table = "#{prefix}#{to_table}#{suffix}"
options = foreign_key_options(from_table, to_table, options)
accept ForeignKeyDefinition.new(from_table, to_table, options)
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
index b2a6109548..ec241fdbe9 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -102,7 +102,7 @@ module ActiveRecord
alias validated? validate?
def export_name_on_schema_dump?
- name !~ ActiveRecord::SchemaDumper.fk_ignore_pattern
+ !ActiveRecord::SchemaDumper.fk_ignore_pattern.match?(name) if name
end
def defined_for?(to_table_ord = nil, to_table: nil, **options)
@@ -198,41 +198,44 @@ module ActiveRecord
end
module ColumnMethods
+ extend ActiveSupport::Concern
+
# Appends a primary key definition to the table definition.
# Can be called multiple times, but this is probably not a good idea.
def primary_key(name, type = :primary_key, **options)
column(name, type, options.merge(primary_key: true))
end
+ ##
+ # :method: column
+ # :call-seq: column(name, type, options = {})
+ #
# Appends a column or columns of a specified type.
#
# t.string(:goat)
# t.string(:goat, :sheep)
#
# See TableDefinition#column
- [
- :bigint,
- :binary,
- :boolean,
- :date,
- :datetime,
- :decimal,
- :float,
- :integer,
- :json,
- :string,
- :text,
- :time,
- :timestamp,
- :virtual,
- ].each do |column_type|
- module_eval <<-CODE, __FILE__, __LINE__ + 1
- def #{column_type}(*args, **options)
- args.each { |name| column(name, :#{column_type}, options) }
+
+ included do
+ define_column_methods :bigint, :binary, :boolean, :date, :datetime, :decimal,
+ :float, :integer, :json, :string, :text, :time, :timestamp, :virtual
+
+ alias :numeric :decimal
+ end
+
+ class_methods do
+ private def define_column_methods(*column_types) # :nodoc:
+ column_types.each do |column_type|
+ module_eval <<-RUBY, __FILE__, __LINE__ + 1
+ def #{column_type}(*names, **options)
+ raise ArgumentError, "Missing column name(s) for #{column_type}" if names.empty?
+ names.each { |name| column(name, :#{column_type}, options) }
+ end
+ RUBY
end
- CODE
+ end
end
- alias_method :numeric, :decimal
end
# Represents the schema of an SQL table in an abstract way. This class
@@ -395,10 +398,7 @@ module ActiveRecord
end
def foreign_key(table_name, options = {}) # :nodoc:
- table_name_prefix = ActiveRecord::Base.table_name_prefix
- table_name_suffix = ActiveRecord::Base.table_name_suffix
- table_name = "#{table_name_prefix}#{table_name}#{table_name_suffix}"
- foreign_keys.push([table_name, options])
+ foreign_keys << [table_name, options]
end
# Appends <tt>:datetime</tt> columns <tt>:created_at</tt> and
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 d88e75d692..78e153bcc9 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -1029,9 +1029,7 @@ module ActiveRecord
end
def foreign_key_column_for(table_name) # :nodoc:
- prefix = Base.table_name_prefix
- suffix = Base.table_name_suffix
- name = table_name.to_s =~ /#{prefix}(.+)#{suffix}/ ? $1 : table_name.to_s
+ name = strip_table_name_prefix_and_suffix(table_name)
"#{name.singularize}_id"
end
@@ -1328,6 +1326,12 @@ module ActiveRecord
{ column: column_names }
end
+ def strip_table_name_prefix_and_suffix(table_name)
+ prefix = Base.table_name_prefix
+ suffix = Base.table_name_suffix
+ table_name.to_s =~ /#{prefix}(.+)#{suffix}/ ? $1 : table_name.to_s
+ end
+
def foreign_key_name(table_name, options)
options.fetch(:name) do
identifier = "#{table_name}_#{options.fetch(:column)}_fk"
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index 9a7d7285f2..3c9510e469 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -335,6 +335,7 @@ module ActiveRecord
def supports_foreign_keys_in_create?
supports_foreign_keys?
end
+ deprecate :supports_foreign_keys_in_create?
# Does this adapter support views?
def supports_views?
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 7b69a63f6e..569c146f92 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -166,9 +166,9 @@ module ActiveRecord
def explain(arel, binds = [])
sql = "EXPLAIN #{to_sql(arel, binds)}"
- start = Time.now
+ start = Concurrent.monotonic_time
result = exec_query(sql, "EXPLAIN", binds)
- elapsed = Time.now - start
+ elapsed = Concurrent.monotonic_time - start
MySQL::ExplainPrettyPrinter.new.pp(result, elapsed)
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb
index 90bcdf3297..8cb6b64fec 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb
@@ -4,48 +4,56 @@ module ActiveRecord
module ConnectionAdapters
module MySQL
module ColumnMethods
- def blob(*args, **options)
- args.each { |name| column(name, :blob, options) }
- end
+ extend ActiveSupport::Concern
- def tinyblob(*args, **options)
- args.each { |name| column(name, :tinyblob, options) }
- end
+ ##
+ # :method: blob
+ # :call-seq: blob(*names, **options)
- def mediumblob(*args, **options)
- args.each { |name| column(name, :mediumblob, options) }
- end
+ ##
+ # :method: tinyblob
+ # :call-seq: tinyblob(*names, **options)
- def longblob(*args, **options)
- args.each { |name| column(name, :longblob, options) }
- end
+ ##
+ # :method: mediumblob
+ # :call-seq: mediumblob(*names, **options)
- def tinytext(*args, **options)
- args.each { |name| column(name, :tinytext, options) }
- end
+ ##
+ # :method: longblob
+ # :call-seq: longblob(*names, **options)
- def mediumtext(*args, **options)
- args.each { |name| column(name, :mediumtext, options) }
- end
+ ##
+ # :method: tinytext
+ # :call-seq: tinytext(*names, **options)
- def longtext(*args, **options)
- args.each { |name| column(name, :longtext, options) }
- end
+ ##
+ # :method: mediumtext
+ # :call-seq: mediumtext(*names, **options)
- def unsigned_integer(*args, **options)
- args.each { |name| column(name, :unsigned_integer, options) }
- end
+ ##
+ # :method: longtext
+ # :call-seq: longtext(*names, **options)
- def unsigned_bigint(*args, **options)
- args.each { |name| column(name, :unsigned_bigint, options) }
- end
+ ##
+ # :method: unsigned_integer
+ # :call-seq: unsigned_integer(*names, **options)
- def unsigned_float(*args, **options)
- args.each { |name| column(name, :unsigned_float, options) }
- end
+ ##
+ # :method: unsigned_bigint
+ # :call-seq: unsigned_bigint(*names, **options)
+
+ ##
+ # :method: unsigned_float
+ # :call-seq: unsigned_float(*names, **options)
+
+ ##
+ # :method: unsigned_decimal
+ # :call-seq: unsigned_decimal(*names, **options)
- def unsigned_decimal(*args, **options)
- args.each { |name| column(name, :unsigned_decimal, options) }
+ included do
+ define_column_methods :blob, :tinyblob, :mediumblob, :longblob,
+ :tinytext, :mediumtext, :longtext, :unsigned_integer, :unsigned_bigint,
+ :unsigned_float, :unsigned_decimal
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb
index dc4a0bb26e..3bb7c52899 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb
@@ -4,6 +4,8 @@ module ActiveRecord
module ConnectionAdapters
module PostgreSQL
module ColumnMethods
+ extend ActiveSupport::Concern
+
# Defines the primary key field.
# Use of the native PostgreSQL UUID type is supported, and can be used
# by defining your tables as such:
@@ -51,124 +53,131 @@ module ActiveRecord
super
end
- def bigserial(*args, **options)
- args.each { |name| column(name, :bigserial, options) }
- end
+ ##
+ # :method: bigserial
+ # :call-seq: bigserial(*names, **options)
- def bit(*args, **options)
- args.each { |name| column(name, :bit, options) }
- end
+ ##
+ # :method: bit
+ # :call-seq: bit(*names, **options)
- def bit_varying(*args, **options)
- args.each { |name| column(name, :bit_varying, options) }
- end
+ ##
+ # :method: bit_varying
+ # :call-seq: bit_varying(*names, **options)
- def cidr(*args, **options)
- args.each { |name| column(name, :cidr, options) }
- end
+ ##
+ # :method: cidr
+ # :call-seq: cidr(*names, **options)
- def citext(*args, **options)
- args.each { |name| column(name, :citext, options) }
- end
+ ##
+ # :method: citext
+ # :call-seq: citext(*names, **options)
- def daterange(*args, **options)
- args.each { |name| column(name, :daterange, options) }
- end
+ ##
+ # :method: daterange
+ # :call-seq: daterange(*names, **options)
- def hstore(*args, **options)
- args.each { |name| column(name, :hstore, options) }
- end
+ ##
+ # :method: hstore
+ # :call-seq: hstore(*names, **options)
- def inet(*args, **options)
- args.each { |name| column(name, :inet, options) }
- end
+ ##
+ # :method: inet
+ # :call-seq: inet(*names, **options)
- def interval(*args, **options)
- args.each { |name| column(name, :interval, options) }
- end
+ ##
+ # :method: interval
+ # :call-seq: interval(*names, **options)
- def int4range(*args, **options)
- args.each { |name| column(name, :int4range, options) }
- end
+ ##
+ # :method: int4range
+ # :call-seq: int4range(*names, **options)
- def int8range(*args, **options)
- args.each { |name| column(name, :int8range, options) }
- end
+ ##
+ # :method: int8range
+ # :call-seq: int8range(*names, **options)
- def jsonb(*args, **options)
- args.each { |name| column(name, :jsonb, options) }
- end
+ ##
+ # :method: jsonb
+ # :call-seq: jsonb(*names, **options)
- def ltree(*args, **options)
- args.each { |name| column(name, :ltree, options) }
- end
+ ##
+ # :method: ltree
+ # :call-seq: ltree(*names, **options)
- def macaddr(*args, **options)
- args.each { |name| column(name, :macaddr, options) }
- end
+ ##
+ # :method: macaddr
+ # :call-seq: macaddr(*names, **options)
- def money(*args, **options)
- args.each { |name| column(name, :money, options) }
- end
+ ##
+ # :method: money
+ # :call-seq: money(*names, **options)
- def numrange(*args, **options)
- args.each { |name| column(name, :numrange, options) }
- end
+ ##
+ # :method: numrange
+ # :call-seq: numrange(*names, **options)
- def oid(*args, **options)
- args.each { |name| column(name, :oid, options) }
- end
+ ##
+ # :method: oid
+ # :call-seq: oid(*names, **options)
- def point(*args, **options)
- args.each { |name| column(name, :point, options) }
- end
+ ##
+ # :method: point
+ # :call-seq: point(*names, **options)
- def line(*args, **options)
- args.each { |name| column(name, :line, options) }
- end
+ ##
+ # :method: line
+ # :call-seq: line(*names, **options)
- def lseg(*args, **options)
- args.each { |name| column(name, :lseg, options) }
- end
+ ##
+ # :method: lseg
+ # :call-seq: lseg(*names, **options)
- def box(*args, **options)
- args.each { |name| column(name, :box, options) }
- end
+ ##
+ # :method: box
+ # :call-seq: box(*names, **options)
- def path(*args, **options)
- args.each { |name| column(name, :path, options) }
- end
+ ##
+ # :method: path
+ # :call-seq: path(*names, **options)
- def polygon(*args, **options)
- args.each { |name| column(name, :polygon, options) }
- end
+ ##
+ # :method: polygon
+ # :call-seq: polygon(*names, **options)
- def circle(*args, **options)
- args.each { |name| column(name, :circle, options) }
- end
+ ##
+ # :method: circle
+ # :call-seq: circle(*names, **options)
- def serial(*args, **options)
- args.each { |name| column(name, :serial, options) }
- end
+ ##
+ # :method: serial
+ # :call-seq: serial(*names, **options)
- def tsrange(*args, **options)
- args.each { |name| column(name, :tsrange, options) }
- end
+ ##
+ # :method: tsrange
+ # :call-seq: tsrange(*names, **options)
- def tstzrange(*args, **options)
- args.each { |name| column(name, :tstzrange, options) }
- end
+ ##
+ # :method: tstzrange
+ # :call-seq: tstzrange(*names, **options)
- def tsvector(*args, **options)
- args.each { |name| column(name, :tsvector, options) }
- end
+ ##
+ # :method: tsvector
+ # :call-seq: tsvector(*names, **options)
- def uuid(*args, **options)
- args.each { |name| column(name, :uuid, options) }
- end
+ ##
+ # :method: uuid
+ # :call-seq: uuid(*names, **options)
+
+ ##
+ # :method: xml
+ # :call-seq: xml(*names, **options)
- def xml(*args, **options)
- args.each { |name| column(name, :xml, options) }
+ included do
+ define_column_methods :bigserial, :bit, :bit_varying, :cidr, :citext, :daterange,
+ :hstore, :inet, :interval, :int4range, :int8range, :jsonb, :ltree, :macaddr,
+ :money, :numrange, :oid, :point, :line, :lseg, :box, :path, :polygon, :circle,
+ :serial, :tsrange, :tstzrange, :tsvector, :uuid, :xml
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb
index 2394982a7d..0a637d81fa 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb
@@ -52,6 +52,34 @@ module ActiveRecord
end.compact
end
+ def add_foreign_key(from_table, to_table, **options)
+ alter_table(from_table) do |definition|
+ to_table = strip_table_name_prefix_and_suffix(to_table)
+ definition.foreign_key(to_table, options)
+ end
+ end
+
+ def remove_foreign_key(from_table, to_table = nil, **options)
+ to_table ||= options[:to_table]
+ options = options.except(:name, :to_table)
+ foreign_keys = foreign_keys(from_table)
+
+ fkey = foreign_keys.detect do |fk|
+ table = to_table || begin
+ table = options[:column].to_s.delete_suffix("_id")
+ Base.pluralize_table_names ? table.pluralize : table
+ end
+ table = strip_table_name_prefix_and_suffix(table)
+ fk_to_table = strip_table_name_prefix_and_suffix(fk.to_table)
+ fk_to_table == table && options.all? { |k, v| fk.options[k].to_s == v.to_s }
+ end || raise(ArgumentError, "Table '#{from_table}' has no foreign key for #{to_table}")
+
+ alter_table(from_table, foreign_keys) do |definition|
+ fk_to_table = strip_table_name_prefix_and_suffix(fkey.to_table)
+ definition.foreign_keys.delete([fk_to_table, fkey.options])
+ end
+ end
+
def create_schema_dumper(options)
SQLite3::SchemaDumper.create(self, options)
end
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index 7b3630662b..9f28fdf749 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -121,7 +121,7 @@ module ActiveRecord
true
end
- def supports_foreign_keys_in_create?
+ def supports_foreign_keys?
true
end
@@ -320,6 +320,9 @@ module ActiveRecord
def remove_column(table_name, column_name, type = nil, options = {}) #:nodoc:
alter_table(table_name) do |definition|
definition.remove_column column_name
+ definition.foreign_keys.delete_if do |_, fk_options|
+ fk_options[:column] == column_name.to_s
+ end
end
end
@@ -421,9 +424,8 @@ module ActiveRecord
type.to_sym == :primary_key || options[:primary_key]
end
- def alter_table(table_name, options = {})
+ def alter_table(table_name, foreign_keys = foreign_keys(table_name), **options)
altered_table_name = "a#{table_name}"
- foreign_keys = foreign_keys(table_name)
caller = lambda do |definition|
rename = options[:rename] || {}
@@ -431,7 +433,8 @@ module ActiveRecord
if column = rename[fk.options[:column]]
fk.options[:column] = column
end
- definition.foreign_key(fk.to_table, fk.options)
+ to_table = strip_table_name_prefix_and_suffix(fk.to_table)
+ definition.foreign_key(to_table, fk.options)
end
yield definition if block_given?
diff --git a/activerecord/lib/active_record/middleware/database_selector.rb b/activerecord/lib/active_record/middleware/database_selector.rb
index 3ab50f5f6b..b5b5df074c 100644
--- a/activerecord/lib/active_record/middleware/database_selector.rb
+++ b/activerecord/lib/active_record/middleware/database_selector.rb
@@ -11,9 +11,9 @@ module ActiveRecord
# behavior.
#
# The resolver class defines when the application should switch (i.e. read
- # from the primary if a write occurred less than 2 seconds ago) and an
- # operations class that sets a value that helps the resolver class decide
- # when to switch.
+ # from the primary if a write occurred less than 2 seconds ago) and a
+ # resolver context class that sets a value that helps the resolver class
+ # decide when to switch.
#
# Rails default middleware uses the request's session to set a timestamp
# that informs the application when to read from a primary or read from a
@@ -24,7 +24,7 @@ module ActiveRecord
#
# config.active_record.database_selector = { delay: 2.seconds }
# config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver
- # config.active_record.database_operations = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session
+ # config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session
#
# New applications will include these lines commented out in the production.rb.
#
@@ -33,16 +33,16 @@ module ActiveRecord
#
# config.active_record.database_selector = { delay: 2.seconds }
# config.active_record.database_resolver = MyResolver
- # config.active_record.database_operations = MyResolver::MySession
+ # config.active_record.database_resolver_context = MyResolver::MySession
class DatabaseSelector
- def initialize(app, resolver_klass = Resolver, operations_klass = Resolver::Session, options = {})
+ def initialize(app, resolver_klass = Resolver, context_klass = Resolver::Session, options = {})
@app = app
@resolver_klass = resolver_klass
- @operations_klass = operations_klass
+ @context_klass = context_klass
@options = options
end
- attr_reader :resolver_klass, :operations_klass, :options
+ attr_reader :resolver_klass, :context_klass, :options
# Middleware that determines which database connection to use in a multiple
# database application.
@@ -57,13 +57,13 @@ module ActiveRecord
private
def select_database(request, &blk)
- operations = operations_klass.build(request)
- database_resolver = resolver_klass.call(operations, options)
+ context = context_klass.call(request)
+ resolver = resolver_klass.call(context, options)
if reading_request?(request)
- database_resolver.read(&blk)
+ resolver.read(&blk)
else
- database_resolver.write(&blk)
+ resolver.write(&blk)
end
end
diff --git a/activerecord/lib/active_record/middleware/database_selector/resolver.rb b/activerecord/lib/active_record/middleware/database_selector/resolver.rb
index a84c292714..80b8cd7cae 100644
--- a/activerecord/lib/active_record/middleware/database_selector/resolver.rb
+++ b/activerecord/lib/active_record/middleware/database_selector/resolver.rb
@@ -18,18 +18,18 @@ module ActiveRecord
class Resolver # :nodoc:
SEND_TO_REPLICA_DELAY = 2.seconds
- def self.call(resolver, options = {})
- new(resolver, options)
+ def self.call(context, options = {})
+ new(context, options)
end
- def initialize(resolver, options = {})
- @resolver = resolver
+ def initialize(context, options = {})
+ @context = context
@options = options
@delay = @options && @options[:delay] ? @options[:delay] : SEND_TO_REPLICA_DELAY
@instrumenter = ActiveSupport::Notifications.instrumenter
end
- attr_reader :resolver, :delay, :instrumenter
+ attr_reader :context, :delay, :instrumenter
def read(&blk)
if read_from_primary?
@@ -68,7 +68,7 @@ module ActiveRecord
instrumenter.instrument("database_selector.active_record.wrote_to_primary") do
yield
ensure
- resolver.update_last_write_timestamp
+ context.update_last_write_timestamp
end
end
end
@@ -82,7 +82,7 @@ module ActiveRecord
end
def time_since_last_write_ok?
- Time.now - resolver.last_write_timestamp >= send_to_replica_delay
+ Time.now - context.last_write_timestamp >= send_to_replica_delay
end
end
end
diff --git a/activerecord/lib/active_record/middleware/database_selector/resolver/session.rb b/activerecord/lib/active_record/middleware/database_selector/resolver/session.rb
index 33e0af5ee4..df7af054b7 100644
--- a/activerecord/lib/active_record/middleware/database_selector/resolver/session.rb
+++ b/activerecord/lib/active_record/middleware/database_selector/resolver/session.rb
@@ -10,7 +10,7 @@ module ActiveRecord
# The last_write is used to determine whether it's safe to read
# from the replica or the request needs to be sent to the primary.
class Session # :nodoc:
- def self.build(request)
+ def self.call(request)
new(request.session)
end
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index 2213fbefb4..510a275b4e 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -142,7 +142,7 @@ module ActiveRecord
end
end
- # Deletes the row with a primary key matching the +id+ argument, using a
+ # Deletes the row with a primary key matching the +id+ argument, using an
# SQL +DELETE+ statement, and returns the number of rows deleted. Active
# Record objects are not instantiated, so the object's callbacks are not
# executed, including any <tt>:dependent</tt> association options.
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index aac49a92b4..a1d7c893bf 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -91,7 +91,7 @@ module ActiveRecord
initializer "active_record.database_selector" do
if options = config.active_record.delete(:database_selector)
resolver = config.active_record.delete(:database_resolver)
- operations = config.active_record.delete(:database_operations)
+ operations = config.active_record.delete(:database_resolver_context)
config.app_middleware.use ActiveRecord::Middleware::DatabaseSelector, resolver, operations, options
end
end
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index bab00ef065..347d745d19 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -67,6 +67,7 @@ module ActiveRecord
# user = users.new { |user| user.name = 'Oscar' }
# user.name # => Oscar
def new(attributes = nil, &block)
+ block = klass.current_scope_restoring_block(&block)
scoping { klass.new(attributes, &block) }
end
@@ -92,7 +93,11 @@ module ActiveRecord
# users.create(name: nil) # validation on name
# # => #<User id: nil, name: nil, ...>
def create(attributes = nil, &block)
- scoping { klass.create(attributes, &block) }
+ if attributes.is_a?(Array)
+ attributes.collect { |attr| create(attr, &block) }
+ else
+ new(attributes, &block).tap(&:save)
+ end
end
# Similar to #create, but calls
@@ -102,7 +107,11 @@ module ActiveRecord
# Expects arguments in the same format as
# {ActiveRecord::Base.create!}[rdoc-ref:Persistence::ClassMethods#create!].
def create!(attributes = nil, &block)
- scoping { klass.create!(attributes, &block) }
+ if attributes.is_a?(Array)
+ attributes.collect { |attr| create!(attr, &block) }
+ else
+ new(attributes, &block).tap(&:save!)
+ end
end
def first_or_create(attributes = nil, &block) # :nodoc:
@@ -312,12 +321,12 @@ module ActiveRecord
# Please check unscoped if you want to remove all previous scopes (including
# the default_scope) during the execution of a block.
def scoping
- @delegate_to_klass && klass.current_scope ? yield : klass._scoping(self) { yield }
+ @delegate_to_klass && klass.current_scope(true) ? yield : _scoping(self) { yield }
end
def _exec_scope(*args, &block) # :nodoc:
@delegate_to_klass = true
- instance_exec(*args, &block) || self
+ _scoping(nil) { instance_exec(*args, &block) || self }
ensure
@delegate_to_klass = false
end
@@ -632,6 +641,13 @@ module ActiveRecord
end
private
+ def _scoping(scope)
+ previous, klass.current_scope = klass.current_scope(true), scope
+ yield
+ ensure
+ klass.current_scope = previous
+ end
+
def _substitute_values(values)
values.map do |name, value|
attr = arel_attribute(name)
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 8f1065c1e7..91cfc4e849 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -307,8 +307,6 @@ module ActiveRecord
return false if !conditions || limit_value == 0
- conditions = sanitize_forbidden_attributes(conditions)
-
if eager_loading?
relation = apply_join_dependency(eager_loading: false)
return relation.exists?(conditions)
@@ -316,7 +314,7 @@ module ActiveRecord
relation = construct_relation_for_exists(conditions)
- skip_query_cache_if_necessary { connection.select_value(relation.arel, "#{name} Exists") } ? true : false
+ skip_query_cache_if_necessary { connection.select_one(relation.arel, "#{name} Exists") } ? true : false
end
# This method is called whenever no records are found with either a single
@@ -354,7 +352,13 @@ module ActiveRecord
end
def construct_relation_for_exists(conditions)
- relation = except(:select, :distinct, :order)._select!(ONE_AS_ONE).limit!(1)
+ conditions = sanitize_forbidden_attributes(conditions)
+
+ if distinct_value && offset_value
+ relation = limit(1)
+ else
+ relation = except(:select, :distinct, :order)._select!(ONE_AS_ONE).limit!(1)
+ end
case conditions
when Array, Hash
diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb
index d758e289ca..8cf4fc469f 100644
--- a/activerecord/lib/active_record/relation/spawn_methods.rb
+++ b/activerecord/lib/active_record/relation/spawn_methods.rb
@@ -8,7 +8,7 @@ module ActiveRecord
module SpawnMethods
# This is overridden by Associations::CollectionProxy
def spawn #:nodoc:
- @delegate_to_klass && klass.current_scope ? klass.all : clone
+ @delegate_to_klass && klass.current_scope(true) ? klass.all : clone
end
# Merges in the conditions from <tt>other</tt>, if <tt>other</tt> is an ActiveRecord::Relation.
diff --git a/activerecord/lib/active_record/scoping.rb b/activerecord/lib/active_record/scoping.rb
index c3a56b2174..1142a87d25 100644
--- a/activerecord/lib/active_record/scoping.rb
+++ b/activerecord/lib/active_record/scoping.rb
@@ -27,10 +27,17 @@ module ActiveRecord
ScopeRegistry.value_for(:current_scope, self, skip_inherited_scope)
end
- private
- def current_scope=(scope)
- ScopeRegistry.set_value_for(:current_scope, self, scope)
+ def current_scope=(scope)
+ ScopeRegistry.set_value_for(:current_scope, self, scope)
+ end
+
+ def current_scope_restoring_block(&block)
+ current_scope = self.current_scope(true)
+ -> *args do
+ self.current_scope = current_scope
+ yield(*args) if block_given?
end
+ end
end
def populate_with_current_scope_attributes # :nodoc:
@@ -84,7 +91,7 @@ module ActiveRecord
base = model.base_class
while klass <= base
value = @registry[scope_type][klass.name]
- return value || nil unless value.nil?
+ return value if value
klass = klass.superclass
end
end
diff --git a/activerecord/lib/active_record/scoping/default.rb b/activerecord/lib/active_record/scoping/default.rb
index 6caf9b3251..8c612df27a 100644
--- a/activerecord/lib/active_record/scoping/default.rb
+++ b/activerecord/lib/active_record/scoping/default.rb
@@ -31,14 +31,7 @@ module ActiveRecord
# Post.limit(10) # Fires "SELECT * FROM posts LIMIT 10"
# }
def unscoped
- block_given? ? _scoping(relation) { yield } : relation
- end
-
- def _scoping(relation) # :nodoc:
- previous, self.current_scope = current_scope(true), relation
- yield
- ensure
- self.current_scope = previous
+ block_given? ? relation.scoping { yield } : relation
end
# Are there attributes associated with this scope?
diff --git a/activerecord/lib/active_record/scoping/named.rb b/activerecord/lib/active_record/scoping/named.rb
index 987e6bd63f..5278eb29a9 100644
--- a/activerecord/lib/active_record/scoping/named.rb
+++ b/activerecord/lib/active_record/scoping/named.rb
@@ -180,8 +180,7 @@ module ActiveRecord
if body.respond_to?(:to_proc)
singleton_class.define_method(name) do |*args|
- scope = all
- scope = _scoping(false) { scope._exec_scope(*args, &body) }
+ scope = all._exec_scope(*args, &body)
scope = scope.extending(extension) if extension
scope
end