diff options
Diffstat (limited to 'activerecord/lib')
49 files changed, 257 insertions, 286 deletions
diff --git a/activerecord/lib/active_record/associations/belongs_to_association.rb b/activerecord/lib/active_record/associations/belongs_to_association.rb index 7a9f5f7937..ba54cd8f49 100644 --- a/activerecord/lib/active_record/associations/belongs_to_association.rb +++ b/activerecord/lib/active_record/associations/belongs_to_association.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true module ActiveRecord - # = Active Record Belongs To Association module Associations + # = Active Record Belongs To Association class BelongsToAssociation < SingularAssociation #:nodoc: def handle_dependency target.send(options[:dependent]) if load_target 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 13b4a084ea..4ce3474bd5 100644 --- a/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb +++ b/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true module ActiveRecord - # = Active Record Belongs To Polymorphic Association module Associations + # = Active Record Belongs To Polymorphic Association class BelongsToPolymorphicAssociation < BelongsToAssociation #:nodoc: def klass type = owner[reflection.foreign_type] diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index 1a424896fe..ceedf150e3 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -55,13 +55,16 @@ module ActiveRecord pk_type = reflection.association_primary_key_type ids = Array(ids).reject(&:blank?) ids.map! { |i| pk_type.cast(i) } - records = klass.where(reflection.association_primary_key => ids).index_by do |r| - r.send(reflection.association_primary_key) + + 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 + if records.size != ids.size - found_ids = records.map { |record| record.send(reflection.association_primary_key) } + found_ids = records.map { |record| record.public_send(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) + klass.all.raise_record_not_found_exception!(ids, records.size, ids.size, primary_key, not_found_ids) else replace(records) end @@ -74,23 +77,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 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/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb index 88fe33eef2..07c7f28d2d 100644 --- a/activerecord/lib/active_record/associations/has_many_association.rb +++ b/activerecord/lib/active_record/associations/has_many_association.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true module ActiveRecord - # = Active Record Has Many Association module Associations + # = Active Record Has Many Association # This is the proxy that handles a has many association. # # If the association has a <tt>:through</tt> option further specialization 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 89ce00f98e..adbf52b87c 100644 --- a/activerecord/lib/active_record/associations/has_many_through_association.rb +++ b/activerecord/lib/active_record/associations/has_many_through_association.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true module ActiveRecord - # = Active Record Has Many Through Association module Associations + # = Active Record Has Many Through Association class HasManyThroughAssociation < HasManyAssociation #:nodoc: include ThroughAssociation diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb index 9a88c1af70..7953b89f61 100644 --- a/activerecord/lib/active_record/associations/has_one_association.rb +++ b/activerecord/lib/active_record/associations/has_one_association.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true module ActiveRecord - # = Active Record Has One Association module Associations + # = Active Record Has One Association class HasOneAssociation < SingularAssociation #:nodoc: include ForeignAssociation 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 eb54977aa0..36746f9115 100644 --- a/activerecord/lib/active_record/associations/has_one_through_association.rb +++ b/activerecord/lib/active_record/associations/has_one_through_association.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true module ActiveRecord - # = Active Record Has One Through Association module Associations + # = Active Record Has One Through Association class HasOneThroughAssociation < HasOneAssociation #:nodoc: include ThroughAssociation diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb index dc029c08bd..ac37c3c015 100644 --- a/activerecord/lib/active_record/associations/join_dependency.rb +++ b/activerecord/lib/active_record/associations/join_dependency.rb @@ -119,7 +119,7 @@ module ActiveRecord end def aliases - Aliases.new join_root.each_with_index.map { |join_part, i| + @aliases ||= Aliases.new join_root.each_with_index.map { |join_part, i| columns = join_part.column_names.each_with_index.map { |column_name, j| Aliases::Column.new column_name, "t#{i}_r#{j}" } @@ -127,7 +127,7 @@ module ActiveRecord } end - def instantiate(result_set, aliases) + def instantiate(result_set, &block) primary_key = aliases.column_alias(join_root, join_root.primary_key) seen = Hash.new { |i, object_id| @@ -150,7 +150,7 @@ module ActiveRecord message_bus.instrument("instantiation.active_record", payload) do result_set.each { |row_hash| parent_key = primary_key ? row_hash[primary_key] : row_hash - parent = parents[parent_key] ||= join_root.instantiate(row_hash, column_aliases) + parent = parents[parent_key] ||= join_root.instantiate(row_hash, column_aliases, &block) construct(parent, join_root, row_hash, result_set, seen, model_cache, aliases) } end diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb index 5ba03c555a..4915a37f06 100644 --- a/activerecord/lib/active_record/associations/preloader/association.rb +++ b/activerecord/lib/active_record/associations/preloader/association.rb @@ -116,18 +116,8 @@ module ActiveRecord @reflection_scope ||= reflection.scope_for(klass) end - def klass_scope - current_scope = klass.current_scope - - if current_scope && current_scope.empty_scope? - klass.unscoped - else - klass.default_scoped - end - end - def build_scope - scope = klass_scope + scope = klass.scope_for_association if reflection.type scope.where!(reflection.type => model.base_class.sti_name) diff --git a/activerecord/lib/active_record/associations/through_association.rb b/activerecord/lib/active_record/associations/through_association.rb index 76237c4a0c..bce2a95ce1 100644 --- a/activerecord/lib/active_record/associations/through_association.rb +++ b/activerecord/lib/active_record/associations/through_association.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true module ActiveRecord - # = Active Record Through Association module Associations + # = Active Record Through Association module ThroughAssociation #:nodoc: delegate :source_reflection, :through_reflection, to: :reflection @@ -15,7 +15,7 @@ module ActiveRecord def target_scope scope = super reflection.chain.drop(1).each do |reflection| - relation = reflection.klass.all + relation = reflection.klass.scope_for_association scope.merge!( relation.except(:select, :create_with, :includes, :preload, :joins, :eager_load) ) diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb index 48d33e6744..c622df132e 100644 --- a/activerecord/lib/active_record/attribute_methods/dirty.rb +++ b/activerecord/lib/active_record/attribute_methods/dirty.rb @@ -62,12 +62,6 @@ module ActiveRecord clear_mutation_trackers end - def write_attribute_without_type_cast(attr_name, *) # :nodoc: - result = super - clear_attribute_change(attr_name) - result - end - def clear_attribute_changes(attr_names) # :nodoc: super attr_names.each do |attr_name| @@ -183,6 +177,11 @@ module ActiveRecord end private + def write_attribute_without_type_cast(attr_name, _) + result = super + clear_attribute_change(attr_name) + result + end def mutation_tracker unless defined?(@mutation_tracker) diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb index 615b2fa701..b070235684 100644 --- a/activerecord/lib/active_record/attribute_methods/read.rb +++ b/activerecord/lib/active_record/attribute_methods/read.rb @@ -5,7 +5,7 @@ module ActiveRecord module Read extend ActiveSupport::Concern - module ClassMethods + module ClassMethods # :nodoc: private # We want to generate the methods via module_eval rather than diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb index acd47629dd..ebc2baed34 100644 --- a/activerecord/lib/active_record/attribute_methods/serialization.rb +++ b/activerecord/lib/active_record/attribute_methods/serialization.rb @@ -70,7 +70,7 @@ module ActiveRecord end decorate_attribute_type(attr_name, :serialize) do |type| - if type_incompatible_with_serialize?(type) + if type_incompatible_with_serialize?(type, class_name_or_coder) raise ColumnNotSerializableError.new(attr_name, type) end @@ -80,12 +80,9 @@ module ActiveRecord 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) - ) + def type_incompatible_with_serialize?(type, class_name) + type.is_a?(ActiveRecord::Type::Json) && class_name == ::JSON || + type.respond_to?(:type_cast_array, true) && class_name == ::Array end end end diff --git a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb index f12a9f915c..d2b7817b45 100644 --- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb +++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb @@ -62,7 +62,7 @@ module ActiveRecord class_attribute :time_zone_aware_types, instance_writer: false, default: [ :datetime, :time ] end - module ClassMethods + module ClassMethods # :nodoc: private def inherited(subclass) diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb index 62c5ce059b..37891ce2ef 100644 --- a/activerecord/lib/active_record/attribute_methods/write.rb +++ b/activerecord/lib/active_record/attribute_methods/write.rb @@ -9,7 +9,7 @@ module ActiveRecord attribute_method_suffix "=" end - module ClassMethods + module ClassMethods # :nodoc: private def define_method_attribute=(name) 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 5febb5a59f..36048bee03 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -10,6 +10,11 @@ module ActiveRecord # Converts an arel AST to SQL def to_sql(arel_or_sql_string, binds = []) + sql, _ = to_sql_and_binds(arel_or_sql_string, binds) + sql + end + + def to_sql_and_binds(arel_or_sql_string, binds = []) # :nodoc: if arel_or_sql_string.respond_to?(:ast) unless binds.empty? raise "Passing bind parameters with an arel AST is forbidden. " \ @@ -21,6 +26,7 @@ module ActiveRecord [arel_or_sql_string.dup.freeze, binds] end end + private :to_sql_and_binds # This is used in the StatementCache object. It returns an object that # can be used to query the database repeatedly. @@ -39,7 +45,7 @@ module ActiveRecord # Returns an ActiveRecord::Result instance. def select_all(arel, name = nil, binds = [], preparable: nil) arel = arel_from_relation(arel) - sql, binds = to_sql(arel, binds) + sql, binds = to_sql_and_binds(arel, binds) if !prepared_statements || (arel.is_a?(String) && preparable.nil?) preparable = false else @@ -138,22 +144,22 @@ module ActiveRecord # # If the next id was calculated in advance (as in Oracle), it should be # passed in as +id_value+. - def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil) - sql, binds = to_sql(arel) + def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = []) + sql, binds = to_sql_and_binds(arel, binds) value = exec_insert(sql, name, binds, pk, sequence_name) id_value || last_inserted_id(value) end alias create insert # Executes the update statement and returns the number of rows affected. - def update(arel, name = nil) - sql, binds = to_sql(arel) + def update(arel, name = nil, binds = []) + sql, binds = to_sql_and_binds(arel, binds) exec_update(sql, name, binds) end # Executes the delete statement and returns the number of rows affected. - def delete(arel, name = nil) - sql, binds = to_sql(arel) + def delete(arel, name = nil, binds = []) + sql, binds = to_sql_and_binds(arel, binds) exec_delete(sql, name, binds) end @@ -175,7 +181,7 @@ module ActiveRecord # # In order to get around this problem, #transaction will emulate the effect # of nested transactions, by using savepoints: - # http://dev.mysql.com/doc/refman/5.7/en/savepoint.html + # https://dev.mysql.com/doc/refman/5.7/en/savepoint.html # Savepoints are supported by MySQL and PostgreSQL. SQLite3 version >= '3.6.8' # supports savepoints. # 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 41d93c4322..25622e34c8 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb @@ -95,7 +95,7 @@ module ActiveRecord def select_all(arel, name = nil, binds = [], preparable: nil) if @query_cache_enabled && !locked?(arel) arel = arel_from_relation(arel) - sql, binds = to_sql(arel, binds) + sql, binds = to_sql_and_binds(arel, binds) cache_sql(sql, name, binds) { super(sql, name, binds, preparable: preparable) } else super diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb index 7b83bc319c..9ad04c3216 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb @@ -7,7 +7,7 @@ module ActiveRecord module ConnectionAdapters # :nodoc: module Quoting # Quotes the column value to help prevent - # {SQL injection attacks}[http://en.wikipedia.org/wiki/SQL_injection]. + # {SQL injection attacks}[https://en.wikipedia.org/wiki/SQL_injection]. def quote(value) value = id_value_for_database(value) if value.is_a?(Base) diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb index 9be26254b2..1926603474 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb @@ -1,65 +1,40 @@ # frozen_string_literal: true +require "active_support/core_ext/hash/compact" + module ActiveRecord module ConnectionAdapters # :nodoc: - # The goal of this module is to move Adapter specific column - # definitions to the Adapter instead of having it in the schema - # dumper itself. This code represents the normal case. - # We can then redefine how certain data types may be handled in the schema dumper on the - # Adapter level by over-writing this code inside the database specific adapters - module ColumnDumper - def column_spec(column) - [schema_type_with_virtual(column), prepare_column_options(column)] - end - - def column_spec_for_primary_key(column) - return {} if default_primary_key?(column) - spec = { id: schema_type(column).inspect } - spec.merge!(prepare_column_options(column).except!(:null)) - spec[:default] ||= "nil" if explicit_primary_key_default?(column) - spec + class SchemaDumper < SchemaDumper # :nodoc: + def self.create(connection, options) + new(connection, options) end - # This can be overridden on an Adapter level basis to support other - # extended datatypes (Example: Adding an array option in the - # PostgreSQL::ColumnDumper) - def prepare_column_options(column) - spec = {} - - if limit = schema_limit(column) - spec[:limit] = limit - end - - if precision = schema_precision(column) - spec[:precision] = precision + private + def column_spec(column) + [schema_type_with_virtual(column), prepare_column_options(column)] end - if scale = schema_scale(column) - spec[:scale] = scale + def column_spec_for_primary_key(column) + return {} if default_primary_key?(column) + spec = { id: schema_type(column).inspect } + spec.merge!(prepare_column_options(column).except!(:null)) + spec[:default] ||= "nil" if explicit_primary_key_default?(column) + spec end - default = schema_default(column) if column.has_default? - spec[:default] = default unless default.nil? - - spec[:null] = "false" unless column.null - - if collation = schema_collation(column) - spec[:collation] = collation + def prepare_column_options(column) + spec = {} + spec[:limit] = schema_limit(column) + spec[:precision] = schema_precision(column) + spec[:scale] = schema_scale(column) + spec[:default] = schema_default(column) + spec[:null] = "false" unless column.null + spec[:collation] = schema_collation(column) + spec[:comment] = column.comment.inspect if column.comment.present? + spec.compact! + spec end - spec[:comment] = column.comment.inspect if column.comment.present? - - spec - end - - # Lists the valid migration options - def migration_keys # :nodoc: - column_options_keys - end - deprecate :migration_keys - - private - def default_primary_key?(column) schema_type(column) == :bigint end @@ -69,7 +44,7 @@ module ActiveRecord end def schema_type_with_virtual(column) - if supports_virtual_columns? && column.virtual? + if @connection.supports_virtual_columns? && column.virtual? :virtual else schema_type(column) @@ -86,7 +61,7 @@ module ActiveRecord def schema_limit(column) limit = column.limit unless column.bigint? - limit.inspect if limit && limit != native_database_types[column.type][:limit] + limit.inspect if limit && limit != @connection.native_database_types[column.type][:limit] end def schema_precision(column) @@ -98,7 +73,8 @@ module ActiveRecord end def schema_default(column) - type = lookup_cast_type_from_column(column) + return unless column.has_default? + type = @connection.lookup_cast_type_from_column(column) default = type.deserialize(column.default) if default.nil? schema_expression(column) 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 3c9b25e411..f36c1f111a 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -1174,6 +1174,10 @@ module ActiveRecord raise NotImplementedError, "#{self.class} does not support changing column comments" end + def create_schema_dumper(options) # :nodoc: + SchemaDumper.create(self, options) + end + private def column_options_keys [:limit, :precision, :scale, :default, :null, :collation, :comment] diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 7645cf7825..47881e3305 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -72,7 +72,6 @@ module ActiveRecord include Quoting, DatabaseStatements, SchemaStatements include DatabaseLimits include QueryCache - include ColumnDumper include Savepoints SIMPLE_INT = /\A\d+\z/ 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 8a9c497918..3683522a0c 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -17,13 +17,8 @@ module ActiveRecord module ConnectionAdapters class AbstractMysqlAdapter < AbstractAdapter include MySQL::Quoting - include MySQL::ColumnDumper include MySQL::SchemaStatements - def update_table_definition(table_name, base) # :nodoc: - MySQL::Table.new(table_name, base) - end - ## # :singleton-method: # By default, the Mysql2Adapter will consider all columns of type <tt>tinyint(1)</tt> @@ -177,8 +172,7 @@ module ActiveRecord #++ def explain(arel, binds = []) - sql, binds = to_sql(arel, binds) - sql = "EXPLAIN #{sql}" + sql = "EXPLAIN #{to_sql(arel, binds)}" start = Time.now result = exec_query(sql, "EXPLAIN", binds) elapsed = Time.now - start @@ -635,6 +629,7 @@ module ActiveRecord ER_LOCK_DEADLOCK = 1213 ER_CANNOT_ADD_FOREIGN = 1215 ER_CANNOT_CREATE_TABLE = 1005 + ER_LOCK_WAIT_TIMEOUT = 1205 def translate_exception(exception, message) case error_number(exception) @@ -658,6 +653,8 @@ module ActiveRecord NotNullViolation.new(message) when ER_LOCK_DEADLOCK Deadlocked.new(message) + when ER_LOCK_WAIT_TIMEOUT + TransactionTimeout.new(message) else super end @@ -761,7 +758,7 @@ module ActiveRecord defaults = [":default", :default].to_set # Make MySQL reject illegal values rather than truncating or blanking them, see - # http://dev.mysql.com/doc/refman/5.7/en/sql-mode.html#sqlmode_strict_all_tables + # https://dev.mysql.com/doc/refman/5.7/en/sql-mode.html#sqlmode_strict_all_tables # If the user has provided another value for sql_mode, don't replace it. if sql_mode = variables.delete("sql_mode") sql_mode = quote(sql_mode) @@ -778,7 +775,7 @@ module ActiveRecord sql_mode_assignment = "@@SESSION.sql_mode = #{sql_mode}, " if sql_mode # NAMES does not have an equals sign, see - # http://dev.mysql.com/doc/refman/5.7/en/set-statement.html#id944430 + # https://dev.mysql.com/doc/refman/5.7/en/set-names.html # (trailing comma because variable_assignments will always have content) if @config[:encoding] encoding = "NAMES #{@config[:encoding]}".dup diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb index 5a35823c72..75377693c6 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb @@ -30,7 +30,7 @@ module ActiveRecord # By default, TIMESTAMP columns are NOT NULL, cannot contain NULL values, # and assigning NULL assigns the current timestamp. To permit a TIMESTAMP # column to contain NULL, explicitly declare it with the NULL attribute. - # See http://dev.mysql.com/doc/refman/5.7/en/timestamp-initialization.html + # See https://dev.mysql.com/doc/refman/5.7/en/timestamp-initialization.html if /\Atimestamp\b/.match?(options[:column].sql_type) && !options[:primary_key] sql << " NULL" unless options[:null] == false || options_include_default?(options) end diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb index fbe3596dda..95eb77aea4 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb @@ -3,25 +3,20 @@ module ActiveRecord module ConnectionAdapters module MySQL - module ColumnDumper # :nodoc: - def prepare_column_options(column) - spec = super - spec[:unsigned] = "true" if column.unsigned? - - if supports_virtual_columns? && column.virtual? - spec[:as] = extract_expression_for_virtual_column(column) - spec[:stored] = "true" if /\b(?:STORED|PERSISTENT)\b/.match?(column.extra) - spec = { type: schema_type(column).inspect }.merge!(spec) - end - - spec - end + class SchemaDumper < ConnectionAdapters::SchemaDumper # :nodoc: + private + def prepare_column_options(column) + spec = super + spec[:unsigned] = "true" if column.unsigned? - def migration_keys - super + [:unsigned] - end + if @connection.supports_virtual_columns? && column.virtual? + spec[:as] = extract_expression_for_virtual_column(column) + spec[:stored] = "true" if /\b(?:STORED|PERSISTENT)\b/.match?(column.extra) + spec = { type: schema_type(column).inspect }.merge!(spec) + end - private + spec + end def default_primary_key?(column) super && column.auto_increment? && !column.unsigned? @@ -49,24 +44,27 @@ module ActiveRecord def schema_collation(column) if column.collation && table_name = column.table_name @table_collation_cache ||= {} - @table_collation_cache[table_name] ||= exec_query("SHOW TABLE STATUS LIKE #{quote(table_name)}", "SCHEMA").first["Collation"] + @table_collation_cache[table_name] ||= + @connection.exec_query("SHOW TABLE STATUS LIKE #{@connection.quote(table_name)}", "SCHEMA").first["Collation"] column.collation.inspect if column.collation != @table_collation_cache[table_name] end end def extract_expression_for_virtual_column(column) - if mariadb? && version < "10.2.5" - create_table_info = create_table_info(column.table_name) - if %r/#{quote_column_name(column.name)} #{Regexp.quote(column.sql_type)}(?: COLLATE \w+)? AS \((?<expression>.+?)\) #{column.extra}/ =~ create_table_info + if @connection.mariadb? && @connection.version < "10.2.5" + create_table_info = @connection.send(:create_table_info, column.table_name) + column_name = @connection.quote_column_name(column.name) + if %r/#{column_name} #{Regexp.quote(column.sql_type)}(?: COLLATE \w+)? AS \((?<expression>.+?)\) #{column.extra}/ =~ create_table_info $~[:expression].inspect end else - scope = quoted_scope(column.table_name) + scope = @connection.send(:quoted_scope, column.table_name) + column_name = @connection.quote(column.name) sql = "SELECT generation_expression FROM information_schema.columns" \ " WHERE table_schema = #{scope[:schema]}" \ " AND table_name = #{scope[:name]}" \ - " AND column_name = #{quote(column.name)}" - query_value(sql, "SCHEMA").inspect + " AND column_name = #{column_name}" + @connection.query_value(sql, "SCHEMA").inspect end end end diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb index 1d87d60ba9..759493e3bd 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb @@ -62,6 +62,14 @@ module ActiveRecord end end + def update_table_definition(table_name, base) + MySQL::Table.new(table_name, base) + end + + def create_schema_dumper(options) + MySQL::SchemaDumper.create(self, options) + end + private CHARSETS_OF_4BYTES_MAXLEN = ["utf8mb4", "utf16", "utf16le", "utf32"] diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb index 0dd4aac463..8db2a645af 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb @@ -5,8 +5,7 @@ module ActiveRecord module PostgreSQL module DatabaseStatements def explain(arel, binds = []) - sql, binds = to_sql(arel, binds) - sql = "EXPLAIN #{sql}" + sql = "EXPLAIN #{to_sql(arel, binds)}" PostgreSQL::ExplainPrettyPrinter.new.pp(exec_query(sql, "EXPLAIN", binds)) end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb index 12c6603081..c0dbb166b7 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb @@ -3,20 +3,13 @@ module ActiveRecord module ConnectionAdapters module PostgreSQL - module ColumnDumper # :nodoc: - # Adds +:array+ option to the default set - def prepare_column_options(column) - spec = super - spec[:array] = "true" if column.array? - spec - end - - # Adds +:array+ as a valid migration key - def migration_keys - super + [:array] - end - + class SchemaDumper < ConnectionAdapters::SchemaDumper # :nodoc: private + def prepare_column_options(column) + spec = super + spec[:array] = "true" if column.array? + spec + end def default_primary_key?(column) schema_type(column) == :bigserial 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 780e642f21..9e2f61e6ce 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -586,6 +586,14 @@ module ActiveRecord [super, *order_columns].join(", ") end + def update_table_definition(table_name, base) # :nodoc: + PostgreSQL::Table.new(table_name, base) + end + + def create_schema_dumper(options) # :nodoc: + PostgreSQL::SchemaDumper.create(self, options) + end + private def schema_creation PostgreSQL::SchemaCreation.new(self) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 3b4439fc46..4d37a292d6 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -76,7 +76,7 @@ module ActiveRecord primary_key: "bigserial primary key", string: { name: "character varying" }, text: { name: "text" }, - integer: { name: "integer" }, + integer: { name: "integer", limit: 4 }, float: { name: "float" }, decimal: { name: "decimal" }, datetime: { name: "timestamp" }, @@ -121,7 +121,6 @@ module ActiveRecord include PostgreSQL::ReferentialIntegrity include PostgreSQL::SchemaStatements include PostgreSQL::DatabaseStatements - include PostgreSQL::ColumnDumper def supports_index_sort_order? true @@ -368,10 +367,6 @@ module ActiveRecord @use_insert_returning end - def update_table_definition(table_name, base) #:nodoc: - PostgreSQL::Table.new(table_name, base) - end - def column_name_for_operation(operation, node) # :nodoc: OPERATION_ALIASES.fetch(operation) { operation.downcase } end @@ -439,9 +434,9 @@ module ActiveRecord end def initialize_type_map(m = type_map) - register_class_with_limit m, "int2", Type::Integer - register_class_with_limit m, "int4", Type::Integer - register_class_with_limit m, "int8", Type::Integer + m.register_type "int2", Type::Integer.new(limit: 2) + m.register_type "int4", Type::Integer.new(limit: 4) + m.register_type "int8", Type::Integer.new(limit: 8) m.register_type "oid", OID::Oid.new m.register_type "float4", Type::Float.new m.alias_type "float8", "float4" @@ -508,17 +503,6 @@ module ActiveRecord load_additional_types end - def extract_limit(sql_type) - case sql_type - when /^bigint/i, /^int8/i - 8 - when /^smallint/i - 2 - else - super - end - end - # Extracts the value from a PostgreSQL column default definition. def extract_value_from_default(default) case default @@ -638,7 +622,7 @@ module ActiveRecord # ActiveRecord::PreparedStatementCacheExpired # # Check here for more details: - # http://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573 + # https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573 CACHED_PLAN_HEURISTIC = "cached plan must not change result type".freeze def is_cached_plan_failure?(e) pgerror = e.cause @@ -703,18 +687,20 @@ module ActiveRecord # Use standard-conforming strings so we don't have to do the E'...' dance. set_standard_conforming_strings + variables = @config.fetch(:variables, {}).stringify_keys + # If using Active Record's time zone support configure the connection to return # TIMESTAMP WITH ZONE types in UTC. - # (SET TIME ZONE does not use an equals sign like other SET variables) - if ActiveRecord::Base.default_timezone == :utc - execute("SET time zone 'UTC'", "SCHEMA") - elsif @local_tz - execute("SET time zone '#{@local_tz}'", "SCHEMA") + unless variables["timezone"] + if ActiveRecord::Base.default_timezone == :utc + variables["timezone"] = "UTC" + elsif @local_tz + variables["timezone"] = @local_tz + end end # SET statements from :variables config hash # https://www.postgresql.org/docs/current/static/sql-set.html - variables = @config[:variables] || {} variables.map do |k, v| if v == ":default" || v == :default # Sets the value to the global or compile default diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb index ab057c73f1..621678ec65 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb @@ -3,9 +3,8 @@ module ActiveRecord module ConnectionAdapters module SQLite3 - module ColumnDumper # :nodoc: + class SchemaDumper < ConnectionAdapters::SchemaDumper # :nodoc: private - def default_primary_key?(column) schema_type(column) == :integer 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 c155e7f1ac..a512702b7b 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb @@ -39,6 +39,14 @@ module ActiveRecord end end + def update_table_definition(table_name, base) + SQLite3::Table.new(table_name, base) + end + + def create_schema_dumper(options) + SQLite3::SchemaDumper.create(self, options) + end + private def schema_creation SQLite3::SchemaCreation.new(self) diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index 8c12cb09bd..d6f357bcc3 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -57,7 +57,6 @@ module ActiveRecord ADAPTER_NAME = "SQLite".freeze include SQLite3::Quoting - include SQLite3::ColumnDumper include SQLite3::SchemaStatements NATIVE_DATABASE_TYPES = { @@ -99,10 +98,6 @@ module ActiveRecord end end - def update_table_definition(table_name, base) # :nodoc: - SQLite3::Table.new(table_name, base) - end - def initialize(connection, logger, connection_options, config) super(connection, logger, config) @@ -203,8 +198,7 @@ module ActiveRecord #++ def explain(arel, binds = []) - sql, binds = to_sql(arel, binds) - sql = "EXPLAIN QUERY PLAN #{sql}" + sql = "EXPLAIN QUERY PLAN #{to_sql(arel, binds)}" SQLite3::ExplainPrettyPrinter.new.pp(exec_query(sql, "EXPLAIN", [])) end @@ -292,7 +286,7 @@ module ActiveRecord rename_table_indexes(table_name, new_name) end - # See: http://www.sqlite.org/lang_altertable.html + # 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 diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index a84e62ab11..945c4eca78 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -529,7 +529,7 @@ module ActiveRecord # # So we can avoid the +method_missing+ hit by explicitly defining +#to_ary+ as +nil+ here. # - # See also http://tenderlovemaking.com/2011/06/28/til-its-ok-to-return-nil-from-to_ary.html + # See also https://tenderlovemaking.com/2011/06/28/til-its-ok-to-return-nil-from-to_ary.html def to_ary nil end diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb index e790760292..9ef3316393 100644 --- a/activerecord/lib/active_record/errors.rb +++ b/activerecord/lib/active_record/errors.rb @@ -334,4 +334,9 @@ module ActiveRecord # +reverse_order+ to automatically reverse. class IrreversibleOrderError < ActiveRecordError end + + # TransactionTimeout will be raised when lock wait timeout expires. + # Wait time value is set by innodb_lock_wait_timeout. + class TransactionTimeout < StatementInvalid + end end diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index ef302fc0a0..4940e122f4 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -1065,6 +1065,10 @@ class ActiveRecord::FixtureSet::RenderContext # :nodoc: def get_binding binding() end + + def binary(path) + %(!!binary "#{Base64.strict_encode64(File.read(path))}") + end end end end diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb index e3deaafeec..0715c11473 100644 --- a/activerecord/lib/active_record/inheritance.rb +++ b/activerecord/lib/active_record/inheritance.rb @@ -32,7 +32,7 @@ module ActiveRecord # for differentiating between them or reloading the right type with find. # # Note, all the attributes for all the cases are kept in the same table. Read more: - # http://www.martinfowler.com/eaaCatalog/singleTableInheritance.html + # https://www.martinfowler.com/eaaCatalog/singleTableInheritance.html # module Inheritance extend ActiveSupport::Concern diff --git a/activerecord/lib/active_record/locking/pessimistic.rb b/activerecord/lib/active_record/locking/pessimistic.rb index e939a24ad5..72bccd4906 100644 --- a/activerecord/lib/active_record/locking/pessimistic.rb +++ b/activerecord/lib/active_record/locking/pessimistic.rb @@ -53,7 +53,7 @@ module ActiveRecord # end # # Database-specific information on row locking: - # MySQL: http://dev.mysql.com/doc/refman/5.7/en/innodb-locking-reads.html + # MySQL: https://dev.mysql.com/doc/refman/5.7/en/innodb-locking-reads.html # 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 diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index d044f9dfe8..e35049bb41 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -221,13 +221,8 @@ module ActiveRecord end def klass_join_scope(table, predicate_builder) # :nodoc: - current_scope = klass.current_scope - - if current_scope && current_scope.empty_scope? - build_scope(table, predicate_builder) - else - klass.default_scoped(build_scope(table, predicate_builder)) - end + relation = build_scope(table, predicate_builder) + klass.scope_for_association(relation) end def constraints @@ -329,6 +324,10 @@ module ActiveRecord def join_pk(_) foreign_key end + + def primary_key(klass) + klass.primary_key || raise(UnknownPrimaryKey.new(klass)) + end end # Base class for AggregateReflection and AssociationReflection. Objects of @@ -512,7 +511,7 @@ module ActiveRecord alias :check_eager_loadable! :check_preloadable! def join_id_for(owner) # :nodoc: - owner[active_record_primary_key] + owner[join_foreign_key] end def through_reflection @@ -658,7 +657,7 @@ module ActiveRecord # from calling +klass+, +reflection+ will already be set to false. def valid_inverse_reflection?(reflection) reflection && - klass.name == reflection.active_record.name && + klass <= reflection.active_record && can_find_inverse_of_automatically?(reflection) end @@ -697,10 +696,6 @@ module ActiveRecord def derive_join_table ModelSchema.derive_join_table_name active_record.table_name, klass.table_name end - - def primary_key(klass) - klass.primary_key || raise(UnknownPrimaryKey.new(klass)) - end end class HasManyReflection < AssociationReflection # :nodoc: @@ -715,6 +710,10 @@ module ActiveRecord Associations::HasManyAssociation end end + + def association_primary_key(klass = nil) + primary_key(klass || self.klass) + end end class HasOneReflection < AssociationReflection # :nodoc: @@ -750,10 +749,6 @@ module ActiveRecord end end - def join_id_for(owner) # :nodoc: - owner[foreign_key] - end - def join_foreign_key foreign_key end @@ -780,7 +775,7 @@ module ActiveRecord # Holds all the metadata about a :through association as it was specified # in the Active Record class. class ThroughReflection < AbstractReflection #:nodoc: - delegate :foreign_key, :foreign_type, :association_foreign_key, + delegate :foreign_key, :foreign_type, :association_foreign_key, :join_id_for, :active_record_primary_key, :type, :get_join_keys, to: :source_reflection def initialize(delegate_reflection) @@ -943,10 +938,6 @@ module ActiveRecord through_reflection.options end - def join_id_for(owner) # :nodoc: - source_reflection.join_id_for(owner) - end - def check_validity! if through_reflection.nil? raise HasManyThroughAssociationNotFoundError.new(active_record.name, self) @@ -1024,10 +1015,6 @@ module ActiveRecord end end - def primary_key(klass) - klass.primary_key || raise(UnknownPrimaryKey.new(klass)) - end - def inverse_name; delegate_reflection.send(:inverse_name); end def derive_class_name @@ -1073,10 +1060,6 @@ module ActiveRecord @reflection.scope end - def table_name - @reflection.table_name - end - def plural_name @reflection.plural_name end @@ -1089,15 +1072,16 @@ module ActiveRecord @reflection.constraints + [source_type_info] end - def source_type_info - type = @previous_reflection.foreign_type - source_type = @previous_reflection.options[:source_type] - lambda { |object| where(type => source_type) } - end - def get_join_keys(association_klass) @reflection.get_join_keys(association_klass) end + + private + def source_type_info + type = @previous_reflection.foreign_type + source_type = @previous_reflection.options[:source_type] + lambda { |object| where(type => source_type) } + end end class RuntimeReflection < PolymorphicReflection # :nodoc: @@ -1112,18 +1096,10 @@ module ActiveRecord @association.klass end - def table_name - klass.table_name - end - def constraints @reflection.constraints end - def source_type_info - @reflection.source_type_info - end - def alias_candidate(name) "#{plural_name}_#{name}_join" end diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index caabad6055..012bc838b1 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -566,13 +566,12 @@ module ActiveRecord relation = self if eager_loading? - find_with_associations { |rel| relation = rel } + find_with_associations { |rel, _| relation = rel } end conn = klass.connection conn.unprepared_statement { - sql, _ = conn.to_sql(relation.arel) - sql + conn.to_sql(relation.arel) } end end @@ -661,7 +660,19 @@ module ActiveRecord def exec_queries(&block) skip_query_cache_if_necessary do - @records = eager_loading? ? find_with_associations.freeze : @klass.find_by_sql(arel, &block).freeze + @records = + if eager_loading? + find_with_associations do |relation, join_dependency| + if ActiveRecord::NullRelation === relation + [] + else + rows = connection.select_all(relation.arel, "SQL") + join_dependency.instantiate(rows, &block) + end.freeze + end + else + klass.find_by_sql(arel, &block).freeze + end preload = preload_values preload += includes_values unless eager_loading? diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb index 141ad176ea..fa19c679cf 100644 --- a/activerecord/lib/active_record/relation/batches.rb +++ b/activerecord/lib/active_record/relation/batches.rb @@ -47,7 +47,12 @@ module ActiveRecord # handle from 10000 and beyond by setting the +:start+ and +:finish+ # option on each worker. # - # # Let's process from record 10_000 on. + # # In worker 1, let's process until 9999 records. + # Person.find_each(finish: 9_999) do |person| + # person.party_all_night! + # end + # + # # In worker 2, let's process from record 10_000 and onwards. # Person.find_each(start: 10_000) do |person| # person.party_all_night! # end diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index d3b5be6bce..42d43224fa 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -259,6 +259,9 @@ module ActiveRecord column = aggregate_column(column_name) select_value = operation_over_aggregate_column(column, operation, distinct) + if operation == "sum" && distinct + select_value.distinct = true + end column_alias = select_value.alias column_alias ||= @klass.connection.column_name_for_operation(operation, select_value) diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 5da9573052..2aed941916 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -372,16 +372,7 @@ module ActiveRecord relation = select aliases.columns relation = apply_join_dependency(relation, join_dependency) - if block_given? - yield relation - else - if ActiveRecord::NullRelation === relation - [] - else - rows = skip_query_cache_if_necessary { connection.select_all(relation.arel, "SQL") } - join_dependency.instantiate(rows, aliases) - end - end + yield relation, join_dependency end def construct_relation_for_exists(relation, conditions) diff --git a/activerecord/lib/active_record/schema.rb b/activerecord/lib/active_record/schema.rb index f166c27fd9..1e121f2a09 100644 --- a/activerecord/lib/active_record/schema.rb +++ b/activerecord/lib/active_record/schema.rb @@ -39,7 +39,7 @@ module ActiveRecord # The +info+ hash is optional, and if given is used to define metadata # about the current schema (currently, only the schema's version): # - # ActiveRecord::Schema.define(version: 20380119000001) do + # ActiveRecord::Schema.define(version: 2038_01_19_000001) do # ... # end def self.define(info = {}, &block) diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb index 27a1c89bd1..d2e1892ae7 100644 --- a/activerecord/lib/active_record/schema_dumper.rb +++ b/activerecord/lib/active_record/schema_dumper.rb @@ -19,7 +19,7 @@ module ActiveRecord class << self def dump(connection = ActiveRecord::Base.connection, stream = STDOUT, config = ActiveRecord::Base) - new(connection, generate_options(config)).dump(stream) + connection.create_schema_dumper(generate_options(config)).dump(stream) stream end @@ -123,7 +123,7 @@ HEADER when String tbl.print ", primary_key: #{pk.inspect}" unless pk == "id" pkcol = columns.detect { |c| c.name == pk } - pkcolspec = @connection.column_spec_for_primary_key(pkcol) + pkcolspec = column_spec_for_primary_key(pkcol) if pkcolspec.present? tbl.print ", #{format_colspec(pkcolspec)}" end @@ -145,7 +145,7 @@ HEADER columns.each do |column| raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'" unless @connection.valid_type?(column.type) next if column.name == pk - type, colspec = @connection.column_spec(column) + type, colspec = column_spec(column) tbl.print " t.#{type} #{column.name.inspect}" tbl.print ", #{format_colspec(colspec)}" if colspec.present? tbl.puts @@ -163,8 +163,6 @@ HEADER stream.puts "# #{e.message}" stream.puts end - - stream end # Keep it for indexing materialized views @@ -243,7 +241,9 @@ HEADER end def remove_prefix_and_suffix(table) - table.gsub(/^(#{@options[:table_name_prefix]})(.+)(#{@options[:table_name_suffix]})$/, "\\2") + prefix = Regexp.escape(@options[:table_name_prefix].to_s) + suffix = Regexp.escape(@options[:table_name_suffix].to_s) + table.sub(/\A#{prefix}(.+)#{suffix}\z/, "\\1") end def ignored?(table_name) diff --git a/activerecord/lib/active_record/scoping/named.rb b/activerecord/lib/active_record/scoping/named.rb index 43cce19c1f..6fa096c1fe 100644 --- a/activerecord/lib/active_record/scoping/named.rb +++ b/activerecord/lib/active_record/scoping/named.rb @@ -31,6 +31,16 @@ module ActiveRecord end end + def scope_for_association(scope = relation) # :nodoc: + current_scope = self.current_scope + + if current_scope && current_scope.empty_scope? + scope + else + default_scoped(scope) + end + end + def default_scoped(scope = relation) # :nodoc: build_default_scope(scope) || scope end diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb index b2f5e39e09..6761f2da25 100644 --- a/activerecord/lib/active_record/transactions.rb +++ b/activerecord/lib/active_record/transactions.rb @@ -170,7 +170,7 @@ module ActiveRecord # writing, the only database that we're aware of that supports true nested # transactions, is MS-SQL. Because of this, Active Record emulates nested # transactions by using savepoints on MySQL and PostgreSQL. See - # http://dev.mysql.com/doc/refman/5.7/en/savepoint.html + # https://dev.mysql.com/doc/refman/5.7/en/savepoint.html # for more information about savepoints. # # === \Callbacks @@ -190,7 +190,7 @@ module ActiveRecord # # === Caveats # - # If you're on MySQL, then do not use Data Definition Language(DDL) operations in nested + # If you're on MySQL, then do not use Data Definition Language (DDL) operations in nested # transactions blocks that are emulated with savepoints. That is, do not execute statements # like 'CREATE TABLE' inside such blocks. This is because MySQL automatically # releases all savepoints upon executing a DDL operation. When +transaction+ diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb index 2677fade18..f4ad58c087 100644 --- a/activerecord/lib/active_record/validations/uniqueness.rb +++ b/activerecord/lib/active_record/validations/uniqueness.rb @@ -8,6 +8,10 @@ module ActiveRecord raise ArgumentError, "#{options[:conditions]} was passed as :conditions but is not callable. " \ "Pass a callable instead: `conditions: -> { where(approved: true) }`" end + unless Array(options[:scope]).all? { |scope| scope.respond_to?(:to_sym) } + raise ArgumentError, "#{options[:scope]} is not supported format for :scope option. " \ + "Pass a symbol or an array of symbols instead: `scope: :user_id`" + end super({ case_sensitive: true }.merge!(options)) @klass = options[:class] end @@ -215,7 +219,7 @@ module ActiveRecord # can catch it and restart the transaction (e.g. by telling the user # that the title already exists, and asking them to re-enter the title). # This technique is also known as - # {optimistic concurrency control}[http://en.wikipedia.org/wiki/Optimistic_concurrency_control]. + # {optimistic concurrency control}[https://en.wikipedia.org/wiki/Optimistic_concurrency_control]. # # The bundled ActiveRecord::ConnectionAdapters distinguish unique index # constraint errors from other types of database errors by throwing an diff --git a/activerecord/lib/rails/generators/active_record/application_record/application_record_generator.rb b/activerecord/lib/rails/generators/active_record/application_record/application_record_generator.rb index d18330f5b2..35d5664400 100644 --- a/activerecord/lib/rails/generators/active_record/application_record/application_record_generator.rb +++ b/activerecord/lib/rails/generators/active_record/application_record/application_record_generator.rb @@ -15,11 +15,12 @@ module ActiveRecord private def application_record_file_name - @application_record_file_name ||= if namespaced? - "app/models/#{namespaced_path}/application_record.rb" - else - "app/models/application_record.rb" - end + @application_record_file_name ||= + if namespaced? + "app/models/#{namespaced_path}/application_record.rb" + else + "app/models/application_record.rb" + end end end end |