diff options
Diffstat (limited to 'activerecord')
46 files changed, 432 insertions, 158 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index a7c2680015..bfdc978005 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,6 +1,47 @@ -* ActiveRecord::Base.configurations now returns an object. +* Allow subclasses to redefine autosave callbacks for associated records. - ActiveRecord::Base.configurations used to return a hash, but this + Fixes #33305. + + *Andrey Subbota* + +* Bump minimum MySQL version to 5.5.8. + + *Yasuo Honda* + +* Use MySQL utf8mb4 character set by default. + + `utf8mb4` character set with 4-Byte encoding supports supplementary characters including emoji. + The previous default 3-Byte encoding character set `utf8` is not enough to support them. + + *Yasuo Honda* + +* Fix duplicated record creation when using nested attributes with `create_with`. + + *Darwin Wu* + +* Configuration item `config.filter_parameters` could also filter out + sensitive values of database columns when call `#inspect`. + We also added `ActiveRecord::Base::filter_attributes`/`=` in order to + specify sensitive attributes to specific model. + + ``` + Rails.application.config.filter_parameters += [:credit_card_number] + Account.last.inspect # => #<Account id: 123, name: "DHH", credit_card_number: [FILTERED] ...> + SecureAccount.filter_attributes += [:name] + SecureAccount.last.inspect # => #<SecureAccount id: 42, name: [FILTERED], credit_card_number: [FILTERED] ...> + ``` + + *Zhang Kang* + +* Deprecate `column_name_length`, `table_name_length`, `columns_per_table`, + `indexes_per_table`, `columns_per_multicolumn_index`, `sql_query_length`, + and `joins_per_query` methods in `DatabaseLimits`. + + *Ryuta Kamizono* + +* `ActiveRecord::Base.configurations` now returns an object. + + `ActiveRecord::Base.configurations` used to return a hash, but this is an inflexible data model. In order to improve multiple-database handling in Rails, we've changed this to return an object. Some methods are provided to make the object behave hash-like in order to ease the @@ -9,7 +50,7 @@ a deprecation warning if used, however calling `ActiveRecord::Base.configurations` will use the new version internally and externally. - For example, the following database.yml... + For example, the following `database.yml`: ``` development: @@ -34,13 +75,14 @@ Iterating over the database configurations has also changed. Instead of calling hash methods on the `configurations` hash directly, a new method `configs_for` has - been provided that allows you to select the correct configuration. `env_name` is a required - argument, `spec_name` is optional as well as passing a block. These return an array of - database config objects for the requested environment and specification name respectively. + been provided that allows you to select the correct configuration. `env_name`, and + `spec_name` arguments are optional. For example these return an array of + database config objects for the requested environment and a single database config object + will be returned for the requested environment and specification name respectively. ``` - ActiveRecord::Base.configurations.configs_for("development") - ActiveRecord::Base.configurations.configs_for("development", "primary") + ActiveRecord::Base.configurations.configs_for(env_name: "development") + ActiveRecord::Base.configurations.configs_for(env_name: "development", spec_name: "primary") ``` *Eileen M. Uchitelle*, *Aaron Patterson* @@ -133,8 +175,8 @@ *Eddie Lebow* -* Add ActiveRecord::Base.create_or_find_by/! to deal with the SELECT/INSERT race condition in - ActiveRecord::Base.find_or_create_by/! by leaning on unique constraints in the database. +* Add `ActiveRecord::Base.create_or_find_by`/`!` to deal with the SELECT/INSERT race condition in + `ActiveRecord::Base.find_or_create_by`/`!` by leaning on unique constraints in the database. *DHH* diff --git a/activerecord/Rakefile b/activerecord/Rakefile index 170c95b827..fae56a51bb 100644 --- a/activerecord/Rakefile +++ b/activerecord/Rakefile @@ -94,8 +94,8 @@ namespace :db do desc "Build the MySQL test databases" task :build do config = ARTest.config["connections"]["mysql2"] - %x( mysql --user=#{config["arunit"]["username"]} --password=#{config["arunit"]["password"]} -e "create DATABASE #{config["arunit"]["database"]} DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_unicode_ci ") - %x( mysql --user=#{config["arunit2"]["username"]} --password=#{config["arunit2"]["password"]} -e "create DATABASE #{config["arunit2"]["database"]} DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_unicode_ci ") + %x( mysql --user=#{config["arunit"]["username"]} --password=#{config["arunit"]["password"]} -e "create DATABASE #{config["arunit"]["database"]} DEFAULT CHARACTER SET utf8mb4" ) + %x( mysql --user=#{config["arunit2"]["username"]} --password=#{config["arunit2"]["password"]} -e "create DATABASE #{config["arunit2"]["database"]} DEFAULT CHARACTER SET utf8mb4" ) end desc "Drop the MySQL test databases" diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb index 783a8366ce..d77d76cb1e 100644 --- a/activerecord/lib/active_record/autosave_association.rb +++ b/activerecord/lib/active_record/autosave_association.rb @@ -149,7 +149,7 @@ module ActiveRecord private def define_non_cyclic_method(name, &block) - return if method_defined?(name) + return if instance_methods(false).include?(name) define_method(name) do |*args| result = true; @_already_called ||= {} # Loop prevention for validation of associations diff --git a/activerecord/lib/active_record/collection_cache_key.rb b/activerecord/lib/active_record/collection_cache_key.rb index dfba78614e..61581b0451 100644 --- a/activerecord/lib/active_record/collection_cache_key.rb +++ b/activerecord/lib/active_record/collection_cache_key.rb @@ -16,7 +16,7 @@ module ActiveRecord collection = collection.send(:apply_join_dependency) end column_type = type_for_attribute(timestamp_column) - column = connection.column_name_from_arel_node(collection.arel_attribute(timestamp_column)) + column = connection.visitor.compile(collection.arel_attribute(timestamp_column)) select_values = "COUNT(*) AS #{connection.quote_column_name("size")}, MAX(%s) AS timestamp" if collection.has_limit_or_offset? diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb index 7a9e7add24..ad148efcfe 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require "active_support/deprecation" + module ActiveRecord module ConnectionAdapters # :nodoc: module DatabaseLimits @@ -12,11 +14,13 @@ module ActiveRecord def column_name_length 64 end + deprecate :column_name_length # Returns the maximum length of a table name. def table_name_length 64 end + deprecate :table_name_length # Returns the maximum allowed length for an index name. This # limit is enforced by \Rails and is less than or equal to @@ -36,16 +40,19 @@ module ActiveRecord def columns_per_table 1024 end + deprecate :columns_per_table # Returns the maximum number of indexes per table. def indexes_per_table 16 end + deprecate :indexes_per_table # Returns the maximum number of columns in a multicolumn index. def columns_per_multicolumn_index 16 end + deprecate :columns_per_multicolumn_index # Returns the maximum number of elements in an IN (x,y,z) clause. # +nil+ means no limit. @@ -57,11 +64,13 @@ module ActiveRecord def sql_query_length 1048575 end + deprecate :sql_query_length # Returns maximum number of joins in a single query. def joins_per_query 256 end + deprecate :joins_per_query end end end 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 1d36c3c8b1..fdc9ffa688 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -20,7 +20,7 @@ module ActiveRecord raise "Passing bind parameters with an arel AST is forbidden. " \ "The values must be stored on the AST directly" end - sql, binds = visitor.accept(arel_or_sql_string.ast, collector).value + sql, binds = visitor.compile(arel_or_sql_string.ast, collector) [sql.freeze, binds || []] else [arel_or_sql_string.dup.freeze, binds] @@ -32,11 +32,11 @@ module ActiveRecord # can be used to query the database repeatedly. def cacheable_query(klass, arel) # :nodoc: if prepared_statements - sql, binds = visitor.accept(arel.ast, collector).value + sql, binds = visitor.compile(arel.ast, collector) query = klass.query(sql) else collector = PartialQueryCollector.new - parts, binds = visitor.accept(arel.ast, collector).value + parts, binds = visitor.compile(arel.ast, collector) query = klass.partial_query(parts) end [query, binds] 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 84ea7a0c33..4a6e915b66 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -602,6 +602,7 @@ module ActiveRecord # The +type+ and +options+ parameters will be ignored if present. It can be helpful # to provide these in a migration's +change+ method so it can be reverted. # In that case, +type+ and +options+ will be used by #add_column. + # Indexes on the column are automatically removed. def remove_column(table_name, column_name, type = nil, options = {}) execute "ALTER TABLE #{quote_table_name(table_name)} #{remove_column_for_alter(table_name, column_name, type, options)}" end diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index a62651daff..42ed9ce82d 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -360,6 +360,10 @@ module ActiveRecord def enable_extension(name) end + def advisory_locks_enabled? # :nodoc: + supports_advisory_locks? && @advisory_locks_enabled + end + # This is meant to be implemented by the adapters that support advisory # locks # @@ -490,11 +494,7 @@ module ActiveRecord end def column_name_for_operation(operation, node) # :nodoc: - column_name_from_arel_node(node) - end - - def column_name_from_arel_node(node) # :nodoc: - visitor.accept(node, Arel::Collectors::SQLString.new).value + visitor.compile(node) end def default_index_type?(index) # :nodoc: 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 ad045f85ef..5d4fc8324f 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -53,8 +53,8 @@ module ActiveRecord @statements = StatementPool.new(self.class.type_cast_config_to_integer(config[:statement_limit])) - if version < "5.1.10" - raise "Your version of MySQL (#{version_string}) is too old. Active Record supports MySQL >= 5.1.10." + if version < "5.5.8" + raise "Your version of MySQL (#{version_string}) is too old. Active Record supports MySQL >= 5.5.8." end end @@ -111,7 +111,15 @@ module ActiveRecord end def supports_advisory_locks? - @advisory_locks_enabled + true + end + + def supports_longer_index_key_prefix? + if mariadb? + version >= "10.2.2" + else + version >= "5.7.9" + end end def get_advisory_lock(lock_name, timeout = 0) # :nodoc: @@ -241,7 +249,7 @@ module ActiveRecord end # Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>. - # Charset defaults to utf8. + # Charset defaults to utf8mb4. # # Example: # create_database 'charset_test', charset: 'latin1', collation: 'latin1_bin' @@ -250,8 +258,12 @@ module ActiveRecord def create_database(name, options = {}) if options[:collation] execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT COLLATE #{quote_table_name(options[:collation])}" + elsif options[:charset] + execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET #{quote_table_name(options[:charset])}" + elsif supports_longer_index_key_prefix? + execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET `utf8mb4`" else - execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET #{quote_table_name(options[:charset] || 'utf8')}" + raise "Configure a supported :charset and ensure innodb_large_prefix is enabled to support indexes on varchar(255) string columns." end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 30e651ee63..3ee344a249 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -298,7 +298,7 @@ module ActiveRecord end def supports_advisory_locks? - @advisory_locks_enabled + true end def supports_explain? diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index 9ec6ba14fd..392602bc0f 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -3,11 +3,14 @@ require "active_support/core_ext/hash/indifferent_access" require "active_support/core_ext/string/filters" require "concurrent/map" +require "set" module ActiveRecord module Core extend ActiveSupport::Concern + FILTERED = "[FILTERED]" # :nodoc: + included do ## # :singleton-method: @@ -123,6 +126,8 @@ module ActiveRecord class_attribute :default_connection_handler, instance_writer: false + self.filter_attributes = [] + def self.connection_handler ActiveRecord::RuntimeRegistry.connection_handler || default_connection_handler end @@ -134,7 +139,7 @@ module ActiveRecord self.default_connection_handler = ConnectionAdapters::ConnectionHandler.new end - module ClassMethods # :nodoc: + module ClassMethods def initialize_find_by_cache # :nodoc: @find_by_statement_cache = { true => Concurrent::Map.new, false => Concurrent::Map.new } end @@ -211,7 +216,7 @@ module ActiveRecord generated_association_methods end - def generated_association_methods + def generated_association_methods # :nodoc: @generated_association_methods ||= begin mod = const_set(:GeneratedAssociationMethods, Module.new) private_constant :GeneratedAssociationMethods @@ -221,8 +226,22 @@ module ActiveRecord end end + # Returns columns which shouldn't be exposed while calling +#inspect+. + def filter_attributes + if defined?(@filter_attributes) + @filter_attributes + else + superclass.filter_attributes + end + end + + # Specifies columns which shouldn't be exposed while calling +#inspect+. + def filter_attributes=(attributes_names) + @filter_attributes = attributes_names.map(&:to_s).to_set + end + # Returns a string like 'Post(id:integer, title:string, body:text)' - def inspect + def inspect # :nodoc: if self == Base super elsif abstract_class? @@ -238,7 +257,7 @@ module ActiveRecord end # Overwrite the default class equality method to provide support for decorated models. - def ===(object) + def ===(object) # :nodoc: object.is_a?(self) end @@ -492,7 +511,11 @@ module ActiveRecord inspection = if defined?(@attributes) && @attributes self.class.attribute_names.collect do |name| if has_attribute?(name) - "#{name}: #{attribute_for_inspect(name)}" + if filter_attribute?(name) + "#{name}: #{ActiveRecord::Core::FILTERED}" + else + "#{name}: #{attribute_for_inspect(name)}" + end end end.compact.join(", ") else @@ -510,13 +533,16 @@ module ActiveRecord if defined?(@attributes) && @attributes column_names = self.class.column_names.select { |name| has_attribute?(name) || new_record? } pp.seplist(column_names, proc { pp.text "," }) do |column_name| - column_value = read_attribute(column_name) pp.breakable " " pp.group(1) do pp.text column_name pp.text ":" pp.breakable - pp.pp column_value + if filter_attribute?(column_name) + pp.text ActiveRecord::Core::FILTERED + else + pp.pp read_attribute(column_name) + end end end else @@ -567,5 +593,9 @@ module ActiveRecord def custom_inspect_method_defined? self.class.instance_method(:inspect).owner != ActiveRecord::Base.instance_method(:inspect).owner end + + def filter_attribute?(attribute_name) + self.class.filter_attributes.include?(attribute_name) && !read_attribute(attribute_name).nil? + end end end diff --git a/activerecord/lib/active_record/database_configurations.rb b/activerecord/lib/active_record/database_configurations.rb index a94f46d07f..9ff63c6e10 100644 --- a/activerecord/lib/active_record/database_configurations.rb +++ b/activerecord/lib/active_record/database_configurations.rb @@ -10,6 +10,7 @@ module ActiveRecord # application's database configuration hash or url string. class DatabaseConfigurations attr_reader :configurations + delegate :any?, to: :configurations def initialize(configurations = {}) @configurations = build_configs(configurations) @@ -18,8 +19,8 @@ module ActiveRecord # Collects the configs for the environment and optionally the specification # name passed in. To include replica configurations pass `include_replicas: true`. # - # If a spec name is provided a single DatabaseConfiguration object will be - # returned, otherwise an array of DatabaseConfiguration objects will be + # If a spec name is provided a single DatabaseConfig object will be + # returned, otherwise an array of DatabaseConfig objects will be # returned that corresponds with the environment and type requested. # # Options: @@ -53,7 +54,7 @@ module ActiveRecord # Returns the config hash that corresponds with the environment # # If the application has multiple databases `default_hash` will - # the first config hash for the environment. + # return the first config hash for the environment. # # { database: "my_db", adapter: "mysql2" } def default_hash(env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call.to_s) @@ -64,7 +65,7 @@ module ActiveRecord # Returns a single DatabaseConfig object based on the requested environment. # - # If the application has multiple databases `select_db_config` will return + # If the application has multiple databases `find_db_config` will return # the first DatabaseConfig for the environment. def find_db_config(env) configurations.find do |db_config| @@ -73,7 +74,7 @@ module ActiveRecord end end - # Returns the DatabaseConfig object as a Hash. + # Returns the DatabaseConfigurations object as a Hash. def to_h configs = configurations.reverse.inject({}) do |memo, db_config| memo.merge(db_config.to_legacy_hash) diff --git a/activerecord/lib/active_record/database_configurations/hash_config.rb b/activerecord/lib/active_record/database_configurations/hash_config.rb index 18ed7c0466..13ffe566cf 100644 --- a/activerecord/lib/active_record/database_configurations/hash_config.rb +++ b/activerecord/lib/active_record/database_configurations/hash_config.rb @@ -12,7 +12,7 @@ module ActiveRecord # Becomes: # # #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd1acbded10 - # @env_name="development", @spec_name="primary", @config={"db_name"}> + # @env_name="development", @spec_name="primary", @config={"database"=>"db_name"}> # # Options are: # diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb index 6891c575c7..b25057acda 100644 --- a/activerecord/lib/active_record/inheritance.rb +++ b/activerecord/lib/active_record/inheritance.rb @@ -55,6 +55,10 @@ module ActiveRecord if has_attribute?(inheritance_column) subclass = subclass_from_attributes(attributes) + if subclass.nil? && scope_attributes = current_scope&.scope_for_create + subclass = subclass_from_attributes(scope_attributes) + end + if subclass.nil? && base_class? subclass = subclass_from_attributes(column_defaults) end diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index d68ed3cad9..ea53324829 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -1357,7 +1357,7 @@ module ActiveRecord end def use_advisory_lock? - Base.connection.supports_advisory_locks? + Base.connection.advisory_locks_enabled? end def with_advisory_lock diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 24af56cf0b..6eb2bfb452 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -70,17 +70,6 @@ module ActiveRecord instantiate_instance_of(klass, attributes, column_types, &block) end - # Given a class, an attributes hash, +instantiate_instance_of+ returns a - # new instance of the class. Accepts only keys as strings. - # - # This is private, don't call it. :) - # - # :nodoc: - 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) - end - # Updates an object (or multiple objects) and saves it to the database, if validations pass. # The resulting object is returned whether the object was saved successfully to the database or not. # @@ -216,6 +205,13 @@ module ActiveRecord end private + # Given a class, an attributes hash, +instantiate_instance_of+ returns a + # 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) + end + # Called by +instantiate+ to decide which class to use for a new # record instance. # diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb index 7ece083fd4..b213754641 100644 --- a/activerecord/lib/active_record/railtie.rb +++ b/activerecord/lib/active_record/railtie.rb @@ -180,9 +180,7 @@ end_warning end initializer "active_record.set_executor_hooks" do - ActiveSupport.on_load(:active_record) do - ActiveRecord::QueryCache.install_executor_hooks - end + ActiveRecord::QueryCache.install_executor_hooks end initializer "active_record.add_watchable_files" do |app| @@ -235,5 +233,11 @@ MSG end end end + + initializer "active_record.set_filter_attributes" do + ActiveSupport.on_load(:active_record) do + self.filter_attributes += Rails.application.config.filter_parameters + end + end end end diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 29a3ceab7d..86e5d14c81 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -62,7 +62,7 @@ module ActiveRecord # user = users.new { |user| user.name = 'Oscar' } # user.name # => Oscar def new(attributes = nil, &block) - scoping { klass.new(scope_for_create(attributes), &block) } + scoping { klass.new(attributes, &block) } end alias build new @@ -87,11 +87,7 @@ module ActiveRecord # users.create(name: nil) # validation on name # # => #<User id: nil, name: nil, ...> def create(attributes = nil, &block) - if attributes.is_a?(Array) - attributes.collect { |attr| create(attr, &block) } - else - scoping { klass.create(scope_for_create(attributes), &block) } - end + scoping { klass.create(attributes, &block) } end # Similar to #create, but calls @@ -101,11 +97,7 @@ module ActiveRecord # Expects arguments in the same format as # {ActiveRecord::Base.create!}[rdoc-ref:Persistence::ClassMethods#create!]. def create!(attributes = nil, &block) - if attributes.is_a?(Array) - attributes.collect { |attr| create!(attr, &block) } - else - scoping { klass.create!(scope_for_create(attributes), &block) } - end + scoping { klass.create!(attributes, &block) } end def first_or_create(attributes = nil, &block) # :nodoc: @@ -315,10 +307,7 @@ 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 - previous, klass.current_scope = klass.current_scope(true), self unless @delegate_to_klass - yield - ensure - klass.current_scope = previous unless @delegate_to_klass + @delegate_to_klass ? yield : klass._scoping(self) { yield } end def _exec_scope(*args, &block) # :nodoc: @@ -554,10 +543,8 @@ module ActiveRecord where_clause.to_h(relation_table_name) end - def scope_for_create(attributes = nil) - scope = where_values_hash.merge!(create_with_value.stringify_keys) - scope.merge!(attributes) if attributes - scope + def scope_for_create + where_values_hash.merge!(create_with_value.stringify_keys) end # Returns true if relation needs eager loading. diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index c5562c1ff0..0fabfe5518 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -398,7 +398,7 @@ module ActiveRecord def limited_ids_for(relation) values = @klass.connection.columns_for_distinct( - connection.column_name_from_arel_node(arel_attribute(primary_key)), + connection.visitor.compile(arel_attribute(primary_key)), relation.order_values ) diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index f734cd0ad8..cb70b8bcde 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -57,10 +57,14 @@ module ActiveRecord end def build_bind_attribute(column_name, value) - attr = Relation::QueryAttribute.new(column_name.to_s, value, table.type(column_name)) + attr = build_query_attribute(column_name, value) Arel::Nodes::BindParam.new(attr) end + def build_query_attribute(column_name, value) + Relation::QueryAttribute.new(column_name.to_s, value, table.type(column_name)) + end + protected def expand_from_hash(attributes) return ["1=0"] if attributes.empty? 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 e5191fa38a..0895b9fba6 100644 --- a/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb +++ b/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb @@ -21,10 +21,11 @@ module ActiveRecord when 0 then NullPredicate when 1 then predicate_builder.build(attribute, values.first) else - bind_values = values.map do |v| - predicate_builder.build_bind_attribute(attribute.name, v) - end - attribute.in(bind_values) + values.map! do |v| + attr = predicate_builder.build_query_attribute(attribute.name, v) + attr.value_for_database if attr.boundable? + end.compact! + values.empty? ? NullPredicate : attribute.in(values) end unless nils.empty? diff --git a/activerecord/lib/active_record/scoping.rb b/activerecord/lib/active_record/scoping.rb index 01ac56570a..9eba1254a4 100644 --- a/activerecord/lib/active_record/scoping.rb +++ b/activerecord/lib/active_record/scoping.rb @@ -12,14 +12,6 @@ module ActiveRecord end module ClassMethods # :nodoc: - def current_scope(skip_inherited_scope = false) - ScopeRegistry.value_for(:current_scope, self, skip_inherited_scope) - end - - def current_scope=(scope) - ScopeRegistry.set_value_for(:current_scope, self, scope) - end - # Collects attributes from scopes that should be applied when creating # an AR instance for the particular class this is called on. def scope_attributes @@ -30,6 +22,15 @@ module ActiveRecord def scope_attributes? current_scope end + + private + def current_scope(skip_inherited_scope = false) + ScopeRegistry.value_for(:current_scope, self, skip_inherited_scope) + end + + def current_scope=(scope) + ScopeRegistry.set_value_for(:current_scope, self, scope) + end end def populate_with_current_scope_attributes # :nodoc: diff --git a/activerecord/lib/active_record/scoping/default.rb b/activerecord/lib/active_record/scoping/default.rb index 8c612df27a..6caf9b3251 100644 --- a/activerecord/lib/active_record/scoping/default.rb +++ b/activerecord/lib/active_record/scoping/default.rb @@ -31,7 +31,14 @@ module ActiveRecord # Post.limit(10) # Fires "SELECT * FROM posts LIMIT 10" # } def unscoped - block_given? ? relation.scoping { yield } : relation + 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 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 a784001587..573d97b819 100644 --- a/activerecord/lib/active_record/scoping/named.rb +++ b/activerecord/lib/active_record/scoping/named.rb @@ -24,13 +24,13 @@ module ActiveRecord # You can define a scope that applies to all finders using # {default_scope}[rdoc-ref:Scoping::Default::ClassMethods#default_scope]. def all - current_scope = self.current_scope + scope = current_scope - if current_scope - if self == current_scope.klass - current_scope.clone + if scope + if self == scope.klass + scope.clone else - relation.merge!(current_scope) + relation.merge!(scope) end else default_scoped @@ -38,9 +38,7 @@ module ActiveRecord end def scope_for_association(scope = relation) # :nodoc: - current_scope = self.current_scope - - if current_scope && current_scope.empty_scope? + if current_scope&.empty_scope? scope else default_scoped(scope) @@ -182,15 +180,13 @@ module ActiveRecord if body.respond_to?(:to_proc) singleton_class.send(:define_method, name) do |*args| - scope = all - scope = scope._exec_scope(*args, &body) + scope = all._exec_scope(*args, &body) scope = scope.extending(extension) if extension scope end else singleton_class.send(:define_method, name) do |*args| - scope = all - scope = scope.scoping { body.call(*args) || scope } + scope = body.call(*args) || all scope = scope.extending(extension) if extension scope end diff --git a/activerecord/lib/arel/nodes/bind_param.rb b/activerecord/lib/arel/nodes/bind_param.rb index 53c5563d93..91e9b2b70f 100644 --- a/activerecord/lib/arel/nodes/bind_param.rb +++ b/activerecord/lib/arel/nodes/bind_param.rb @@ -3,7 +3,7 @@ module Arel # :nodoc: all module Nodes class BindParam < Node - attr_accessor :value + attr_reader :value def initialize(value) @value = value diff --git a/activerecord/lib/arel/select_manager.rb b/activerecord/lib/arel/select_manager.rb index 22a04b00c6..733176ad9e 100644 --- a/activerecord/lib/arel/select_manager.rb +++ b/activerecord/lib/arel/select_manager.rb @@ -252,9 +252,9 @@ module Arel # :nodoc: all end private - def collapse(exprs, existing = nil) - exprs = exprs.unshift(existing.expr) if existing - exprs = exprs.compact.map { |expr| + def collapse(exprs) + exprs = exprs.compact + exprs.map! { |expr| if String === expr # FIXME: Don't do this automatically Arel.sql(expr) diff --git a/activerecord/lib/arel/visitors/to_sql.rb b/activerecord/lib/arel/visitors/to_sql.rb index 5986fd5576..0682c066fb 100644 --- a/activerecord/lib/arel/visitors/to_sql.rb +++ b/activerecord/lib/arel/visitors/to_sql.rb @@ -67,8 +67,8 @@ module Arel # :nodoc: all @connection = connection end - def compile(node, &block) - accept(node, Arel::Collectors::SQLString.new, &block).value + def compile(node, collector = Arel::Collectors::SQLString.new, &block) + accept(node, collector, &block).value end private diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb index 67734d24d7..a93e5e2b40 100644 --- a/activerecord/test/cases/adapter_test.rb +++ b/activerecord/test/cases/adapter_test.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require "cases/helper" +require "support/connection_helper" require "models/book" require "models/post" require "models/author" @@ -300,6 +301,34 @@ module ActiveRecord def test_supports_multi_insert_is_deprecated assert_deprecated { @connection.supports_multi_insert? } end + + def test_column_name_length_is_deprecated + assert_deprecated { @connection.column_name_length } + end + + def test_table_name_length_is_deprecated + assert_deprecated { @connection.table_name_length } + end + + def test_columns_per_table_is_deprecated + assert_deprecated { @connection.columns_per_table } + end + + def test_indexes_per_table_is_deprecated + assert_deprecated { @connection.indexes_per_table } + end + + def test_columns_per_multicolumn_index_is_deprecated + assert_deprecated { @connection.columns_per_multicolumn_index } + end + + def test_sql_query_length_is_deprecated + assert_deprecated { @connection.sql_query_length } + end + + def test_joins_per_query_is_deprecated + assert_deprecated { @connection.joins_per_query } + end end class AdapterForeignKeyTest < ActiveRecord::TestCase @@ -418,3 +447,27 @@ module ActiveRecord end end end + +if ActiveRecord::Base.connection.supports_advisory_locks? + class AdvisoryLocksEnabledTest < ActiveRecord::TestCase + include ConnectionHelper + + def test_advisory_locks_enabled? + assert ActiveRecord::Base.connection.advisory_locks_enabled? + + run_without_connection do |orig_connection| + ActiveRecord::Base.establish_connection( + orig_connection.merge(advisory_locks: false) + ) + + assert_not ActiveRecord::Base.connection.advisory_locks_enabled? + + ActiveRecord::Base.establish_connection( + orig_connection.merge(advisory_locks: true) + ) + + assert ActiveRecord::Base.connection.advisory_locks_enabled? + end + end + end +end diff --git a/activerecord/test/cases/adapters/helpers/test_supports_advisory_locks.rb b/activerecord/test/cases/adapters/helpers/test_supports_advisory_locks.rb deleted file mode 100644 index 4905e17725..0000000000 --- a/activerecord/test/cases/adapters/helpers/test_supports_advisory_locks.rb +++ /dev/null @@ -1,25 +0,0 @@ -# frozen_string_literal: true - -require "support/connection_helper" - -module TestSupportsAdvisoryLocks - include ConnectionHelper - - def test_supports_advisory_locks? - assert ActiveRecord::Base.connection.supports_advisory_locks? - - run_without_connection do |orig_connection| - ActiveRecord::Base.establish_connection( - orig_connection.merge(advisory_locks: false) - ) - - assert_not ActiveRecord::Base.connection.supports_advisory_locks? - - ActiveRecord::Base.establish_connection( - orig_connection.merge(advisory_locks: true) - ) - - assert ActiveRecord::Base.connection.supports_advisory_locks? - end - end -end diff --git a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb index 6fc9df5083..261fee13eb 100644 --- a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb +++ b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb @@ -106,7 +106,7 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase end def test_create_mysql_database_with_encoding - assert_equal "CREATE DATABASE `matt` DEFAULT CHARACTER SET `utf8`", create_database(:matt) + assert_equal "CREATE DATABASE `matt` DEFAULT CHARACTER SET `utf8mb4`", create_database(:matt) assert_equal "CREATE DATABASE `aimonetti` DEFAULT CHARACTER SET `latin1`", create_database(:aimonetti, charset: "latin1") assert_equal "CREATE DATABASE `matt_aimonetti` DEFAULT COLLATE `utf8mb4_bin`", create_database(:matt_aimonetti, collation: "utf8mb4_bin") end diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb index 0c0e2a116e..3103589186 100644 --- a/activerecord/test/cases/adapters/mysql2/connection_test.rb +++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb @@ -104,8 +104,8 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase end def test_mysql_connection_collation_is_configured - assert_equal "utf8_unicode_ci", @connection.show_variable("collation_connection") - assert_equal "utf8_general_ci", ARUnit2Model.connection.show_variable("collation_connection") + assert_equal "utf8mb4_unicode_ci", @connection.show_variable("collation_connection") + assert_equal "utf8mb4_general_ci", ARUnit2Model.connection.show_variable("collation_connection") end def test_mysql_default_in_strict_mode diff --git a/activerecord/test/cases/adapters/mysql2/test_advisory_locks_disabled_test.rb b/activerecord/test/cases/adapters/mysql2/test_advisory_locks_disabled_test.rb deleted file mode 100644 index 4857900820..0000000000 --- a/activerecord/test/cases/adapters/mysql2/test_advisory_locks_disabled_test.rb +++ /dev/null @@ -1,8 +0,0 @@ -# frozen_string_literal: true - -require "cases/helper" -require "cases/adapters/helpers/test_supports_advisory_locks" - -class Mysql2AdvisoryLocksDisabledTest < ActiveRecord::Mysql2TestCase - include TestSupportsAdvisoryLocks -end diff --git a/activerecord/test/cases/adapters/postgresql/advisory_locks_disabled_test.rb b/activerecord/test/cases/adapters/postgresql/advisory_locks_disabled_test.rb deleted file mode 100644 index f14e9baeb9..0000000000 --- a/activerecord/test/cases/adapters/postgresql/advisory_locks_disabled_test.rb +++ /dev/null @@ -1,8 +0,0 @@ -# frozen_string_literal: true - -require "cases/helper" -require "cases/adapters/helpers/test_supports_advisory_locks" - -class PostgresqlAdvisoryLocksDisabledTest < ActiveRecord::PostgreSQLTestCase - include TestSupportsAdvisoryLocks -end diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb index a1fba8dc66..ca902131f4 100644 --- a/activerecord/test/cases/associations/eager_test.rb +++ b/activerecord/test/cases/associations/eager_test.rb @@ -18,6 +18,7 @@ require "models/job" require "models/subscriber" require "models/subscription" require "models/book" +require "models/citation" require "models/developer" require "models/computer" require "models/project" @@ -29,6 +30,18 @@ require "models/sponsor" require "models/mentor" require "models/contract" +class EagerLoadingTooManyIdsTest < ActiveRecord::TestCase + fixtures :citations + + def test_preloading_too_many_ids + assert_equal Citation.count, Citation.preload(:citations).to_a.size + end + + def test_eager_loading_too_may_ids + assert_equal Citation.count, Citation.eager_load(:citations).offset(0).size + end +end + class EagerAssociationTest < ActiveRecord::TestCase fixtures :posts, :comments, :authors, :essays, :author_addresses, :categories, :categories_posts, :companies, :accounts, :tags, :taggings, :people, :readers, :categorizations, diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb index fa618735d7..db3a58eba9 100644 --- a/activerecord/test/cases/autosave_association_test.rb +++ b/activerecord/test/cases/autosave_association_test.rb @@ -14,6 +14,7 @@ require "models/line_item" require "models/order" require "models/parrot" require "models/pirate" +require "models/project" require "models/ship" require "models/ship_part" require "models/tag" @@ -1787,3 +1788,21 @@ class TestAutosaveAssociationOnAHasManyAssociationWithInverse < ActiveRecord::Te assert_equal 1, comment.post_comments_count end end + +class TestAutosaveAssociationOnAHasManyAssociationDefinedInSubclassWithAcceptsNestedAttributes < ActiveRecord::TestCase + def test_should_update_children_when_asssociation_redefined_in_subclass + agency = Agency.create!(name: "Agency") + valid_project = Project.create!(firm: agency, name: "Initial") + agency.update!( + "projects_attributes" => { + "0" => { + "name" => "Updated", + "id" => valid_project.id + } + } + ) + valid_project.reload + + assert_equal "Updated", valid_project.name + end +end diff --git a/activerecord/test/cases/filter_attributes_test.rb b/activerecord/test/cases/filter_attributes_test.rb new file mode 100644 index 0000000000..af5badd87d --- /dev/null +++ b/activerecord/test/cases/filter_attributes_test.rb @@ -0,0 +1,84 @@ +# frozen_string_literal: true + +require "cases/helper" +require "models/admin" +require "models/admin/user" +require "models/admin/account" +require "pp" + +class FilterAttributesTest < ActiveRecord::TestCase + fixtures :"admin/users", :"admin/accounts" + + setup do + @previous_filter_attributes = ActiveRecord::Base.filter_attributes + ActiveRecord::Base.filter_attributes = [:name] + end + + teardown do + ActiveRecord::Base.filter_attributes = @previous_filter_attributes + end + + test "filter_attributes" do + Admin::User.all.each do |user| + assert_includes user.inspect, "name: [FILTERED]" + assert_equal 1, user.inspect.scan("[FILTERED]").length + end + + Admin::Account.all.each do |account| + assert_includes account.inspect, "name: [FILTERED]" + assert_equal 1, account.inspect.scan("[FILTERED]").length + end + end + + test "filter_attributes could be overwritten by models" do + Admin::Account.all.each do |account| + assert_includes account.inspect, "name: [FILTERED]" + assert_equal 1, account.inspect.scan("[FILTERED]").length + end + + begin + previous_account_filter_attributes = Admin::Account.filter_attributes + Admin::Account.filter_attributes = [] + + # Above changes should not impact other models + Admin::User.all.each do |user| + assert_includes user.inspect, "name: [FILTERED]" + assert_equal 1, user.inspect.scan("[FILTERED]").length + end + + Admin::Account.all.each do |account| + assert_not_includes account.inspect, "name: [FILTERED]" + assert_equal 0, account.inspect.scan("[FILTERED]").length + end + ensure + Admin::Account.filter_attributes = previous_account_filter_attributes + end + end + + test "filter_attributes should not filter nil value" do + account = Admin::Account.new + + assert_includes account.inspect, "name: nil" + assert_not_includes account.inspect, "name: [FILTERED]" + assert_equal 0, account.inspect.scan("[FILTERED]").length + end + + test "filter_attributes on pretty_print" do + user = admin_users(:david) + actual = "".dup + PP.pp(user, StringIO.new(actual)) + + assert_includes actual, "name: [FILTERED]" + assert_equal 1, actual.scan("[FILTERED]").length + end + + test "filter_attributes on pretty_print should not filter nil value" do + user = Admin::User.new + actual = "".dup + PP.pp(user, StringIO.new(actual)) + + assert_includes actual, "name: nil" + assert_not_includes actual, "name: [FILTERED]" + assert_equal 0, actual.scan("[FILTERED]").length + end +end diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb index aa519fd332..bb1c1ea17d 100644 --- a/activerecord/test/cases/nested_attributes_test.rb +++ b/activerecord/test/cases/nested_attributes_test.rb @@ -217,6 +217,18 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase mean_pirate.parrot_attributes = { name: "James" } assert_equal "James", mean_pirate.parrot.name end + + def test_should_not_create_duplicates_with_create_with + Man.accepts_nested_attributes_for(:interests) + + assert_difference("Interest.count", 1) do + Man.create_with( + interests_attributes: [{ topic: "Pirate king" }] + ).find_or_create_by!( + name: "Monkey D. Luffy" + ) + end + end end class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index d03b412efb..c14dc23cf3 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -9,6 +9,7 @@ require "models/comment" require "models/author" require "models/entrant" require "models/developer" +require "models/project" require "models/person" require "models/computer" require "models/reply" @@ -1405,6 +1406,16 @@ class RelationTest < ActiveRecord::TestCase assert_equal "cock", hens.new.name end + def test_create_with_nested_attributes + assert_difference("Project.count", 1) do + developers = Developer.where(name: "Aaron") + developers = developers.create_with( + projects_attributes: [{ name: "p1" }] + ) + developers.create! + end + end + def test_except relation = Post.where(author_id: 1).order("id ASC").limit(1) assert_equal [posts(:welcome)], relation.to_a diff --git a/activerecord/test/cases/scoping/default_scoping_test.rb b/activerecord/test/cases/scoping/default_scoping_test.rb index e3a34aa50d..6281712df6 100644 --- a/activerecord/test/cases/scoping/default_scoping_test.rb +++ b/activerecord/test/cases/scoping/default_scoping_test.rb @@ -4,6 +4,7 @@ require "cases/helper" require "models/post" require "models/comment" require "models/developer" +require "models/project" require "models/computer" require "models/vehicle" require "models/cat" @@ -366,6 +367,21 @@ class DefaultScopingTest < ActiveRecord::TestCase assert_equal "Jamis", jamis.name end + def test_create_with_takes_precedence_over_where + developer = Developer.where(name: nil).create_with(name: "Aaron").new + assert_equal "Aaron", developer.name + end + + def test_create_with_nested_attributes + assert_difference("Project.count", 1) do + Developer.create_with( + projects_attributes: [{ name: "p1" }] + ).scoping do + Developer.create!(name: "Aaron") + end + end + end + # FIXME: I don't know if this is *desired* behavior, but it is *today's* # behavior. def test_create_with_empty_hash_will_not_reset diff --git a/activerecord/test/cases/scoping/relation_scoping_test.rb b/activerecord/test/cases/scoping/relation_scoping_test.rb index 544adc9b39..b4f4379e5e 100644 --- a/activerecord/test/cases/scoping/relation_scoping_test.rb +++ b/activerecord/test/cases/scoping/relation_scoping_test.rb @@ -236,8 +236,8 @@ class RelationScopingTest < ActiveRecord::TestCase SpecialComment.unscoped.created end - assert_nil Comment.current_scope - assert_nil SpecialComment.current_scope + assert_nil Comment.send(:current_scope) + assert_nil SpecialComment.send(:current_scope) end def test_scoping_respects_current_class diff --git a/activerecord/test/config.example.yml b/activerecord/test/config.example.yml index 4bcb2aeea6..be337ddcd8 100644 --- a/activerecord/test/config.example.yml +++ b/activerecord/test/config.example.yml @@ -54,11 +54,11 @@ connections: mysql2: arunit: username: rails - encoding: utf8 - collation: utf8_unicode_ci + encoding: utf8mb4 + collation: utf8mb4_unicode_ci arunit2: username: rails - encoding: utf8 + encoding: utf8mb4 oracle: arunit: diff --git a/activerecord/test/fixtures/citations.yml b/activerecord/test/fixtures/citations.yml new file mode 100644 index 0000000000..d31cb8efa1 --- /dev/null +++ b/activerecord/test/fixtures/citations.yml @@ -0,0 +1,4 @@ +<% 65536.times do |i| %> +fixture_no_<%= i %>: + id: <%= i %> +<% end %> diff --git a/activerecord/test/models/citation.rb b/activerecord/test/models/citation.rb index 3d786f27eb..cee3d18173 100644 --- a/activerecord/test/models/citation.rb +++ b/activerecord/test/models/citation.rb @@ -2,4 +2,5 @@ class Citation < ActiveRecord::Base belongs_to :reference_of, class_name: "Book", foreign_key: :book2_id + has_many :citations end diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb index 485b35d58b..838f515aad 100644 --- a/activerecord/test/models/company.rb +++ b/activerecord/test/models/company.rb @@ -122,6 +122,12 @@ class RestrictedWithErrorFirm < Company has_many :companies, -> { order("id") }, foreign_key: "client_of", dependent: :restrict_with_error end +class Agency < Firm + has_many :projects, foreign_key: :firm_id + + accepts_nested_attributes_for :projects +end + class Client < Company belongs_to :firm, foreign_key: "client_of" belongs_to :firm_with_basic_id, class_name: "Firm", foreign_key: "firm_id" diff --git a/activerecord/test/schema/mysql2_specific_schema.rb b/activerecord/test/schema/mysql2_specific_schema.rb index 0f2f6ddd68..c384297658 100644 --- a/activerecord/test/schema/mysql2_specific_schema.rb +++ b/activerecord/test/schema/mysql2_specific_schema.rb @@ -36,7 +36,7 @@ ActiveRecord::Schema.define do t.index :var_binary end - create_table :key_tests, force: true, options: "ENGINE=MyISAM" do |t| + create_table :key_tests, force: true do |t| t.string :awesome t.string :pizza t.string :snacks diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index 266e55f682..27c7348f5c 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -160,6 +160,7 @@ ActiveRecord::Schema.define do create_table :citations, force: true do |t| t.column :book1_id, :integer t.column :book2_id, :integer + t.references :citation end create_table :clubs, force: true do |t| |