diff options
Diffstat (limited to 'activerecord')
21 files changed, 163 insertions, 56 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index f854c106e8..d59101d621 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,6 +1,40 @@ +* Delegate `empty?`, `none?` and `one?`. Now they can be invoked as model class methods. + + Example: + + # When no record is found on the table + Topic.empty? # => true + Topic.none? # => true + + # When only one record is found on the table + Topic.one? # => true + + *Kenta Shirai* + +* The form builder now properly displays values when passing a proc form + default to the attributes API. + + Fixes #24249. + + *Sean Griffin* + +* The schema cache is now cleared after the `db:migrate` task is run. + + Closes #24273. + + *Chris Arcand* + +* MySQL: strict mode respects other SQL modes rather than overwriting them. + Setting `strict: true` adds `STRICT_ALL_TABLES` to `sql_mode`. Setting + `strict: false` removes `STRICT_TRANS_TABLES`, `STRICT_ALL_TABLES`, and + `TRADITIONAL` from `sql_mode`. + + *Ryuta Kamizono* + * Execute default_scope defined by abstract class in the context of subclass. - Fixes #23413 & #10658 + Fixes #23413. + Fixes #10658. *Mehmet Emin İNAÇ* @@ -523,13 +557,13 @@ * Add option to index errors in nested attributes For models which have nested attributes, errors within those models will - now be indexed if :index_errors is specified when defining a + now be indexed if `:index_errors` is specified when defining a has_many relationship, or if its set in the global config. Example: class Guitar < ActiveRecord::Base - has_many :tuning_pegs + has_many :tuning_pegs, index_errors: true accepts_nested_attributes_for :tuning_pegs end diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index e13fe33b85..77d17fc975 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -504,7 +504,7 @@ module ActiveRecord # # == Customizing the query # - # \Associations are built from <tt>Relation</tt>s, and you can use the Relation syntax + # \Associations are built from <tt>Relation</tt> objects, and you can use the Relation syntax # to customize them. For example, to add a condition: # # class Blog < ActiveRecord::Base diff --git a/activerecord/lib/active_record/attribute/user_provided_default.rb b/activerecord/lib/active_record/attribute/user_provided_default.rb index 6dbd92ce28..4580813364 100644 --- a/activerecord/lib/active_record/attribute/user_provided_default.rb +++ b/activerecord/lib/active_record/attribute/user_provided_default.rb @@ -4,20 +4,25 @@ module ActiveRecord class Attribute # :nodoc: class UserProvidedDefault < FromUser # :nodoc: def initialize(name, value, type, database_default) + @user_provided_value = value super(name, value, type, database_default) end - def type_cast(value) - if value.is_a?(Proc) - super(value.call) + def value_before_type_cast + if user_provided_value.is_a?(Proc) + @memoized_value_before_type_cast ||= user_provided_value.call else - super + @user_provided_value end end def with_type(type) - self.class.new(name, value_before_type_cast, type, original_attribute) + self.class.new(name, user_provided_value, type, original_attribute) end + + protected + + attr_reader :user_provided_value 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 ebaaa54b2b..e160460286 100644 --- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb +++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb @@ -104,7 +104,7 @@ module ActiveRecord To silence this deprecation warning, add the following: - config.active_record.time_zone_aware_types << :time + config.active_record.time_zone_aware_types = [:datetime, :time] MESSAGE end diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 7ed2fe48be..6a1a27ce41 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -169,7 +169,8 @@ module ActiveRecord #:nodoc: # ActiveRecord::RecordNotFound error if they do not return any records, # like <tt>Person.find_by_last_name!</tt>. # - # It's also possible to use multiple attributes in the same find by separating them with "_and_". + # It's also possible to use multiple attributes in the same <tt>find_by_</tt> by separating them with + # "_and_". # # Person.find_by(user_name: user_name, password: password) # Person.find_by_user_name_and_password(user_name, password) # with dynamic finder 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 a401310ee0..020d9bbdca 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -1051,9 +1051,9 @@ module ActiveRecord end # Adds timestamps (+created_at+ and +updated_at+) columns to +table_name+. - # Additional options (like <tt>null: false</tt>) are forwarded to #add_column. + # Additional options (like +:null+) are forwarded to #add_column. # - # add_timestamps(:suppliers, null: false) + # add_timestamps(:suppliers, null: true) # def add_timestamps(table_name, options = {}) options[:null] = false if options[:null].nil? 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 22b71fbaba..e31d2237eb 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -846,9 +846,19 @@ module ActiveRecord # 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 # If the user has provided another value for sql_mode, don't replace it. - unless variables.has_key?('sql_mode') || defaults.include?(@config[:strict]) - variables['sql_mode'] = strict_mode? ? 'STRICT_ALL_TABLES' : '' + if sql_mode = variables.delete('sql_mode') + sql_mode = quote(sql_mode) + elsif !defaults.include?(strict_mode?) + if strict_mode? + sql_mode = "CONCAT(@@sql_mode, ',STRICT_ALL_TABLES')" + else + sql_mode = "REPLACE(@@sql_mode, 'STRICT_TRANS_TABLES', '')" + sql_mode = "REPLACE(#{sql_mode}, 'STRICT_ALL_TABLES', '')" + sql_mode = "REPLACE(#{sql_mode}, 'TRADITIONAL', '')" + end + sql_mode = "CONCAT(#{sql_mode}, ',NO_AUTO_VALUE_ON_ZERO')" end + 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 @@ -870,7 +880,7 @@ module ActiveRecord end.compact.join(', ') # ...and send them all in one query - @connection.query "SET #{encoding} #{variable_assignments}" + @connection.query "SET #{encoding} #{sql_mode_assignment} #{variable_assignments}" end def column_definitions(table_name) # :nodoc: diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index 4419a7b1e7..245c05f3e0 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -528,7 +528,7 @@ module ActiveRecord name = "V#{version.tr('.', '_')}" unless Compatibility.const_defined?(name) versions = Compatibility.constants.grep(/\AV[0-9_]+\z/).map { |s| s.to_s.delete('V').tr('_', '.').inspect } - raise "Unknown migration version #{version.inspect}; expected one of #{versions.sort.join(', ')}" + raise ArgumentError, "Unknown migration version #{version.inspect}; expected one of #{versions.sort.join(', ')}" end Compatibility.const_get(name) end diff --git a/activerecord/lib/active_record/migration/compatibility.rb b/activerecord/lib/active_record/migration/compatibility.rb index 09d55adcd7..a20d7e0820 100644 --- a/activerecord/lib/active_record/migration/compatibility.rb +++ b/activerecord/lib/active_record/migration/compatibility.rb @@ -57,7 +57,7 @@ module ActiveRecord def index_exists?(table_name, column_name, options = {}) column_names = Array(column_name).map(&:to_s) options[:name] = - if options.key?(:name).present? + if options[:name].present? options[:name].to_s else index_name(table_name, column: column_names) diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb index ee52c3ae02..52eab952e1 100644 --- a/activerecord/lib/active_record/model_schema.rb +++ b/activerecord/lib/active_record/model_schema.rb @@ -231,6 +231,18 @@ module ActiveRecord @explicit_sequence_name = true end + # Determines if the primary key values should be selected from their + # corresponding sequence before the insert statement. + def prefetch_primary_key? + connection.prefetch_primary_key?(table_name) + end + + # Returns the next value that will be used as the primary key on + # an insert statment. + def next_sequence_value + connection.next_sequence_value(sequence_name) + end + # Indicates whether the table associated with this class exists def table_exists? connection.schema_cache.data_source_exists?(table_name) diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb index de5b42e987..4e32d73001 100644 --- a/activerecord/lib/active_record/querying.rb +++ b/activerecord/lib/active_record/querying.rb @@ -1,6 +1,6 @@ module ActiveRecord module Querying - delegate :find, :take, :take!, :first, :first!, :last, :last!, :exists?, :any?, :many?, to: :all + delegate :find, :take, :take!, :first, :first!, :last, :last!, :exists?, :any?, :many?, :empty?, :none?, :one?, to: :all delegate :second, :second!, :third, :third!, :fourth, :fourth!, :fifth, :fifth!, :forty_two, :forty_two!, :third_to_last, :third_to_last!, :second_to_last, :second_to_last!, to: :all delegate :first_or_create, :first_or_create!, :first_or_initialize, to: :all delegate :find_or_create_by, :find_or_create_by!, :find_or_initialize_by, to: :all diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb index 4c074c93ed..98ea425d16 100644 --- a/activerecord/lib/active_record/railtie.rb +++ b/activerecord/lib/active_record/railtie.rb @@ -65,7 +65,6 @@ module ActiveRecord ActiveSupport.on_load(:active_record) do self.time_zone_aware_attributes = true self.default_timezone = :utc - self.time_zone_aware_types = ActiveRecord::Base.time_zone_aware_types end end diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 09afdc6c69..777b593812 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -45,8 +45,8 @@ module ActiveRecord k.name == primary_key }] - if !primary_key_value && connection.prefetch_primary_key?(klass.table_name) - primary_key_value = connection.next_sequence_value(klass.sequence_name) + if !primary_key_value && klass.prefetch_primary_key? + primary_key_value = klass.next_sequence_value values[arel_attribute(klass.primary_key)] = primary_key_value end end diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb index 261a6a264f..8881986f1b 100644 --- a/activerecord/lib/active_record/tasks/database_tasks.rb +++ b/activerecord/lib/active_record/tasks/database_tasks.rb @@ -159,6 +159,7 @@ module ActiveRecord Migrator.migrate(migrations_paths, version) do |migration| scope.blank? || scope == migration.scope end + ActiveRecord::Base.clear_cache! ensure Migration.verbose = verbose_was end diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb index a9f1993842..4f389e9249 100644 --- a/activerecord/test/cases/adapter_test.rb +++ b/activerecord/test/cases/adapter_test.rb @@ -23,6 +23,12 @@ module ActiveRecord end end + def test_create_record_with_pk_as_zero + Book.create(id: 0) + assert_equal 0, Book.find(0).id + assert_nothing_raised { Book.destroy(0) } + end + def test_tables tables = nil ActiveSupport::Deprecation.silence { tables = @connection.tables } diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb index 575138eb2a..c4715393b3 100644 --- a/activerecord/test/cases/adapters/mysql2/connection_test.rb +++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb @@ -69,40 +69,48 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase end def test_mysql_default_in_strict_mode - result = @connection.exec_query "SELECT @@SESSION.sql_mode" - assert_equal [["STRICT_ALL_TABLES"]], result.rows + result = @connection.select_value("SELECT @@SESSION.sql_mode") + assert_match %r(STRICT_ALL_TABLES), result end def test_mysql_strict_mode_disabled run_without_connection do |orig_connection| - ActiveRecord::Base.establish_connection(orig_connection.merge({:strict => false})) - result = ActiveRecord::Base.connection.exec_query "SELECT @@SESSION.sql_mode" - assert_equal [['']], result.rows + ActiveRecord::Base.establish_connection(orig_connection.merge(strict: false)) + result = ActiveRecord::Base.connection.select_value("SELECT @@SESSION.sql_mode") + assert_no_match %r(STRICT_ALL_TABLES), result + end + end + + def test_mysql_strict_mode_specified_default + run_without_connection do |orig_connection| + ActiveRecord::Base.establish_connection(orig_connection.merge(strict: :default)) + global_sql_mode = ActiveRecord::Base.connection.select_value("SELECT @@GLOBAL.sql_mode") + session_sql_mode = ActiveRecord::Base.connection.select_value("SELECT @@SESSION.sql_mode") + assert_equal global_sql_mode, session_sql_mode + end + end + + def test_mysql_sql_mode_variable_overrides_strict_mode + run_without_connection do |orig_connection| + ActiveRecord::Base.establish_connection(orig_connection.deep_merge(variables: { 'sql_mode' => 'ansi' })) + result = ActiveRecord::Base.connection.select_value('SELECT @@SESSION.sql_mode') + assert_no_match %r(STRICT_ALL_TABLES), result end end def test_passing_arbitary_flags_to_adapter - run_without_connection do |orig_connection| + run_without_connection do |orig_connection| ActiveRecord::Base.establish_connection(orig_connection.merge({flags: Mysql2::Client::COMPRESS})) assert_equal (Mysql2::Client::COMPRESS | Mysql2::Client::FOUND_ROWS), ActiveRecord::Base.connection.raw_connection.query_options[:flags] end end - + def test_passing_flags_by_array_to_adapter - run_without_connection do |orig_connection| + run_without_connection do |orig_connection| ActiveRecord::Base.establish_connection(orig_connection.merge({flags: ['COMPRESS'] })) assert_equal ["COMPRESS", "FOUND_ROWS"], ActiveRecord::Base.connection.raw_connection.query_options[:flags] end end - - def test_mysql_strict_mode_specified_default - run_without_connection do |orig_connection| - ActiveRecord::Base.establish_connection(orig_connection.merge({strict: :default})) - global_sql_mode = ActiveRecord::Base.connection.exec_query "SELECT @@GLOBAL.sql_mode" - session_sql_mode = ActiveRecord::Base.connection.exec_query "SELECT @@SESSION.sql_mode" - assert_equal global_sql_mode.rows, session_sql_mode.rows - end - end def test_mysql_set_session_variable run_without_connection do |orig_connection| @@ -112,14 +120,6 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase end end - def test_mysql_sql_mode_variable_overrides_strict_mode - run_without_connection do |orig_connection| - ActiveRecord::Base.establish_connection(orig_connection.deep_merge(variables: { 'sql_mode' => 'ansi' })) - result = ActiveRecord::Base.connection.exec_query 'SELECT @@SESSION.sql_mode' - assert_not_equal [['STRICT_ALL_TABLES']], result.rows - end - end - def test_mysql_set_session_variable_to_default run_without_connection do |orig_connection| ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:default_week_format => :default}})) diff --git a/activerecord/test/cases/attributes_test.rb b/activerecord/test/cases/attributes_test.rb index 2991ca8b76..2bebbfa205 100644 --- a/activerecord/test/cases/attributes_test.rb +++ b/activerecord/test/cases/attributes_test.rb @@ -135,6 +135,17 @@ module ActiveRecord assert_equal 2, klass.new.counter end + test "procs are memoized before type casting" do + klass = Class.new(OverloadedType) do + @@counter = 0 + attribute :counter, :integer, default: -> { @@counter += 1 } + end + + model = klass.new + assert_equal 1, model.counter_before_type_cast + assert_equal 1, model.counter_before_type_cast + end + test "user provided defaults are persisted even if unchanged" do model = OverloadedType.create! diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index 6a6250eec3..5a6d2ce80c 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -1107,4 +1107,7 @@ class CopyMigrationsTest < ActiveRecord::TestCase ActiveRecord::Base.logger = old end + def test_unknown_migration_version_should_raise_an_argument_error + assert_raise(ArgumentError) { ActiveRecord::Migration[1.0] } + end end diff --git a/activerecord/test/cases/scoping/named_scoping_test.rb b/activerecord/test/cases/scoping/named_scoping_test.rb index acba97bbb8..96c94eefa0 100644 --- a/activerecord/test/cases/scoping/named_scoping_test.rb +++ b/activerecord/test/cases/scoping/named_scoping_test.rb @@ -544,4 +544,24 @@ class NamedScopingTest < ActiveRecord::TestCase assert_equal 1, SpecialComment.where(body: 'go crazy').created.count end + def test_model_class_should_respond_to_empty + assert !Topic.empty? + Topic.delete_all + assert Topic.empty? + end + + def test_model_class_should_respond_to_none + assert !Topic.none? + Topic.delete_all + assert Topic.none? + end + + def test_model_class_should_respond_to_one + assert !Topic.one? + Topic.delete_all + assert !Topic.one? + Topic.create! + assert Topic.one? + end + end diff --git a/activerecord/test/cases/tasks/database_tasks_test.rb b/activerecord/test/cases/tasks/database_tasks_test.rb index 429aeca1d9..0aac5bad31 100644 --- a/activerecord/test/cases/tasks/database_tasks_test.rb +++ b/activerecord/test/cases/tasks/database_tasks_test.rb @@ -309,19 +309,30 @@ module ActiveRecord end class DatabaseTasksMigrateTest < ActiveRecord::TestCase + def setup + ActiveRecord::Tasks::DatabaseTasks.migrations_paths = 'custom/path' + end + + def teardown + ActiveRecord::Tasks::DatabaseTasks.migrations_paths = nil + end + def test_migrate_receives_correct_env_vars verbose, version = ENV['VERBOSE'], ENV['VERSION'] - ActiveRecord::Tasks::DatabaseTasks.migrations_paths = 'custom/path' ENV['VERBOSE'] = 'false' ENV['VERSION'] = '4' ActiveRecord::Migrator.expects(:migrate).with('custom/path', 4) ActiveRecord::Tasks::DatabaseTasks.migrate ensure - ActiveRecord::Tasks::DatabaseTasks.migrations_paths = nil ENV['VERBOSE'], ENV['VERSION'] = verbose, version end + + def test_migrate_clears_schema_cache_afterward + ActiveRecord::Base.expects(:clear_cache!) + ActiveRecord::Tasks::DatabaseTasks.migrate + end end class DatabaseTasksPurgeTest < ActiveRecord::TestCase diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index 01dbf2cee6..2bcdc8729e 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -1,10 +1,4 @@ ActiveRecord::Schema.define do - def except(adapter_names_to_exclude) - unless [adapter_names_to_exclude].flatten.include?(adapter_name) - yield - end - end - # ------------------------------------------------------------------- # # # # Please keep these create table statements in alphabetical order # @@ -991,7 +985,7 @@ ActiveRecord::Schema.define do create_table :records, force: true do |t| end - except 'SQLite' do + if supports_foreign_keys? # fk_test_has_fk should be before fk_test_has_pk create_table :fk_test_has_fk, force: true do |t| t.integer :fk_id, null: false |