diff options
Diffstat (limited to 'activerecord/lib')
20 files changed, 134 insertions, 99 deletions
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb index 08b7c9274d..811fbfd927 100644 --- a/activerecord/lib/active_record/associations/collection_proxy.rb +++ b/activerecord/lib/active_record/associations/collection_proxy.rb @@ -1125,7 +1125,7 @@ module ActiveRecord SpawnMethods, ].flat_map { |klass| klass.public_instance_methods(false) - } - self.public_instance_methods(false) - [:select] + [:scoping] + } - self.public_instance_methods(false) - [:select] + [:scoping, :values] delegate(*delegate_methods, to: :scope) diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index 1581490f16..1e92ee3b96 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -261,21 +261,14 @@ module ActiveRecord def respond_to?(name, include_private = false) return false unless super - case name - when :to_partial_path - name = "to_partial_path" - when :to_model - name = "to_model" - else - name = name.to_s - end - # If the result is true then check for the select case. # For queries selecting a subset of columns, return false for unselected columns. # We check defined?(@attributes) not to issue warnings if called on objects that # have been allocated but not yet initialized. - if defined?(@attributes) && self.class.column_names.include?(name) - return has_attribute?(name) + if defined?(@attributes) + if name = self.class.symbol_column_to_string(name.to_sym) + return has_attribute?(name) + end end true diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb index ff0e863529..6e1275e990 100644 --- a/activerecord/lib/active_record/attribute_methods/read.rb +++ b/activerecord/lib/active_record/attribute_methods/read.rb @@ -29,10 +29,9 @@ module ActiveRecord # it has been typecast (for example, "2004-12-12" in a date column is cast # to a date object, like Date.new(2004, 12, 12)). def read_attribute(attr_name, &block) - name = if self.class.attribute_alias?(attr_name) - self.class.attribute_alias(attr_name).to_s - else - attr_name.to_s + name = attr_name.to_s + if self.class.attribute_alias?(name) + name = self.class.attribute_alias(name) end primary_key = self.class.primary_key diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb index 7f246eed46..455e67e19b 100644 --- a/activerecord/lib/active_record/attribute_methods/write.rb +++ b/activerecord/lib/active_record/attribute_methods/write.rb @@ -33,10 +33,9 @@ module ActiveRecord # specified +value+. Empty strings for Integer and Float columns are # turned into +nil+. def write_attribute(attr_name, value) - name = if self.class.attribute_alias?(attr_name) - self.class.attribute_alias(attr_name).to_s - else - attr_name.to_s + name = attr_name.to_s + if self.class.attribute_alias?(name) + name = self.class.attribute_alias(name) end primary_key = self.class.primary_key diff --git a/activerecord/lib/active_record/collection_cache_key.rb b/activerecord/lib/active_record/collection_cache_key.rb index 61581b0451..4b6db8a96c 100644 --- a/activerecord/lib/active_record/collection_cache_key.rb +++ b/activerecord/lib/active_record/collection_cache_key.rb @@ -20,9 +20,9 @@ module ActiveRecord select_values = "COUNT(*) AS #{connection.quote_column_name("size")}, MAX(%s) AS timestamp" if collection.has_limit_or_offset? - query = collection.select(column) + query = collection.select("#{column} AS collection_cache_key_timestamp") subquery_alias = "subquery_for_cache_key" - subquery_column = "#{subquery_alias}.#{timestamp_column}" + subquery_column = "#{subquery_alias}.collection_cache_key_timestamp" subquery = query.arel.as(subquery_alias) arel = Arel::SelectManager.new(subquery).project(select_values % subquery_column) else 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 015204c056..70607fda5a 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -348,8 +348,8 @@ module ActiveRecord # # create_table :taggings do |t| # t.references :tag, index: { name: 'index_taggings_on_tag_id' } - # t.references :tagger, polymorphic: true, index: true - # t.references :taggable, polymorphic: { default: 'Photo' } + # t.references :tagger, polymorphic: true + # t.references :taggable, polymorphic: { default: 'Photo' }, index: false # end def column(name, type, options = {}) name = name.to_s 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 723d8c318d..8a7020a799 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -846,17 +846,17 @@ module ActiveRecord # [<tt>:null</tt>] # Whether the column allows nulls. Defaults to true. # - # ====== Create a user_id bigint column + # ====== Create a user_id bigint column without a index # - # add_reference(:products, :user) + # add_reference(:products, :user, index: false) # # ====== Create a user_id string column # # add_reference(:products, :user, type: :string) # - # ====== Create supplier_id, supplier_type columns and appropriate index + # ====== Create supplier_id, supplier_type columns # - # add_reference(:products, :supplier, polymorphic: true, index: true) + # add_reference(:products, :supplier, polymorphic: true) # # ====== Create a supplier_id column with a unique index # @@ -884,7 +884,7 @@ module ActiveRecord # # ====== Remove the reference # - # remove_reference(:products, :user, index: true) + # remove_reference(:products, :user, index: false) # # ====== Remove polymorphic reference # @@ -892,7 +892,7 @@ module ActiveRecord # # ====== Remove the reference with a foreign key # - # remove_reference(:products, :user, index: true, foreign_key: true) + # remove_reference(:products, :user, foreign_key: true) # def remove_reference(table_name, ref_name, foreign_key: false, polymorphic: false, **options) if foreign_key diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index fa10f18cb7..0fe868478c 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -125,6 +125,8 @@ module ActiveRecord @advisory_locks_enabled = self.class.type_cast_config_to_boolean( config.fetch(:advisory_locks, true) ) + + check_version end def replica? @@ -502,6 +504,9 @@ module ActiveRecord end private + def check_version + end + def type_map @type_map ||= Type::TypeMap.new.tap do |mapping| initialize_type_map(mapping) 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 d40f38fb77..13c799b64a 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -54,10 +54,6 @@ module ActiveRecord super(connection, logger, config) @statements = StatementPool.new(self.class.type_cast_config_to_integer(config[:statement_limit])) - - if version < "5.5.8" - raise "Your version of MySQL (#{version_string}) is too old. Active Record supports MySQL >= 5.5.8." - end end def version #:nodoc: @@ -76,6 +72,10 @@ module ActiveRecord !mariadb? && version >= "8.0.1" end + def supports_expression_index? + !mariadb? && version >= "8.0.13" + end + def supports_transaction_isolation? true end @@ -535,6 +535,12 @@ module ActiveRecord end private + def check_version + if version < "5.5.8" + raise "Your version of MySQL (#{version_string}) is too old. Active Record supports MySQL >= 5.5.8." + end + end + def combine_multi_statements(total_sql) total_sql.each_with_object([]) do |sql, total_sql_chunks| previous_packet = total_sql_chunks.last @@ -622,6 +628,8 @@ module ActiveRecord # See https://dev.mysql.com/doc/refman/5.7/en/error-messages-server.html ER_DUP_ENTRY = 1062 ER_NOT_NULL_VIOLATION = 1048 + ER_NO_REFERENCED_ROW = 1216 + ER_ROW_IS_REFERENCED = 1217 ER_DO_NOT_HAVE_DEFAULT = 1364 ER_ROW_IS_REFERENCED_2 = 1451 ER_NO_REFERENCED_ROW_2 = 1452 @@ -638,7 +646,7 @@ module ActiveRecord case error_number(exception) when ER_DUP_ENTRY RecordNotUnique.new(message) - when ER_ROW_IS_REFERENCED_2, ER_NO_REFERENCED_ROW_2 + when ER_NO_REFERENCED_ROW, ER_ROW_IS_REFERENCED, ER_ROW_IS_REFERENCED_2, ER_NO_REFERENCED_ROW_2 InvalidForeignKey.new(message) when ER_CANNOT_ADD_FOREIGN mismatched_foreign_key(message) diff --git a/activerecord/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb b/activerecord/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb index 3dcb916d99..f158946c6d 100644 --- a/activerecord/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +++ b/activerecord/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb @@ -10,8 +10,17 @@ module ActiveRecord super end - def visit_Arel_Nodes_In(*) + def visit_Arel_Nodes_In(o, collector) @preparable = false + + if Array === o.right && !o.right.empty? + o.right.delete_if do |bind| + if Arel::Nodes::BindParam === bind && Relation::QueryAttribute === bind.value + !bind.value.boundable? + end + end + end + super 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 e167c01802..4894fd1c08 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb @@ -35,13 +35,39 @@ module ActiveRecord ] end - indexes.last[-2] << row[:Column_name] - indexes.last[-1][:lengths][row[:Column_name]] = row[:Sub_part].to_i if row[:Sub_part] - indexes.last[-1][:orders].merge!(row[:Column_name] => :desc) if row[:Collation] == "D" + if row[:Expression] + expression = row[:Expression] + expression = +"(#{expression})" unless expression.start_with?("(") + indexes.last[-2] << expression + indexes.last[-1][:expressions] ||= {} + indexes.last[-1][:expressions][expression] = expression + indexes.last[-1][:orders][expression] = :desc if row[:Collation] == "D" + else + indexes.last[-2] << row[:Column_name] + indexes.last[-1][:lengths][row[:Column_name]] = row[:Sub_part].to_i if row[:Sub_part] + indexes.last[-1][:orders][row[:Column_name]] = :desc if row[:Collation] == "D" + end end end - indexes.map { |index| IndexDefinition.new(*index) } + indexes.map do |index| + options = index.last + + if expressions = options.delete(:expressions) + orders = options.delete(:orders) + lengths = options.delete(:lengths) + + columns = index[-2].map { |name| + [ name.to_sym, expressions[name] || +quote_column_name(name) ] + }.to_h + + index[-2] = add_options_for_index_columns( + columns, order: orders, length: lengths + ).values.join(", ") + end + + IndexDefinition.new(*index) + end end def remove_column(table_name, column_name, type = nil, options = {}) @@ -80,10 +106,13 @@ module ActiveRecord def new_column_from_field(table_name, field) type_metadata = fetch_type_metadata(field[:Type], field[:Extra]) - if type_metadata.type == :datetime && /\ACURRENT_TIMESTAMP(?:\([0-6]?\))?\z/i.match?(field[:Default]) - default, default_function = nil, field[:Default] - else - default, default_function = field[:Default], nil + default, default_function = field[:Default], nil + + if type_metadata.type == :datetime && /\ACURRENT_TIMESTAMP(?:\([0-6]?\))?\z/i.match?(default) + default, default_function = nil, default + elsif type_metadata.extra == "DEFAULT_GENERATED" + default = +"(#{default})" unless default.start_with?("(") + default, default_function = nil, default end MySQL::Column.new( diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index bc6eb11572..a11a786ec7 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -5,7 +5,7 @@ gem "pg", ">= 0.18", "< 2.0" require "pg" # Use async_exec instead of exec_params on pg versions before 1.1 -class ::PG::Connection +class ::PG::Connection # :nodoc: unless self.public_method_defined?(:async_exec_params) remove_method :exec_params alias exec_params async_exec @@ -43,9 +43,14 @@ module ActiveRecord valid_conn_param_keys = PG::Connection.conndefaults_hash.keys + [:requiressl] conn_params.slice!(*valid_conn_param_keys) - # The postgres drivers don't allow the creation of an unconnected PG::Connection object, - # so just pass a nil connection object for the time being. - ConnectionAdapters::PostgreSQLAdapter.new(nil, logger, conn_params, config) + conn = PG.connect(conn_params) + ConnectionAdapters::PostgreSQLAdapter.new(conn, logger, conn_params, config) + rescue ::PG::Error => error + if error.message.include?("does not exist") + raise ActiveRecord::NoDatabaseError + else + raise + end end end @@ -220,15 +225,11 @@ module ActiveRecord @local_tz = nil @max_identifier_length = nil - connect + configure_connection add_pg_encoders @statements = StatementPool.new @connection, self.class.type_cast_config_to_integer(config[:statement_limit]) - if postgresql_version < 90100 - raise "Your version of PostgreSQL (#{postgresql_version}) is too old. Active Record supports PostgreSQL >= 9.1." - end - add_pg_decoders @type_map = Type::HashLookupTypeMap.new @@ -410,6 +411,12 @@ module ActiveRecord end private + def check_version + if postgresql_version < 90100 + raise "Your version of PostgreSQL (#{postgresql_version}) is too old. Active Record supports PostgreSQL >= 9.1." + end + end + # See https://www.postgresql.org/docs/current/static/errcodes-appendix.html VALUE_LIMIT_VIOLATION = "22001" NUMERIC_VALUE_OUT_OF_RANGE = "22003" @@ -699,12 +706,6 @@ module ActiveRecord def connect @connection = PG.connect(@connection_parameters) configure_connection - rescue ::PG::Error => error - if error.message.include?("does not exist") - raise ActiveRecord::NoDatabaseError - else - raise - end end # Configures the encoding, verbosity, schema search path, and time zone of the connection. diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index 81882f6cc1..e0355a316b 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -105,11 +105,6 @@ module ActiveRecord @active = true @statements = StatementPool.new(self.class.type_cast_config_to_integer(config[:statement_limit])) - - if sqlite_version < "3.8.0" - raise "Your version of SQLite (#{sqlite_version}) is too old. Active Record supports SQLite >= 3.8." - end - configure_connection end @@ -401,6 +396,12 @@ module ActiveRecord end private + def check_version + if sqlite_version < "3.8.0" + raise "Your version of SQLite (#{sqlite_version}) is too old. Active Record supports SQLite >= 3.8." + end + end + def initialize_type_map(m = type_map) super register_class_with_limit m, %r(int)i, SQLite3Integer diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index 9780258302..da3e2549a2 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -340,34 +340,22 @@ module ActiveRecord # post = Post.allocate # post.init_with(coder) # post.title # => 'hello world' - def init_with(coder) + def init_with(coder, &block) coder = LegacyYamlAdapter.convert(self.class, coder) - @attributes = self.class.yaml_encoder.decode(coder) - - init_internals - - @new_record = coder["new_record"] - - self.class.define_attribute_methods - - yield self if block_given? - - _run_find_callbacks - _run_initialize_callbacks - - self + attributes = self.class.yaml_encoder.decode(coder) + init_with_attributes(attributes, coder["new_record"], &block) end ## - # Initializer used for instantiating objects that have been read from the - # database. +attributes+ should be an attributes object, and unlike the + # Initialize an empty model object from +attributes+. + # +attributes+ should be an attributes object, and unlike the # `initialize` method, no assignment calls are made per attribute. # # :nodoc: - def init_from_db(attributes) + def init_with_attributes(attributes, new_record = false) init_internals - @new_record = false + @new_record = new_record @attributes = attributes self.class.define_attribute_methods diff --git a/activerecord/lib/active_record/database_configurations.rb b/activerecord/lib/active_record/database_configurations.rb index fa1589511e..30cb0a27e7 100644 --- a/activerecord/lib/active_record/database_configurations.rb +++ b/activerecord/lib/active_record/database_configurations.rb @@ -29,7 +29,7 @@ module ActiveRecord # configs for all environments. # <tt>spec_name:</tt> The specification name (ie primary, animals, etc.). Defaults # to +nil+. - # <tt>include_replicas:</tt> Determines whether to include replicas in the + # <tt>include_replicas:</tt> Determines whether to include replicas in # the returned list. Most of the time we're only iterating over the write # connection (i.e. migrations don't need to run for the write and read connection). # Defaults to +false+. diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb index 5a4a1fb969..c50a420432 100644 --- a/activerecord/lib/active_record/model_schema.rb +++ b/activerecord/lib/active_record/model_schema.rb @@ -388,6 +388,11 @@ module ActiveRecord @column_names ||= columns.map(&:name) end + def symbol_column_to_string(name_symbol) # :nodoc: + @symbol_column_to_string_name_hash ||= column_names.index_by(&:to_sym) + @symbol_column_to_string_name_hash[name_symbol] + end + # Returns an array of column objects where the primary id, all columns ending in "_id" or "_count", # and columns used for single table inheritance have been removed. def content_columns @@ -477,6 +482,7 @@ module ActiveRecord def reload_schema_from_cache @arel_table = nil @column_names = nil + @symbol_column_to_string_name_hash = nil @attribute_types = nil @content_columns = nil @default_attributes = nil diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb index 50767ee93f..8b9098df6c 100644 --- a/activerecord/lib/active_record/nested_attributes.rb +++ b/activerecord/lib/active_record/nested_attributes.rb @@ -426,7 +426,7 @@ module ActiveRecord existing_record.assign_attributes(assignable_attributes) association(association_name).initialize_attributes(existing_record) else - method = "build_#{association_name}" + method = :"build_#{association_name}" if respond_to?(method) send(method, assignable_attributes) else diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 8404119631..45ceb1e3ad 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -209,7 +209,7 @@ module ActiveRecord # new instance of the class. Accepts only keys as strings. def instantiate_instance_of(klass, attributes, column_types = {}, &block) attributes = klass.attributes_builder.build_from_database(attributes, column_types) - klass.allocate.init_from_db(attributes, &block) + klass.allocate.init_with_attributes(attributes, &block) end # Called by +instantiate+ to decide which class to use for a new diff --git a/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb index fadb3c420d..ee2ece1560 100644 --- a/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb +++ b/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb @@ -22,9 +22,8 @@ module ActiveRecord when 1 then predicate_builder.build(attribute, values.first) else values.map! do |v| - bind = predicate_builder.build_bind_attribute(attribute.name, v) - bind if bind.value.boundable? - end.compact! + predicate_builder.build_bind_attribute(attribute.name, v) + end values.empty? ? NullPredicate : attribute.in(values) end diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 5170d38fa8..eb80aab701 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -909,21 +909,18 @@ module ActiveRecord @arel ||= build_arel(aliases) end - # Returns a relation value with a given name - def get_value(name) # :nodoc: - @values.fetch(name, DEFAULT_VALUES[name]) - end - - protected + private + # Returns a relation value with a given name + def get_value(name) + @values.fetch(name, DEFAULT_VALUES[name]) + end # Sets the relation value with the given name - def set_value(name, value) # :nodoc: + def set_value(name, value) assert_mutability! @values[name] = value end - private - def assert_mutability! raise ImmutableRelation if @loaded raise ImmutableRelation if defined?(@arel) && @arel @@ -1188,8 +1185,9 @@ module ActiveRecord STRUCTURAL_OR_METHODS = Relation::VALUE_METHODS - [:extending, :where, :having, :unscope, :references] def structurally_incompatible_values_for_or(other) + values = other.values STRUCTURAL_OR_METHODS.reject do |method| - get_value(method) == other.get_value(method) + get_value(method) == values.fetch(method, DEFAULT_VALUES[method]) end end |