diff options
Diffstat (limited to 'activerecord/test')
137 files changed, 2847 insertions, 1382 deletions
diff --git a/activerecord/test/active_record/connection_adapters/fake_adapter.rb b/activerecord/test/active_record/connection_adapters/fake_adapter.rb index 49a68fb94c..43c817e057 100644 --- a/activerecord/test/active_record/connection_adapters/fake_adapter.rb +++ b/activerecord/test/active_record/connection_adapters/fake_adapter.rb @@ -7,7 +7,7 @@ module ActiveRecord    module ConnectionAdapters      class FakeAdapter < AbstractAdapter -      attr_accessor :tables, :primary_keys +      attr_accessor :data_sources, :primary_keys        @columns = Hash.new { |h,k| h[k] = [] }        class << self @@ -16,7 +16,7 @@ module ActiveRecord        def initialize(connection, logger)          super -        @tables       = [] +        @data_sources = []          @primary_keys = {}          @columns      = self.class.columns        end @@ -37,7 +37,7 @@ module ActiveRecord          @columns[table_name]        end -      def table_exists?(*) +      def data_source_exists?(*)          true        end diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb index 1712ff0ac6..62579a4a7a 100644 --- a/activerecord/test/cases/adapter_test.rb +++ b/activerecord/test/cases/adapter_test.rb @@ -36,6 +36,21 @@ module ActiveRecord        assert !@connection.table_exists?(nil)      end +    def test_data_sources +      data_sources = @connection.data_sources +      assert data_sources.include?("accounts") +      assert data_sources.include?("authors") +      assert data_sources.include?("tasks") +      assert data_sources.include?("topics") +    end + +    def test_data_source_exists? +      assert @connection.data_source_exists?("accounts") +      assert @connection.data_source_exists?(:accounts) +      assert_not @connection.data_source_exists?("nonexistingtable") +      assert_not @connection.data_source_exists?(nil) +    end +      def test_indexes        idx_name = "accounts_idx" @@ -63,7 +78,7 @@ module ActiveRecord        end      end -    if current_adapter?(:MysqlAdapter) +    if current_adapter?(:MysqlAdapter, :Mysql2Adapter)        def test_charset          assert_not_nil @connection.charset          assert_not_equal 'character_set_database', @connection.charset diff --git a/activerecord/test/cases/adapters/mysql/active_schema_test.rb b/activerecord/test/cases/adapters/mysql/active_schema_test.rb index f0fd95ac16..0b5c9e1798 100644 --- a/activerecord/test/cases/adapters/mysql/active_schema_test.rb +++ b/activerecord/test/cases/adapters/mysql/active_schema_test.rb @@ -100,17 +100,15 @@ class MysqlActiveSchemaTest < ActiveRecord::MysqlTestCase      assert_equal "DROP TABLE `people`", drop_table(:people)    end -  if current_adapter?(:MysqlAdapter, :Mysql2Adapter) -    def test_create_mysql_database_with_encoding -      assert_equal "CREATE DATABASE `matt` DEFAULT CHARACTER SET `utf8`", create_database(:matt) -      assert_equal "CREATE DATABASE `aimonetti` DEFAULT CHARACTER SET `latin1`", create_database(:aimonetti, {:charset => 'latin1'}) -      assert_equal "CREATE DATABASE `matt_aimonetti` DEFAULT CHARACTER SET `big5` COLLATE `big5_chinese_ci`", create_database(:matt_aimonetti, {:charset => :big5, :collation => :big5_chinese_ci}) -    end +  def test_create_mysql_database_with_encoding +    assert_equal "CREATE DATABASE `matt` DEFAULT CHARACTER SET `utf8`", create_database(:matt) +    assert_equal "CREATE DATABASE `aimonetti` DEFAULT CHARACTER SET `latin1`", create_database(:aimonetti, {:charset => 'latin1'}) +    assert_equal "CREATE DATABASE `matt_aimonetti` DEFAULT CHARACTER SET `big5` COLLATE `big5_chinese_ci`", create_database(:matt_aimonetti, {:charset => :big5, :collation => :big5_chinese_ci}) +  end -    def test_recreate_mysql_database_with_encoding -      create_database(:luca, {:charset => 'latin1'}) -      assert_equal "CREATE DATABASE `luca` DEFAULT CHARACTER SET `latin1`", recreate_database(:luca, {:charset => 'latin1'}) -    end +  def test_recreate_mysql_database_with_encoding +    create_database(:luca, {:charset => 'latin1'}) +    assert_equal "CREATE DATABASE `luca` DEFAULT CHARACTER SET `latin1`", recreate_database(:luca, {:charset => 'latin1'})    end    def test_add_column diff --git a/activerecord/test/cases/adapters/mysql/connection_test.rb b/activerecord/test/cases/adapters/mysql/connection_test.rb index ddbc007b87..decac9e83b 100644 --- a/activerecord/test/cases/adapters/mysql/connection_test.rb +++ b/activerecord/test/cases/adapters/mysql/connection_test.rb @@ -26,7 +26,7 @@ class MysqlConnectionTest < ActiveRecord::MysqlTestCase        run_without_connection do          ar_config = ARTest.connection_config['arunit'] -        url = "mysql://#{ar_config["username"]}@localhost/#{ar_config["database"]}" +        url = "mysql://#{ar_config["username"]}:#{ar_config["password"]}@localhost/#{ar_config["database"]}"          Klass.establish_connection(url)          assert_equal ar_config['database'], Klass.connection.current_database        end @@ -118,15 +118,6 @@ class MysqlConnectionTest < ActiveRecord::MysqlTestCase      end    end -  # Test that MySQL allows multiple results for stored procedures -  if defined?(Mysql) && Mysql.const_defined?(:CLIENT_MULTI_RESULTS) -    def test_multi_results -      rows = ActiveRecord::Base.connection.select_rows('CALL ten();') -      assert_equal 10, rows[0][0].to_i, "ten() did not return 10 as expected: #{rows.inspect}" -      assert @connection.active?, "Bad connection use by 'MysqlAdapter.select_rows'" -    end -  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') @@ -183,7 +174,7 @@ class MysqlConnectionTest < ActiveRecord::MysqlTestCase    def with_example_table(&block)      definition ||= <<-SQL -      `id` int(11) auto_increment PRIMARY KEY, +      `id` int auto_increment PRIMARY KEY,        `data` varchar(255)      SQL      super(@connection, 'ex', definition, &block) diff --git a/activerecord/test/cases/adapters/mysql/explain_test.rb b/activerecord/test/cases/adapters/mysql/explain_test.rb new file mode 100644 index 0000000000..c44c1e6648 --- /dev/null +++ b/activerecord/test/cases/adapters/mysql/explain_test.rb @@ -0,0 +1,21 @@ +require "cases/helper" +require 'models/developer' +require 'models/computer' + +class MysqlExplainTest < ActiveRecord::MysqlTestCase +  fixtures :developers + +  def test_explain_for_one_query +    explain = Developer.where(id: 1).explain +    assert_match %(EXPLAIN for: SELECT `developers`.* FROM `developers` WHERE `developers`.`id` = 1), explain +    assert_match %r(developers |.* const), explain +  end + +  def test_explain_with_eager_loading +    explain = Developer.where(id: 1).includes(:audit_logs).explain +    assert_match %(EXPLAIN for: SELECT `developers`.* FROM `developers` WHERE `developers`.`id` = 1), explain +    assert_match %r(developers |.* const), explain +    assert_match %(EXPLAIN for: SELECT `audit_logs`.* FROM `audit_logs` WHERE `audit_logs`.`developer_id` = 1), explain +    assert_match %r(audit_logs |.* ALL), explain +  end +end diff --git a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb index b804cb45b9..d2ce48fc00 100644 --- a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb +++ b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb @@ -1,4 +1,3 @@ -  require "cases/helper"  require 'support/ddl_helper' @@ -66,40 +65,8 @@ module ActiveRecord          end        end -      def test_tables_quoting -        @conn.tables(nil, "foo-bar", nil) -        flunk -      rescue => e -        # assertion for *quoted* database properly -        assert_match(/database 'foo-bar'/, e.inspect) -      end - -      def test_pk_and_sequence_for -        with_example_table do -          pk, seq = @conn.pk_and_sequence_for('ex') -          assert_equal 'id', pk -          assert_equal @conn.default_sequence_name('ex', 'id'), seq -        end -      end - -      def test_pk_and_sequence_for_with_non_standard_primary_key -        with_example_table '`code` INT(11) auto_increment, PRIMARY KEY (`code`)' do -          pk, seq = @conn.pk_and_sequence_for('ex') -          assert_equal 'code', pk -          assert_equal @conn.default_sequence_name('ex', 'code'), seq -        end -      end - -      def test_pk_and_sequence_for_with_custom_index_type_pk -        with_example_table '`id` INT(11) auto_increment, PRIMARY KEY USING BTREE (`id`)' do -          pk, seq = @conn.pk_and_sequence_for('ex') -          assert_equal 'id', pk -          assert_equal @conn.default_sequence_name('ex', 'id'), seq -        end -      end -        def test_composite_primary_key -        with_example_table '`id` INT(11), `number` INT(11), foo INT(11), PRIMARY KEY (`id`, `number`)' do +        with_example_table '`id` INT, `number` INT, foo INT, PRIMARY KEY (`id`, `number`)' do            assert_nil @conn.primary_key('ex')          end        end @@ -141,7 +108,7 @@ module ActiveRecord        def with_example_table(definition = nil, &block)          definition ||= <<-SQL -          `id` int(11) auto_increment PRIMARY KEY, +          `id` int auto_increment PRIMARY KEY,            `number` integer,            `data` varchar(255)          SQL diff --git a/activerecord/test/cases/adapters/mysql/quoting_test.rb b/activerecord/test/cases/adapters/mysql/quoting_test.rb index a296cf9d31..2024aa36ab 100644 --- a/activerecord/test/cases/adapters/mysql/quoting_test.rb +++ b/activerecord/test/cases/adapters/mysql/quoting_test.rb @@ -12,4 +12,18 @@ class MysqlQuotingTest < ActiveRecord::MysqlTestCase    def test_type_cast_false      assert_equal 0, @conn.type_cast(false)    end + +  def test_quoted_date_precision_for_gte_564 +    @conn.stubs(:full_version).returns('5.6.4') +    @conn.remove_instance_variable(:@version) if @conn.instance_variable_defined?(:@version) +    t = Time.now.change(usec: 1) +    assert_match(/\.000001\z/, @conn.quoted_date(t)) +  end + +  def test_quoted_date_precision_for_lt_564 +    @conn.stubs(:full_version).returns('5.6.3') +    @conn.remove_instance_variable(:@version) if @conn.instance_variable_defined?(:@version) +    t = Time.now.change(usec: 1) +    assert_no_match(/\.000001\z/, @conn.quoted_date(t)) +  end  end diff --git a/activerecord/test/cases/adapters/mysql/sp_test.rb b/activerecord/test/cases/adapters/mysql/sp_test.rb index a3d5110032..579c3273c6 100644 --- a/activerecord/test/cases/adapters/mysql/sp_test.rb +++ b/activerecord/test/cases/adapters/mysql/sp_test.rb @@ -1,15 +1,29 @@  require "cases/helper"  require 'models/topic' +require 'models/reply' -class StoredProcedureTest < ActiveRecord::MysqlTestCase +class MysqlStoredProcedureTest < ActiveRecord::MysqlTestCase    fixtures :topics +  def setup +    @connection = ActiveRecord::Base.connection +  end +    # Test that MySQL allows multiple results for stored procedures -  if defined?(Mysql) && Mysql.const_defined?(:CLIENT_MULTI_RESULTS) +  # +  # In MySQL 5.6, CLIENT_MULTI_RESULTS is enabled by default. +  # http://dev.mysql.com/doc/refman/5.6/en/call.html +  if ActiveRecord::Base.connection.version >= '5.6.0' || Mysql.const_defined?(:CLIENT_MULTI_RESULTS) +    def test_multi_results +      rows = @connection.select_rows('CALL ten();') +      assert_equal 10, rows[0][0].to_i, "ten() did not return 10 as expected: #{rows.inspect}" +      assert @connection.active?, "Bad connection use by 'MysqlAdapter.select_rows'" +    end +      def test_multi_results_from_find_by_sql -      topics = Topic.find_by_sql 'CALL topics();' -      assert_equal 1, topics.size -      assert ActiveRecord::Base.connection.active?, "Bad connection use by 'MysqlAdapter.select'" +      topics = Topic.find_by_sql 'CALL topics(3);' +      assert_equal 3, topics.size +      assert @connection.active?, "Bad connection use by 'MysqlAdapter.select'"      end    end  end diff --git a/activerecord/test/cases/adapters/mysql/sql_types_test.rb b/activerecord/test/cases/adapters/mysql/sql_types_test.rb index 25b28de7f0..d18579f242 100644 --- a/activerecord/test/cases/adapters/mysql/sql_types_test.rb +++ b/activerecord/test/cases/adapters/mysql/sql_types_test.rb @@ -4,7 +4,7 @@ class MysqlSqlTypesTest < ActiveRecord::MysqlTestCase    def test_binary_types      assert_equal 'varbinary(64)', type_to_sql(:binary, 64)      assert_equal 'varbinary(4095)', type_to_sql(:binary, 4095) -    assert_equal 'blob(4096)', type_to_sql(:binary, 4096) +    assert_equal 'blob', type_to_sql(:binary, 4096)      assert_equal 'blob', type_to_sql(:binary)    end diff --git a/activerecord/test/cases/adapters/mysql/statement_pool_test.rb b/activerecord/test/cases/adapters/mysql/statement_pool_test.rb index 6be36566de..0d1f968022 100644 --- a/activerecord/test/cases/adapters/mysql/statement_pool_test.rb +++ b/activerecord/test/cases/adapters/mysql/statement_pool_test.rb @@ -3,7 +3,7 @@ require 'cases/helper'  class MysqlStatementPoolTest < ActiveRecord::MysqlTestCase    if Process.respond_to?(:fork)      def test_cache_is_per_pid -      cache = ActiveRecord::ConnectionAdapters::MysqlAdapter::StatementPool.new nil, 10 +      cache = ActiveRecord::ConnectionAdapters::MysqlAdapter::StatementPool.new(10)        cache['foo'] = 'bar'        assert_equal 'bar', cache['foo'] diff --git a/activerecord/test/cases/adapters/mysql/unsigned_type_test.rb b/activerecord/test/cases/adapters/mysql/unsigned_type_test.rb index ed9398a918..84c5394c2e 100644 --- a/activerecord/test/cases/adapters/mysql/unsigned_type_test.rb +++ b/activerecord/test/cases/adapters/mysql/unsigned_type_test.rb @@ -1,6 +1,8 @@  require "cases/helper" +require "support/schema_dumping_helper"  class MysqlUnsignedTypeTest < ActiveRecord::MysqlTestCase +  include SchemaDumpingHelper    self.use_transactional_tests = false    class UnsignedType < ActiveRecord::Base @@ -9,12 +11,15 @@ class MysqlUnsignedTypeTest < ActiveRecord::MysqlTestCase    setup do      @connection = ActiveRecord::Base.connection      @connection.create_table("unsigned_types", force: true) do |t| -      t.column :unsigned_integer, "int unsigned" +      t.integer :unsigned_integer, unsigned: true +      t.bigint  :unsigned_bigint,  unsigned: true +      t.float   :unsigned_float,   unsigned: true +      t.decimal :unsigned_decimal, unsigned: true, precision: 10, scale: 2      end    end    teardown do -    @connection.drop_table "unsigned_types" +    @connection.drop_table "unsigned_types", if_exists: true    end    test "unsigned int max value is in range" do @@ -26,5 +31,35 @@ class MysqlUnsignedTypeTest < ActiveRecord::MysqlTestCase      assert_raise(RangeError) do        UnsignedType.create(unsigned_integer: -10)      end +    assert_raise(RangeError) do +      UnsignedType.create(unsigned_bigint: -10) +    end +    assert_raise(ActiveRecord::StatementInvalid) do +      UnsignedType.create(unsigned_float: -10.0) +    end +    assert_raise(ActiveRecord::StatementInvalid) do +      UnsignedType.create(unsigned_decimal: -10.0) +    end +  end + +  test "schema definition can use unsigned as the type" do +    @connection.change_table("unsigned_types") do |t| +      t.unsigned_integer :unsigned_integer_t +      t.unsigned_bigint  :unsigned_bigint_t +      t.unsigned_float   :unsigned_float_t +      t.unsigned_decimal :unsigned_decimal_t, precision: 10, scale: 2 +    end + +    @connection.columns("unsigned_types").select { |c| /^unsigned_/ === c.name }.each do |column| +      assert column.unsigned? +    end +  end + +  test "schema dump includes unsigned option" do +    schema = dump_table_schema "unsigned_types" +    assert_match %r{t.integer\s+"unsigned_integer",\s+limit: 4,\s+unsigned: true$}, schema +    assert_match %r{t.integer\s+"unsigned_bigint",\s+limit: 8,\s+unsigned: true$}, schema +    assert_match %r{t.float\s+"unsigned_float",\s+limit: 24,\s+unsigned: true$}, schema +    assert_match %r{t.decimal\s+"unsigned_decimal",\s+precision: 10,\s+scale: 2,\s+unsigned: true$}, schema    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 6558d60aa1..31dc69a45b 100644 --- a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb +++ b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb @@ -100,17 +100,15 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase      assert_equal "DROP TABLE `people`", drop_table(:people)    end -  if current_adapter?(:Mysql2Adapter) -    def test_create_mysql_database_with_encoding -      assert_equal "CREATE DATABASE `matt` DEFAULT CHARACTER SET `utf8`", create_database(:matt) -      assert_equal "CREATE DATABASE `aimonetti` DEFAULT CHARACTER SET `latin1`", create_database(:aimonetti, {:charset => 'latin1'}) -      assert_equal "CREATE DATABASE `matt_aimonetti` DEFAULT CHARACTER SET `big5` COLLATE `big5_chinese_ci`", create_database(:matt_aimonetti, {:charset => :big5, :collation => :big5_chinese_ci}) -    end +  def test_create_mysql_database_with_encoding +    assert_equal "CREATE DATABASE `matt` DEFAULT CHARACTER SET `utf8`", create_database(:matt) +    assert_equal "CREATE DATABASE `aimonetti` DEFAULT CHARACTER SET `latin1`", create_database(:aimonetti, {:charset => 'latin1'}) +    assert_equal "CREATE DATABASE `matt_aimonetti` DEFAULT CHARACTER SET `big5` COLLATE `big5_chinese_ci`", create_database(:matt_aimonetti, {:charset => :big5, :collation => :big5_chinese_ci}) +  end -    def test_recreate_mysql_database_with_encoding -      create_database(:luca, {:charset => 'latin1'}) -      assert_equal "CREATE DATABASE `luca` DEFAULT CHARACTER SET `latin1`", recreate_database(:luca, {:charset => 'latin1'}) -    end +  def test_recreate_mysql_database_with_encoding +    create_database(:luca, {:charset => 'latin1'}) +    assert_equal "CREATE DATABASE `luca` DEFAULT CHARACTER SET `latin1`", recreate_database(:luca, {:charset => 'latin1'})    end    def test_add_column diff --git a/activerecord/test/cases/adapters/mysql2/json_test.rb b/activerecord/test/cases/adapters/mysql2/json_test.rb new file mode 100644 index 0000000000..c8c933af5e --- /dev/null +++ b/activerecord/test/cases/adapters/mysql2/json_test.rb @@ -0,0 +1,172 @@ +require 'cases/helper' +require 'support/schema_dumping_helper' + +if ActiveRecord::Base.connection.supports_json? +class Mysql2JSONTest < ActiveRecord::Mysql2TestCase +  include SchemaDumpingHelper +  self.use_transactional_tests = false + +  class JsonDataType < ActiveRecord::Base +    self.table_name = 'json_data_type' + +    store_accessor :settings, :resolution +  end + +  def setup +    @connection = ActiveRecord::Base.connection +    begin +      @connection.create_table('json_data_type') do |t| +        t.json 'payload' +        t.json 'settings' +      end +    end +  end + +  def teardown +    @connection.drop_table :json_data_type, if_exists: true +    JsonDataType.reset_column_information +  end + +  def test_column +    column = JsonDataType.columns_hash["payload"] +    assert_equal :json, column.type +    assert_equal 'json', column.sql_type + +    type = JsonDataType.type_for_attribute("payload") +    assert_not type.binary? +  end + +  def test_change_table_supports_json +    @connection.change_table('json_data_type') do |t| +      t.json 'users' +    end +    JsonDataType.reset_column_information +    column = JsonDataType.columns_hash['users'] +    assert_equal :json, column.type +  end + +  def test_schema_dumping +    output = dump_table_schema("json_data_type") +    assert_match(/t\.json\s+"settings"/, output) +  end + +  def test_cast_value_on_write +    x = JsonDataType.new payload: {"string" => "foo", :symbol => :bar} +    assert_equal({"string" => "foo", :symbol => :bar}, x.payload_before_type_cast) +    assert_equal({"string" => "foo", "symbol" => "bar"}, x.payload) +    x.save +    assert_equal({"string" => "foo", "symbol" => "bar"}, x.reload.payload) +  end + +  def test_type_cast_json +    type = JsonDataType.type_for_attribute("payload") + +    data = "{\"a_key\":\"a_value\"}" +    hash = type.deserialize(data) +    assert_equal({'a_key' => 'a_value'}, hash) +    assert_equal({'a_key' => 'a_value'}, type.deserialize(data)) + +    assert_equal({}, type.deserialize("{}")) +    assert_equal({'key'=>nil}, type.deserialize('{"key": null}')) +    assert_equal({'c'=>'}','"a"'=>'b "a b'}, type.deserialize(%q({"c":"}", "\"a\"":"b \"a b"}))) +  end + +  def test_rewrite +    @connection.execute "insert into json_data_type (payload) VALUES ('{\"k\":\"v\"}')" +    x = JsonDataType.first +    x.payload = { '"a\'' => 'b' } +    assert x.save! +  end + +  def test_select +    @connection.execute "insert into json_data_type (payload) VALUES ('{\"k\":\"v\"}')" +    x = JsonDataType.first +    assert_equal({'k' => 'v'}, x.payload) +  end + +  def test_select_multikey +    @connection.execute %q|insert into json_data_type (payload) VALUES ('{"k1":"v1", "k2":"v2", "k3":[1,2,3]}')| +    x = JsonDataType.first +    assert_equal({'k1' => 'v1', 'k2' => 'v2', 'k3' => [1,2,3]}, x.payload) +  end + +  def test_null_json +    @connection.execute %q|insert into json_data_type (payload) VALUES(null)| +    x = JsonDataType.first +    assert_equal(nil, x.payload) +  end + +  def test_select_array_json_value +    @connection.execute %q|insert into json_data_type (payload) VALUES ('["v0",{"k1":"v1"}]')| +    x = JsonDataType.first +    assert_equal(['v0', {'k1' => 'v1'}], x.payload) +  end + +  def test_rewrite_array_json_value +    @connection.execute %q|insert into json_data_type (payload) VALUES ('["v0",{"k1":"v1"}]')| +    x = JsonDataType.first +    x.payload = ['v1', {'k2' => 'v2'}, 'v3'] +    assert x.save! +  end + +  def test_with_store_accessors +    x = JsonDataType.new(resolution: "320×480") +    assert_equal "320×480", x.resolution + +    x.save! +    x = JsonDataType.first +    assert_equal "320×480", x.resolution + +    x.resolution = "640×1136" +    x.save! + +    x = JsonDataType.first +    assert_equal "640×1136", x.resolution +  end + +  def test_duplication_with_store_accessors +    x = JsonDataType.new(resolution: "320×480") +    assert_equal "320×480", x.resolution + +    y = x.dup +    assert_equal "320×480", y.resolution +  end + +  def test_yaml_round_trip_with_store_accessors +    x = JsonDataType.new(resolution: "320×480") +    assert_equal "320×480", x.resolution + +    y = YAML.load(YAML.dump(x)) +    assert_equal "320×480", y.resolution +  end + +  def test_changes_in_place +    json = JsonDataType.new +    assert_not json.changed? + +    json.payload = { 'one' => 'two' } +    assert json.changed? +    assert json.payload_changed? + +    json.save! +    assert_not json.changed? + +    json.payload['three'] = 'four' +    assert json.payload_changed? + +    json.save! +    json.reload + +    assert_equal({ 'one' => 'two', 'three' => 'four' }, json.payload) +    assert_not json.changed? +  end + +  def test_assigning_invalid_json +    json = JsonDataType.new + +    json.payload = 'foo' + +    assert_nil json.payload +  end +end +end diff --git a/activerecord/test/cases/adapters/mysql2/quoting_test.rb b/activerecord/test/cases/adapters/mysql2/quoting_test.rb new file mode 100644 index 0000000000..2de7e1b526 --- /dev/null +++ b/activerecord/test/cases/adapters/mysql2/quoting_test.rb @@ -0,0 +1,21 @@ +require "cases/helper" + +class Mysql2QuotingTest < ActiveRecord::Mysql2TestCase +  setup do +    @connection = ActiveRecord::Base.connection +  end + +  test 'quoted date precision for gte 5.6.4' do +    @connection.stubs(:full_version).returns('5.6.4') +    @connection.remove_instance_variable(:@version) if @connection.instance_variable_defined?(:@version) +    t = Time.now.change(usec: 1) +    assert_match(/\.000001\z/, @connection.quoted_date(t)) +  end + +  test 'quoted date precision for lt 5.6.4' do +    @connection.stubs(:full_version).returns('5.6.3') +    @connection.remove_instance_variable(:@version) if @connection.instance_variable_defined?(:@version) +    t = Time.now.change(usec: 1) +    assert_no_match(/\.000001\z/, @connection.quoted_date(t)) +  end +end diff --git a/activerecord/test/cases/adapters/mysql2/schema_test.rb b/activerecord/test/cases/adapters/mysql2/schema_test.rb index 880a2123d2..faf2acb9cb 100644 --- a/activerecord/test/cases/adapters/mysql2/schema_test.rb +++ b/activerecord/test/cases/adapters/mysql2/schema_test.rb @@ -36,14 +36,6 @@ module ActiveRecord          assert(!@connection.table_exists?("#{@db_name}.zomg"), "table should not exist")        end -      def test_tables_quoting -        @connection.tables(nil, "foo-bar", nil) -        flunk -      rescue => e -        # assertion for *quoted* database properly -        assert_match(/database 'foo-bar'/, e.inspect) -      end -        def test_dump_indexes          index_a_name = 'index_key_tests_on_snack'          index_b_name = 'index_key_tests_on_pizza' diff --git a/activerecord/test/cases/adapters/mysql2/sp_test.rb b/activerecord/test/cases/adapters/mysql2/sp_test.rb new file mode 100644 index 0000000000..8b12945f24 --- /dev/null +++ b/activerecord/test/cases/adapters/mysql2/sp_test.rb @@ -0,0 +1,29 @@ +require "cases/helper" +require 'models/topic' +require 'models/reply' + +class Mysql2StoredProcedureTest < ActiveRecord::Mysql2TestCase +  fixtures :topics + +  def setup +    @connection = ActiveRecord::Base.connection +  end + +  # Test that MySQL allows multiple results for stored procedures +  # +  # In MySQL 5.6, CLIENT_MULTI_RESULTS is enabled by default. +  # http://dev.mysql.com/doc/refman/5.6/en/call.html +  if ActiveRecord::Base.connection.version >= '5.6.0' +    def test_multi_results +      rows = @connection.select_rows('CALL ten();') +      assert_equal 10, rows[0][0].to_i, "ten() did not return 10 as expected: #{rows.inspect}" +      assert @connection.active?, "Bad connection use by 'Mysql2Adapter.select_rows'" +    end + +    def test_multi_results_from_find_by_sql +      topics = Topic.find_by_sql 'CALL topics(3);' +      assert_equal 3, topics.size +      assert @connection.active?, "Bad connection use by 'Mysql2Adapter.select'" +    end +  end +end diff --git a/activerecord/test/cases/adapters/mysql2/sql_types_test.rb b/activerecord/test/cases/adapters/mysql2/sql_types_test.rb index ae505d29c9..4926bc2267 100644 --- a/activerecord/test/cases/adapters/mysql2/sql_types_test.rb +++ b/activerecord/test/cases/adapters/mysql2/sql_types_test.rb @@ -4,7 +4,7 @@ class Mysql2SqlTypesTest < ActiveRecord::Mysql2TestCase    def test_binary_types      assert_equal 'varbinary(64)', type_to_sql(:binary, 64)      assert_equal 'varbinary(4095)', type_to_sql(:binary, 4095) -    assert_equal 'blob(4096)', type_to_sql(:binary, 4096) +    assert_equal 'blob', type_to_sql(:binary, 4096)      assert_equal 'blob', type_to_sql(:binary)    end diff --git a/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb b/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb index 9e06db2519..a6f6dd21bb 100644 --- a/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb +++ b/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb @@ -1,6 +1,8 @@  require "cases/helper" +require "support/schema_dumping_helper"  class Mysql2UnsignedTypeTest < ActiveRecord::Mysql2TestCase +  include SchemaDumpingHelper    self.use_transactional_tests = false    class UnsignedType < ActiveRecord::Base @@ -9,12 +11,15 @@ class Mysql2UnsignedTypeTest < ActiveRecord::Mysql2TestCase    setup do      @connection = ActiveRecord::Base.connection      @connection.create_table("unsigned_types", force: true) do |t| -      t.column :unsigned_integer, "int unsigned" +      t.integer :unsigned_integer, unsigned: true +      t.bigint  :unsigned_bigint,  unsigned: true +      t.float   :unsigned_float,   unsigned: true +      t.decimal :unsigned_decimal, unsigned: true, precision: 10, scale: 2      end    end    teardown do -    @connection.drop_table "unsigned_types" +    @connection.drop_table "unsigned_types", if_exists: true    end    test "unsigned int max value is in range" do @@ -26,5 +31,35 @@ class Mysql2UnsignedTypeTest < ActiveRecord::Mysql2TestCase      assert_raise(RangeError) do        UnsignedType.create(unsigned_integer: -10)      end +    assert_raise(RangeError) do +      UnsignedType.create(unsigned_bigint: -10) +    end +    assert_raise(ActiveRecord::StatementInvalid) do +      UnsignedType.create(unsigned_float: -10.0) +    end +    assert_raise(ActiveRecord::StatementInvalid) do +      UnsignedType.create(unsigned_decimal: -10.0) +    end +  end + +  test "schema definition can use unsigned as the type" do +    @connection.change_table("unsigned_types") do |t| +      t.unsigned_integer :unsigned_integer_t +      t.unsigned_bigint  :unsigned_bigint_t +      t.unsigned_float   :unsigned_float_t +      t.unsigned_decimal :unsigned_decimal_t, precision: 10, scale: 2 +    end + +    @connection.columns("unsigned_types").select { |c| /^unsigned_/ === c.name }.each do |column| +      assert column.unsigned? +    end +  end + +  test "schema dump includes unsigned option" do +    schema = dump_table_schema "unsigned_types" +    assert_match %r{t.integer\s+"unsigned_integer",\s+limit: 4,\s+unsigned: true$}, schema +    assert_match %r{t.integer\s+"unsigned_bigint",\s+limit: 8,\s+unsigned: true$}, schema +    assert_match %r{t.float\s+"unsigned_float",\s+limit: 24,\s+unsigned: true$}, schema +    assert_match %r{t.decimal\s+"unsigned_decimal",\s+precision: 10,\s+scale: 2,\s+unsigned: true$}, schema    end  end diff --git a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb index dc7ba314c6..24def31e36 100644 --- a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb +++ b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb @@ -25,7 +25,7 @@ class PostgresqlActiveSchemaTest < ActiveRecord::PostgreSQLTestCase    def test_add_index      # add_index calls index_name_exists? which can't work since execute is stubbed -    ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.stubs(:index_name_exists?).returns(false) +    ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.send(:define_method, :index_name_exists?) { |*| false }      expected = %(CREATE UNIQUE INDEX  "index_people_on_last_name" ON "people"  ("last_name") WHERE state = 'active')      assert_equal expected, add_index(:people, :last_name, :unique => true, :where => "state = 'active'") @@ -49,6 +49,22 @@ class PostgresqlActiveSchemaTest < ActiveRecord::PostgreSQLTestCase      expected = %(CREATE UNIQUE INDEX  "index_people_on_last_name" ON "people" USING gist ("last_name") WHERE state = 'active')      assert_equal expected, add_index(:people, :last_name, :unique => true, :where => "state = 'active'", :using => :gist) + +    ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.send :remove_method, :index_name_exists? +  end + +  def test_remove_index +    # remove_index calls index_name_exists? which can't work since execute is stubbed +    ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.send(:define_method, :index_name_exists?) { |*| true } + +    expected = %(DROP INDEX CONCURRENTLY "index_people_on_last_name") +    assert_equal expected, remove_index(:people, name: "index_people_on_last_name", algorithm: :concurrently) + +    assert_raise ArgumentError do +      add_index(:people, :last_name, algorithm: :copy) +    end + +    ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.send :remove_method, :index_name_exists?    end    private diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb index 820d41e13b..722e2377c1 100644 --- a/activerecord/test/cases/adapters/postgresql/connection_test.rb +++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb @@ -127,7 +127,7 @@ module ActiveRecord      def test_statement_key_is_logged        bind = Relation::QueryAttribute.new(nil, 1, Type::Value.new) -      @connection.exec_query('SELECT $1::integer', 'SQL', [bind]) +      @connection.exec_query('SELECT $1::integer', 'SQL', [bind], prepare: true)        name = @subscriber.payloads.last[:statement_name]        assert name        res = @connection.exec_query("EXPLAIN (FORMAT JSON) EXECUTE #{name}(1)") diff --git a/activerecord/test/cases/adapters/postgresql/json_test.rb b/activerecord/test/cases/adapters/postgresql/json_test.rb index f242f32496..b3b121b4fb 100644 --- a/activerecord/test/cases/adapters/postgresql/json_test.rb +++ b/activerecord/test/cases/adapters/postgresql/json_test.rb @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*-  require "cases/helper"  require 'support/schema_dumping_helper' diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb index 6e6850c4a9..e361521155 100644 --- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb +++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb @@ -123,6 +123,20 @@ module ActiveRecord          assert_equal expect.to_i, result.rows.first.first        end +      def test_exec_insert_default_values_with_returning_disabled_and_no_sequence_name_given +        connection = connection_without_insert_returning +        result = connection.exec_insert("insert into postgresql_partitioned_table_parent DEFAULT VALUES", nil, [], 'id') +        expect = connection.query('select max(id) from postgresql_partitioned_table_parent').first.first +        assert_equal expect.to_i, result.rows.first.first +      end + +      def test_exec_insert_default_values_quoted_schema_with_returning_disabled_and_no_sequence_name_given +        connection = connection_without_insert_returning +        result = connection.exec_insert('insert into "public"."postgresql_partitioned_table_parent" DEFAULT VALUES', nil, [], 'id') +        expect = connection.query('select max(id) from postgresql_partitioned_table_parent').first.first +        assert_equal expect.to_i, result.rows.first.first +      end +        def test_sql_for_insert_with_returning_disabled          connection = connection_without_insert_returning          result = connection.sql_for_insert('sql', nil, nil, nil, 'binds') diff --git a/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb b/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb index fa6584eae5..a0afd922b2 100644 --- a/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb +++ b/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb @@ -31,7 +31,7 @@ class SchemaAuthorizationTest < ActiveRecord::PostgreSQLTestCase      set_session_auth      @connection.execute "RESET search_path"      USERS.each do |u| -      @connection.execute "DROP SCHEMA #{u} CASCADE" +      @connection.drop_schema u        @connection.execute "DROP USER #{u}"      end    end diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb index 35d5581aa7..93e98ec872 100644 --- a/activerecord/test/cases/adapters/postgresql/schema_test.rb +++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb @@ -2,7 +2,19 @@ require "cases/helper"  require 'models/default'  require 'support/schema_dumping_helper' +module PGSchemaHelper +  def with_schema_search_path(schema_search_path) +    @connection.schema_search_path = schema_search_path +    @connection.schema_cache.clear! +    yield if block_given? +  ensure +    @connection.schema_search_path = "'$user', public" +    @connection.schema_cache.clear! +  end +end +  class SchemaTest < ActiveRecord::PostgreSQLTestCase +  include PGSchemaHelper    self.use_transactional_tests = false    SCHEMA_NAME = 'test_schema' @@ -84,8 +96,8 @@ class SchemaTest < ActiveRecord::PostgreSQLTestCase    end    teardown do -    @connection.execute "DROP SCHEMA #{SCHEMA2_NAME} CASCADE" -    @connection.execute "DROP SCHEMA #{SCHEMA_NAME} CASCADE" +    @connection.drop_schema SCHEMA2_NAME, if_exists: true +    @connection.drop_schema SCHEMA_NAME, if_exists: true    end    def test_schema_names @@ -121,10 +133,17 @@ class SchemaTest < ActiveRecord::PostgreSQLTestCase      assert !@connection.schema_names.include?("test_schema3")    end +  def test_drop_schema_if_exists +    @connection.create_schema "some_schema" +    assert_includes @connection.schema_names, "some_schema" +    @connection.drop_schema "some_schema", if_exists: true +    assert_not_includes @connection.schema_names, "some_schema" +  end +    def test_habtm_table_name_with_schema +    ActiveRecord::Base.connection.drop_schema "music", if_exists: true +    ActiveRecord::Base.connection.create_schema "music"      ActiveRecord::Base.connection.execute <<-SQL -      DROP SCHEMA IF EXISTS music CASCADE; -      CREATE SCHEMA music;        CREATE TABLE music.albums (id serial primary key);        CREATE TABLE music.songs (id serial primary key);        CREATE TABLE music.albums_songs (album_id integer, song_id integer); @@ -134,18 +153,22 @@ class SchemaTest < ActiveRecord::PostgreSQLTestCase      Album.create      assert_equal song, Song.includes(:albums).references(:albums).first    ensure -    ActiveRecord::Base.connection.execute "DROP SCHEMA music CASCADE;" +    ActiveRecord::Base.connection.drop_schema "music", if_exists: true    end -  def test_raise_drop_schema_with_nonexisting_schema +  def test_drop_schema_with_nonexisting_schema      assert_raises(ActiveRecord::StatementInvalid) do -      @connection.drop_schema "test_schema3" +      @connection.drop_schema "idontexist" +    end + +    assert_nothing_raised do +      @connection.drop_schema "idontexist", if_exists: true      end    end    def test_raise_wraped_exception_on_bad_prepare      assert_raises(ActiveRecord::StatementInvalid) do -      @connection.exec_query "select * from developers where id = ?", 'sql', [[nil, 1]] +      @connection.exec_query "select * from developers where id = ?", 'sql', [bind_param(1)]      end    end @@ -300,11 +323,11 @@ class SchemaTest < ActiveRecord::PostgreSQLTestCase    def test_with_uppercase_index_name      @connection.execute "CREATE INDEX \"things_Index\" ON #{SCHEMA_NAME}.things (name)" -    assert_nothing_raised { @connection.remove_index! "things", "#{SCHEMA_NAME}.things_Index"} +    assert_nothing_raised { @connection.remove_index "things", name: "#{SCHEMA_NAME}.things_Index"}      @connection.execute "CREATE INDEX \"things_Index\" ON #{SCHEMA_NAME}.things (name)"      with_schema_search_path SCHEMA_NAME do -      assert_nothing_raised { @connection.remove_index! "things", "things_Index"} +      assert_nothing_raised { @connection.remove_index "things", name: "things_Index"}      end    end @@ -404,13 +427,6 @@ class SchemaTest < ActiveRecord::PostgreSQLTestCase        end      end -    def with_schema_search_path(schema_search_path) -      @connection.schema_search_path = schema_search_path -      yield if block_given? -    ensure -      @connection.schema_search_path = "'$user', public" -    end -      def do_dump_index_tests_for_schema(this_schema_name, first_index_column_name, second_index_column_name, third_index_column_name, fourth_index_column_name)        with_schema_search_path(this_schema_name) do          indexes = @connection.indexes(TABLE_NAME).sort_by(&:name) @@ -462,14 +478,14 @@ class SchemaForeignKeyTest < ActiveRecord::PostgreSQLTestCase    ensure      @connection.drop_table "wagons", if_exists: true      @connection.drop_table "my_schema.trains", if_exists: true -    @connection.execute "DROP SCHEMA IF EXISTS my_schema" +    @connection.drop_schema "my_schema", if_exists: true    end  end  class DefaultsUsingMultipleSchemasAndDomainTest < ActiveRecord::PostgreSQLTestCase    setup do      @connection = ActiveRecord::Base.connection -    @connection.execute "DROP SCHEMA IF EXISTS schema_1 CASCADE" +    @connection.drop_schema "schema_1", if_exists: true      @connection.execute "CREATE SCHEMA schema_1"      @connection.execute "CREATE DOMAIN schema_1.text AS text"      @connection.execute "CREATE DOMAIN schema_1.varchar AS varchar" @@ -487,7 +503,7 @@ class DefaultsUsingMultipleSchemasAndDomainTest < ActiveRecord::PostgreSQLTestCa    teardown do      @connection.schema_search_path = @old_search_path -    @connection.execute "DROP SCHEMA IF EXISTS schema_1 CASCADE" +    @connection.drop_schema "schema_1", if_exists: true      Default.reset_column_information    end @@ -519,3 +535,40 @@ class DefaultsUsingMultipleSchemasAndDomainTest < ActiveRecord::PostgreSQLTestCa      assert_equal "foo'::bar", Default.new.string_col    end  end + +class SchemaWithDotsTest < ActiveRecord::PostgreSQLTestCase +  include PGSchemaHelper +  self.use_transactional_tests = false + +  setup do +    @connection = ActiveRecord::Base.connection +    @connection.create_schema "my.schema" +  end + +  teardown do +    @connection.drop_schema "my.schema", if_exists: true +  end + +  test "rename_table" do +    with_schema_search_path('"my.schema"') do +      @connection.create_table :posts +      @connection.rename_table :posts, :articles +      assert_equal ["articles"], @connection.tables +    end +  end + +  test "Active Record basics" do +    with_schema_search_path('"my.schema"') do +      @connection.create_table :articles do |t| +        t.string :title +      end +      article_class = Class.new(ActiveRecord::Base) do +        self.table_name = '"my.schema".articles' +      end + +      article_class.create!(title: "zOMG, welcome to my blorgh!") +      welcome_article = article_class.last +      assert_equal "zOMG, welcome to my blorgh!", welcome_article.title +    end +  end +end diff --git a/activerecord/test/cases/adapters/postgresql/view_test.rb b/activerecord/test/cases/adapters/postgresql/view_test.rb deleted file mode 100644 index 2dd6ec5fe6..0000000000 --- a/activerecord/test/cases/adapters/postgresql/view_test.rb +++ /dev/null @@ -1,64 +0,0 @@ -require "cases/helper" -require "cases/view_test" - -class UpdateableViewTest < ActiveRecord::PostgreSQLTestCase -  fixtures :books - -  class PrintedBook < ActiveRecord::Base -    self.primary_key = "id" -  end - -  setup do -    @connection = ActiveRecord::Base.connection -    @connection.execute <<-SQL -      CREATE VIEW printed_books -        AS SELECT id, name, status, format FROM books WHERE format = 'paperback' -    SQL -  end - -  teardown do -    @connection.execute "DROP VIEW printed_books" if @connection.table_exists? "printed_books" -  end - -  def test_update_record -    book = PrintedBook.first -    book.name = "AWDwR" -    book.save! -    book.reload -    assert_equal "AWDwR", book.name -  end - -  def test_insert_record -    PrintedBook.create! name: "Rails in Action", status: 0, format: "paperback" - -    new_book = PrintedBook.last -    assert_equal "Rails in Action", new_book.name -  end - -  def test_update_record_to_fail_view_conditions -    book = PrintedBook.first -    book.format = "ebook" -    book.save! - -    assert_raises ActiveRecord::RecordNotFound do -      book.reload -    end -  end -end - -if ActiveRecord::Base.connection.respond_to?(:supports_materialized_views?) && -    ActiveRecord::Base.connection.supports_materialized_views? -class MaterializedViewTest < ActiveRecord::PostgreSQLTestCase -  include ViewBehavior - -  private -  def create_view(name, query) -    @connection.execute "CREATE MATERIALIZED VIEW #{name} AS #{query}" -  end - -  def drop_view(name) -    @connection.execute "DROP MATERIALIZED VIEW #{name}" if @connection.table_exists? name - -  end -end -end diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb index 7996e7ad50..77d99bc116 100644 --- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb @@ -425,13 +425,16 @@ module ActiveRecord                                     configurations['arunit']['database'])          statement = ::SQLite3::Statement.new(db,                                             'CREATE TABLE statement_test (number integer not null)') -        statement.stubs(:step).raises(::SQLite3::BusyException, 'busy') -        statement.stubs(:columns).once.returns([]) -        statement.expects(:close).once -        ::SQLite3::Statement.stubs(:new).returns(statement) - -        assert_raises ActiveRecord::StatementInvalid do -          @conn.exec_query 'select * from statement_test' +        statement.stub(:step, ->{ raise ::SQLite3::BusyException.new('busy') }) do +          assert_called(statement, :columns, returns: []) do +            assert_called(statement, :close) do +              ::SQLite3::Statement.stub(:new, statement) do +                assert_raises ActiveRecord::StatementInvalid do +                  @conn.exec_query 'select * from statement_test' +                end +              end +            end +          end          end        end diff --git a/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb b/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb index ef324183a7..559b951109 100644 --- a/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb @@ -6,7 +6,7 @@ module ActiveRecord::ConnectionAdapters        if Process.respond_to?(:fork)          def test_cache_is_per_pid -          cache = StatementPool.new nil, 10 +          cache = StatementPool.new(10)            cache['foo'] = 'bar'            assert_equal 'bar', cache['foo'] diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb index ebe08c618f..938350627f 100644 --- a/activerecord/test/cases/associations/belongs_to_associations_test.rb +++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb @@ -1,6 +1,5 @@  require 'cases/helper'  require 'models/developer' -require 'models/computer'  require 'models/project'  require 'models/company'  require 'models/topic' @@ -21,6 +20,9 @@ require 'models/column'  require 'models/record'  require 'models/admin'  require 'models/admin/user' +require 'models/ship' +require 'models/treasure' +require 'models/parrot'  class BelongsToAssociationsTest < ActiveRecord::TestCase    fixtures :accounts, :companies, :developers, :projects, :topics, @@ -91,7 +93,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase      end      account = model.new -    refute account.valid? +    assert_not account.valid?      assert_equal [{error: :blank}], account.errors.details[:company]    ensure      ActiveRecord::Base.belongs_to_required_by_default = original_value @@ -108,7 +110,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase      end      account = model.new -    refute account.valid? +    assert_not account.valid?      assert_equal [{error: :blank}], account.errors.details[:company]    ensure      ActiveRecord::Base.belongs_to_required_by_default = original_value @@ -293,7 +295,8 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase      client = Client.find(3)      client.firm = nil      client.save -    assert_nil client.firm(true) +    client.association(:firm).reload +    assert_nil client.firm      assert_nil client.client_of    end @@ -301,7 +304,8 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase      client = Client.create(:name => "Primary key client", :firm_name => companies(:first_firm).name)      client.firm_with_primary_key = nil      client.save -    assert_nil client.firm_with_primary_key(true) +    client.association(:firm_with_primary_key).reload +    assert_nil client.firm_with_primary_key      assert_nil client.client_of    end @@ -318,11 +322,13 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase    def test_polymorphic_association_class      sponsor = Sponsor.new      assert_nil sponsor.association(:sponsorable).send(:klass) -    assert_nil sponsor.sponsorable(force_reload: true) +    sponsor.association(:sponsorable).reload +    assert_nil sponsor.sponsorable      sponsor.sponsorable_type = '' # the column doesn't have to be declared NOT NULL      assert_nil sponsor.association(:sponsorable).send(:klass) -    assert_nil sponsor.sponsorable(force_reload: true) +    sponsor.association(:sponsorable).reload +    assert_nil sponsor.sponsorable      sponsor.sponsorable = Member.new :name => "Bert"      assert_equal Member, sponsor.association(:sponsorable).send(:klass) @@ -343,6 +349,22 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase      assert_equal 1, Company.all.merge!(:includes => :firm_with_select ).find(2).firm_with_select.attributes.size    end +  def test_belongs_to_without_counter_cache_option +    # Ship has a conventionally named `treasures_count` column, but the counter_cache +    # option is not given on the association. +    ship = Ship.create(name: 'Countless') + +    assert_no_difference lambda { ship.reload.treasures_count }, "treasures_count should not be changed unless counter_cache is given on the relation" do +      treasure = Treasure.new(name: 'Gold', ship: ship) +      treasure.save +    end + +    assert_no_difference lambda { ship.reload.treasures_count }, "treasures_count should not be changed unless counter_cache is given on the relation" do +      treasure = ship.treasures.first +      treasure.destroy +    end +  end +    def test_belongs_to_counter      debate = Topic.create("title" => "debate")      assert_equal 0, debate.read_attribute("replies_count"), "No replies yet" @@ -478,7 +500,9 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase      line_item = LineItem.create!      invoice = Invoice.create!(line_items: [line_item])      initial = invoice.updated_at -    line_item.touch +    travel(1.second) do +      line_item.touch +    end      assert_not_equal initial, invoice.reload.updated_at    end @@ -557,7 +581,8 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase      assert final_cut.persisted?      assert firm.persisted?      assert_equal firm, final_cut.firm -    assert_equal firm, final_cut.firm(true) +    final_cut.association(:firm).reload +    assert_equal firm, final_cut.firm    end    def test_assignment_before_child_saved_with_primary_key @@ -569,7 +594,8 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase      assert final_cut.persisted?      assert firm.persisted?      assert_equal firm, final_cut.firm_with_primary_key -    assert_equal firm, final_cut.firm_with_primary_key(true) +    final_cut.association(:firm_with_primary_key).reload +    assert_equal firm, final_cut.firm_with_primary_key    end    def test_new_record_with_foreign_key_but_no_object @@ -1064,6 +1090,12 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase      Column.create! record: record      assert_equal 1, Column.count    end + +  def test_association_force_reload_with_only_true_is_deprecated +    client = Client.find(3) + +    assert_deprecated { client.firm(true) } +  end  end  class BelongsToWithForeignKeyTest < ActiveRecord::TestCase diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb index ffbf60e390..ddfb856a05 100644 --- a/activerecord/test/cases/associations/eager_test.rb +++ b/activerecord/test/cases/associations/eager_test.rb @@ -108,53 +108,57 @@ class EagerAssociationTest < ActiveRecord::TestCase    end    def test_preloading_has_many_in_multiple_queries_with_more_ids_than_database_can_handle -    Comment.connection.expects(:in_clause_length).at_least_once.returns(5) -    posts = Post.all.merge!(:includes=>:comments).to_a -    assert_equal 11, posts.size +    assert_called(Comment.connection, :in_clause_length, returns: 5) do +      posts = Post.all.merge!(:includes=>:comments).to_a +      assert_equal 11, posts.size +    end    end    def test_preloading_has_many_in_one_queries_when_database_has_no_limit_on_ids_it_can_handle -    Comment.connection.expects(:in_clause_length).at_least_once.returns(nil) -    posts = Post.all.merge!(:includes=>:comments).to_a -    assert_equal 11, posts.size +    assert_called(Comment.connection, :in_clause_length, returns: nil) do +      posts = Post.all.merge!(:includes=>:comments).to_a +      assert_equal 11, posts.size +    end    end    def test_preloading_habtm_in_multiple_queries_with_more_ids_than_database_can_handle -    Comment.connection.expects(:in_clause_length).at_least_once.returns(5) -    posts = Post.all.merge!(:includes=>:categories).to_a -    assert_equal 11, posts.size +    assert_called(Comment.connection, :in_clause_length, times: 2, returns: 5) do +      posts = Post.all.merge!(:includes=>:categories).to_a +      assert_equal 11, posts.size +    end    end    def test_preloading_habtm_in_one_queries_when_database_has_no_limit_on_ids_it_can_handle -    Comment.connection.expects(:in_clause_length).at_least_once.returns(nil) -    posts = Post.all.merge!(:includes=>:categories).to_a -    assert_equal 11, posts.size +    assert_called(Comment.connection, :in_clause_length, times: 2, returns: nil) do +      posts = Post.all.merge!(:includes=>:categories).to_a +      assert_equal 11, posts.size +    end    end    def test_load_associated_records_in_one_query_when_adapter_has_no_limit -    Comment.connection.expects(:in_clause_length).at_least_once.returns(nil) - -    post = posts(:welcome) -    assert_queries(2) do -      Post.includes(:comments).where(:id => post.id).to_a +    assert_called(Comment.connection, :in_clause_length, returns: nil) do +      post = posts(:welcome) +      assert_queries(2) do +        Post.includes(:comments).where(:id => post.id).to_a +      end      end    end    def test_load_associated_records_in_several_queries_when_many_ids_passed -    Comment.connection.expects(:in_clause_length).at_least_once.returns(1) - -    post1, post2 = posts(:welcome), posts(:thinking) -    assert_queries(3) do -      Post.includes(:comments).where(:id => [post1.id, post2.id]).to_a +    assert_called(Comment.connection, :in_clause_length, returns: 1) do +      post1, post2 = posts(:welcome), posts(:thinking) +      assert_queries(3) do +        Post.includes(:comments).where(:id => [post1.id, post2.id]).to_a +      end      end    end    def test_load_associated_records_in_one_query_when_a_few_ids_passed -    Comment.connection.expects(:in_clause_length).at_least_once.returns(3) - -    post = posts(:welcome) -    assert_queries(2) do -      Post.includes(:comments).where(:id => post.id).to_a +    assert_called(Comment.connection, :in_clause_length, returns: 3) do +      post = posts(:welcome) +      assert_queries(2) do +        Post.includes(:comments).where(:id => post.id).to_a +      end      end    end @@ -1325,6 +1329,14 @@ class EagerAssociationTest < ActiveRecord::TestCase      assert_match message, error.message    end +  test "preload with invalid argument" do +    exception = assert_raises(ArgumentError) do +      Author.preload(10).to_a +    end +    assert_equal('10 was not recognized for preload', exception.message) +  end + +    test "preloading readonly association" do      # has-one      firm = Firm.where(id: "1").preload(:readonly_account).first! diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb index e584c94ad8..20af436e02 100644 --- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb @@ -3,6 +3,7 @@ require 'models/developer'  require 'models/computer'  require 'models/project'  require 'models/company' +require 'models/course'  require 'models/customer'  require 'models/order'  require 'models/categorization' @@ -14,6 +15,7 @@ require 'models/tagging'  require 'models/parrot'  require 'models/person'  require 'models/pirate' +require 'models/professor'  require 'models/treasure'  require 'models/price_estimate'  require 'models/club' @@ -93,6 +95,15 @@ class DeveloperWithExtendOption < Developer    has_and_belongs_to_many :projects, extend: NamedExtension  end +class ProjectUnscopingDavidDefaultScope < ActiveRecord::Base +  self.table_name = 'projects' +  has_and_belongs_to_many :developers, -> { unscope(where: 'name') }, +    class_name: "LazyBlockDeveloperCalledDavid", +    join_table: "developers_projects", +    foreign_key: "project_id", +    association_foreign_key: "developer_id" +end +  class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase    fixtures :accounts, :companies, :categories, :posts, :categories_posts, :developers, :projects, :developers_projects,             :parrots, :pirates, :parrots_pirates, :treasures, :price_estimates, :tags, :taggings, :computers @@ -157,8 +168,8 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase      jamis.projects << action_controller      assert_equal 2, jamis.projects.size -    assert_equal 2, jamis.projects(true).size -    assert_equal 2, action_controller.developers(true).size +    assert_equal 2, jamis.projects.reload.size +    assert_equal 2, action_controller.developers.reload.size    end    def test_adding_type_mismatch @@ -176,9 +187,9 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase      action_controller.developers << jamis -    assert_equal 2, jamis.projects(true).size +    assert_equal 2, jamis.projects.reload.size      assert_equal 2, action_controller.developers.size -    assert_equal 2, action_controller.developers(true).size +    assert_equal 2, action_controller.developers.reload.size    end    def test_adding_from_the_project_fixed_timestamp @@ -192,9 +203,9 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase      action_controller.developers << jamis      assert_equal updated_at, jamis.updated_at -    assert_equal 2, jamis.projects(true).size +    assert_equal 2, jamis.projects.reload.size      assert_equal 2, action_controller.developers.size -    assert_equal 2, action_controller.developers(true).size +    assert_equal 2, action_controller.developers.reload.size    end    def test_adding_multiple @@ -203,7 +214,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase      aredridel.projects.reload      aredridel.projects.push(Project.find(1), Project.find(2))      assert_equal 2, aredridel.projects.size -    assert_equal 2, aredridel.projects(true).size +    assert_equal 2, aredridel.projects.reload.size    end    def test_adding_a_collection @@ -212,7 +223,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase      aredridel.projects.reload      aredridel.projects.concat([Project.find(1), Project.find(2)])      assert_equal 2, aredridel.projects.size -    assert_equal 2, aredridel.projects(true).size +    assert_equal 2, aredridel.projects.reload.size    end    def test_habtm_adding_before_save @@ -227,7 +238,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase      assert_equal no_of_devels+1, Developer.count      assert_equal no_of_projects+1, Project.count      assert_equal 2, aredridel.projects.size -    assert_equal 2, aredridel.projects(true).size +    assert_equal 2, aredridel.projects.reload.size    end    def test_habtm_saving_multiple_relationships @@ -391,8 +402,8 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase      david.projects.delete(active_record)      assert_equal 1, david.projects.size -    assert_equal 1, david.projects(true).size -    assert_equal 2, active_record.developers(true).size +    assert_equal 1, david.projects.reload.size +    assert_equal 2, active_record.developers.reload.size    end    def test_deleting_array @@ -400,7 +411,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase      david.projects.reload      david.projects.delete(Project.all.to_a)      assert_equal 0, david.projects.size -    assert_equal 0, david.projects(true).size +    assert_equal 0, david.projects.reload.size    end    def test_deleting_all @@ -408,7 +419,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase      david.projects.reload      david.projects.clear      assert_equal 0, david.projects.size -    assert_equal 0, david.projects(true).size +    assert_equal 0, david.projects.reload.size    end    def test_removing_associations_on_destroy @@ -434,7 +445,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase      assert join_records.empty?      assert_equal 1, david.reload.projects.size -    assert_equal 1, david.projects(true).size +    assert_equal 1, david.projects.reload.size    end    def test_destroying_many @@ -450,7 +461,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase      assert join_records.empty?      assert_equal 0, david.reload.projects.size -    assert_equal 0, david.projects(true).size +    assert_equal 0, david.projects.reload.size    end    def test_destroy_all @@ -466,7 +477,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase      assert join_records.empty?      assert david.projects.empty? -    assert david.projects(true).empty? +    assert david.projects.reload.empty?    end    def test_destroy_associations_destroys_multiple_associations @@ -482,11 +493,11 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase      join_records = Parrot.connection.select_all("SELECT * FROM parrots_pirates WHERE parrot_id = #{george.id}")      assert join_records.empty? -    assert george.pirates(true).empty? +    assert george.pirates.reload.empty?      join_records = Parrot.connection.select_all("SELECT * FROM parrots_treasures WHERE parrot_id = #{george.id}")      assert join_records.empty? -    assert george.treasures(true).empty? +    assert george.treasures.reload.empty?    end    def test_associations_with_conditions @@ -654,7 +665,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase    end    def test_habtm_respects_select -    categories(:technology).select_testing_posts(true).each do |o| +    categories(:technology).select_testing_posts.reload.each do |o|        assert_respond_to o, :correctness_marker      end      assert_respond_to categories(:technology).select_testing_posts.first, :correctness_marker @@ -726,7 +737,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase    def test_get_ids_for_loaded_associations      developer = developers(:david) -    developer.projects(true) +    developer.projects.reload      assert_queries(0) do        developer.project_ids        developer.project_ids @@ -794,9 +805,10 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase    end    def test_association_proxy_transaction_method_starts_transaction_in_association_class -    Post.expects(:transaction) -    Category.first.posts.transaction do -      # nothing +    assert_called(Post, :transaction) do +      Category.first.posts.transaction do +        # nothing +      end      end    end @@ -917,4 +929,32 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase        DeveloperWithSymbolClassName.new      end    end + +  def test_association_force_reload_with_only_true_is_deprecated +    developer = Developer.find(1) + +    assert_deprecated { developer.projects(true) } +  end + +  def test_alternate_database +    professor = Professor.create(name: "Plum") +    course = Course.create(name: "Forensics") +    assert_equal 0, professor.courses.count +    assert_nothing_raised do +      professor.courses << course +    end +    assert_equal 1, professor.courses.count +  end + +  def test_habtm_scope_can_unscope +    project = ProjectUnscopingDavidDefaultScope.new +    project.save! + +    developer = LazyBlockDeveloperCalledDavid.new(name: "Not David") +    developer.save! +    project.developers << developer + +    projects = ProjectUnscopingDavidDefaultScope.includes(:developers).where(id: project.id) +    assert_equal 1, projects.first.developers.size +  end  end diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index 2c4e2a875c..eb94870a35 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -31,6 +31,8 @@ require 'models/student'  require 'models/pirate'  require 'models/ship'  require 'models/ship_part' +require 'models/treasure' +require 'models/parrot'  require 'models/tyre'  require 'models/subscriber'  require 'models/subscription' @@ -168,7 +170,9 @@ class HasManyAssociationsTest < ActiveRecord::TestCase      part = ShipPart.create(name: 'cockpit')      updated_at = part.updated_at -    ship.parts << part +    travel(1.second) do +      ship.parts << part +    end      assert_equal part.ship, ship      assert_not_equal part.updated_at, updated_at @@ -704,7 +708,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase      natural = Client.new("name" => "Natural Company")      companies(:first_firm).clients_of_firm << natural      assert_equal 3, companies(:first_firm).clients_of_firm.size # checking via the collection -    assert_equal 3, companies(:first_firm).clients_of_firm(true).size # checking using the db +    assert_equal 3, companies(:first_firm).clients_of_firm.reload.size # checking using the db      assert_equal natural, companies(:first_firm).clients_of_firm.last    end @@ -759,7 +763,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase      force_signal37_to_load_all_clients_of_firm      companies(:first_firm).clients_of_firm.concat([Client.new("name" => "Natural Company"), Client.new("name" => "Apple")])      assert_equal 4, companies(:first_firm).clients_of_firm.size -    assert_equal 4, companies(:first_firm).clients_of_firm(true).size +    assert_equal 4, companies(:first_firm).clients_of_firm.reload.size    end    def test_transactions_when_adding_to_persisted @@ -771,7 +775,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase      rescue Client::RaisedOnSave      end -    assert !companies(:first_firm).clients_of_firm(true).include?(good) +    assert !companies(:first_firm).clients_of_firm.reload.include?(good)    end    def test_transactions_when_adding_to_new_record @@ -903,12 +907,12 @@ class HasManyAssociationsTest < ActiveRecord::TestCase      new_client = companies(:first_firm).clients_of_firm.create("name" => "Another Client")      assert new_client.persisted?      assert_equal new_client, companies(:first_firm).clients_of_firm.last -    assert_equal new_client, companies(:first_firm).clients_of_firm(true).last +    assert_equal new_client, companies(:first_firm).clients_of_firm.reload.last    end    def test_create_many      companies(:first_firm).clients_of_firm.create([{"name" => "Another Client"}, {"name" => "Another Client II"}]) -    assert_equal 4, companies(:first_firm).clients_of_firm(true).size +    assert_equal 4, companies(:first_firm).clients_of_firm.reload.size    end    def test_create_followed_by_save_does_not_load_target @@ -921,7 +925,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase      force_signal37_to_load_all_clients_of_firm      companies(:first_firm).clients_of_firm.delete(companies(:first_firm).clients_of_firm.first)      assert_equal 1, companies(:first_firm).clients_of_firm.size -    assert_equal 1, companies(:first_firm).clients_of_firm(true).size +    assert_equal 1, companies(:first_firm).clients_of_firm.reload.size    end    def test_deleting_before_save @@ -932,6 +936,25 @@ class HasManyAssociationsTest < ActiveRecord::TestCase      assert_equal 0, new_firm.clients_of_firm.size    end +  def test_has_many_without_counter_cache_option +    # Ship has a conventionally named `treasures_count` column, but the counter_cache +    # option is not given on the association. +    ship = Ship.create(name: 'Countless', treasures_count: 10) + +    assert_not Ship.reflect_on_association(:treasures).has_cached_counter? + +    # Count should come from sql count() of treasures rather than treasures_count attribute +    assert_equal ship.treasures.size, 0 + +    assert_no_difference lambda { ship.reload.treasures_count }, "treasures_count should not be changed" do +      ship.treasures.create(name: 'Gold') +    end + +    assert_no_difference lambda { ship.reload.treasures_count }, "treasures_count should not be changed" do +      ship.treasures.destroy_all +    end +  end +    def test_deleting_updates_counter_cache      topic = Topic.order("id ASC").first      assert_equal topic.replies.to_a.size, topic.replies_count @@ -1058,7 +1081,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase      assert_equal 3, companies(:first_firm).clients_of_firm.size      companies(:first_firm).clients_of_firm.delete([companies(:first_firm).clients_of_firm[0], companies(:first_firm).clients_of_firm[1], companies(:first_firm).clients_of_firm[2]])      assert_equal 0, companies(:first_firm).clients_of_firm.size -    assert_equal 0, companies(:first_firm).clients_of_firm(true).size +    assert_equal 0, companies(:first_firm).clients_of_firm.reload.size    end    def test_delete_all @@ -1079,7 +1102,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase      companies(:first_firm).clients_of_firm.reset      companies(:first_firm).clients_of_firm.delete_all      assert_equal 0, companies(:first_firm).clients_of_firm.size -    assert_equal 0, companies(:first_firm).clients_of_firm(true).size +    assert_equal 0, companies(:first_firm).clients_of_firm.reload.size    end    def test_transaction_when_deleting_persisted @@ -1093,7 +1116,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase      rescue Client::RaisedOnDestroy      end -    assert_equal [good, bad], companies(:first_firm).clients_of_firm(true) +    assert_equal [good, bad], companies(:first_firm).clients_of_firm.reload    end    def test_transaction_when_deleting_new_record @@ -1113,7 +1136,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase      firm.clients_of_firm.clear      assert_equal 0, firm.clients_of_firm.size -    assert_equal 0, firm.clients_of_firm(true).size +    assert_equal 0, firm.clients_of_firm.reload.size      assert_equal [], Client.destroyed_client_ids[firm.id]      # Should not be destroyed since the association is not dependent. @@ -1149,7 +1172,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase      firm.dependent_clients_of_firm.clear      assert_equal 0, firm.dependent_clients_of_firm.size -    assert_equal 0, firm.dependent_clients_of_firm(true).size +    assert_equal 0, firm.dependent_clients_of_firm.reload.size      assert_equal [], Client.destroyed_client_ids[firm.id]      # Should be destroyed since the association is dependent. @@ -1182,7 +1205,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase      firm.exclusively_dependent_clients_of_firm.clear      assert_equal 0, firm.exclusively_dependent_clients_of_firm.size -    assert_equal 0, firm.exclusively_dependent_clients_of_firm(true).size +    assert_equal 0, firm.exclusively_dependent_clients_of_firm.reload.size      # no destroy-filters should have been called      assert_equal [], Client.destroyed_client_ids[firm.id] @@ -1231,7 +1254,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase      # break the vanilla firm_id foreign key      assert_equal 3, firm.clients.count      firm.clients.first.update_columns(firm_id: nil) -    assert_equal 2, firm.clients(true).count +    assert_equal 2, firm.clients.reload.count      assert_equal 2, firm.clients_using_primary_key_with_delete_all.count      old_record = firm.clients_using_primary_key_with_delete_all.first      firm = Firm.first @@ -1257,7 +1280,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase      firm.clients_of_firm.clear      assert_equal 0, firm.clients_of_firm.size -    assert_equal 0, firm.clients_of_firm(true).size +    assert_equal 0, firm.clients_of_firm.reload.size    end    def test_deleting_a_item_which_is_not_in_the_collection @@ -1265,7 +1288,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase      summit = Client.find_by_name('Summit')      companies(:first_firm).clients_of_firm.delete(summit)      assert_equal 2, companies(:first_firm).clients_of_firm.size -    assert_equal 2, companies(:first_firm).clients_of_firm(true).size +    assert_equal 2, companies(:first_firm).clients_of_firm.reload.size      assert_equal 2, summit.client_of    end @@ -1303,7 +1326,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase      end      assert_equal 1, companies(:first_firm).reload.clients_of_firm.size -    assert_equal 1, companies(:first_firm).clients_of_firm(true).size +    assert_equal 1, companies(:first_firm).clients_of_firm.reload.size    end    def test_destroying_by_fixnum_id @@ -1314,7 +1337,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase      end      assert_equal 1, companies(:first_firm).reload.clients_of_firm.size -    assert_equal 1, companies(:first_firm).clients_of_firm(true).size +    assert_equal 1, companies(:first_firm).clients_of_firm.reload.size    end    def test_destroying_by_string_id @@ -1325,7 +1348,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase      end      assert_equal 1, companies(:first_firm).reload.clients_of_firm.size -    assert_equal 1, companies(:first_firm).clients_of_firm(true).size +    assert_equal 1, companies(:first_firm).clients_of_firm.reload.size    end    def test_destroying_a_collection @@ -1338,7 +1361,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase      end      assert_equal 1, companies(:first_firm).reload.clients_of_firm.size -    assert_equal 1, companies(:first_firm).clients_of_firm(true).size +    assert_equal 1, companies(:first_firm).clients_of_firm.reload.size    end    def test_destroy_all @@ -1349,7 +1372,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase      assert_equal clients.sort_by(&:id), destroyed.sort_by(&:id)      assert destroyed.all?(&:frozen?), "destroyed clients should be frozen"      assert companies(:first_firm).clients_of_firm.empty?, "37signals has no clients after destroy all" -    assert companies(:first_firm).clients_of_firm(true).empty?, "37signals has no clients after destroy all and refresh" +    assert companies(:first_firm).clients_of_firm.reload.empty?, "37signals has no clients after destroy all and refresh"    end    def test_dependence @@ -1426,6 +1449,26 @@ class HasManyAssociationsTest < ActiveRecord::TestCase      assert firm.companies.exists?(:name => 'child')    end +  def test_restrict_with_error_is_deprecated_using_key_many +    I18n.backend = I18n::Backend::Simple.new +    I18n.backend.store_translations :en, activerecord: { errors: { messages: { restrict_dependent_destroy: { many: 'message for deprecated key' } } } } + +    firm = RestrictedWithErrorFirm.create!(name: 'restrict') +    firm.companies.create(name: 'child') + +    assert !firm.companies.empty? + +    assert_deprecated { firm.destroy } + +    assert !firm.errors.empty? + +    assert_equal 'message for deprecated key', firm.errors[:base].first +    assert RestrictedWithErrorFirm.exists?(name: 'restrict') +    assert firm.companies.exists?(name: 'child') +  ensure +    I18n.backend.reload! +  end +    def test_restrict_with_error      firm = RestrictedWithErrorFirm.create!(:name => 'restrict')      firm.companies.create(:name => 'child') @@ -1441,6 +1484,25 @@ class HasManyAssociationsTest < ActiveRecord::TestCase      assert firm.companies.exists?(:name => 'child')    end +  def test_restrict_with_error_with_locale +    I18n.backend = I18n::Backend::Simple.new +    I18n.backend.store_translations 'en', activerecord: {attributes: {restricted_with_error_firm: {companies: 'client companies'}}} +    firm = RestrictedWithErrorFirm.create!(name: 'restrict') +    firm.companies.create(name: 'child') + +    assert !firm.companies.empty? + +    firm.destroy + +    assert !firm.errors.empty? + +    assert_equal "Cannot delete record because dependent client companies exist", firm.errors[:base].first +    assert RestrictedWithErrorFirm.exists?(name: 'restrict') +    assert firm.companies.exists?(name: 'child') +  ensure +    I18n.backend.reload! +  end +    def test_included_in_collection      assert_equal true, companies(:first_firm).clients.include?(Client.find(2))    end @@ -1518,7 +1580,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase      rescue Client::RaisedOnSave      end -    assert_equal [good], companies(:first_firm).clients_of_firm(true) +    assert_equal [good], companies(:first_firm).clients_of_firm.reload    end    def test_transactions_when_replacing_on_new_record @@ -1534,7 +1596,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase    def test_get_ids_for_loaded_associations      company = companies(:first_firm) -    company.clients(true) +    company.clients.reload      assert_queries(0) do        company.client_ids        company.client_ids @@ -1588,7 +1650,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase      firm.client_ids = [companies(:first_client).id, nil, companies(:second_client).id, '']      firm.save! -    assert_equal 2, firm.clients(true).size +    assert_equal 2, firm.clients.reload.size      assert_equal true, firm.clients.include?(companies(:second_client))    end @@ -2119,6 +2181,26 @@ class HasManyAssociationsTest < ActiveRecord::TestCase      assert_equal [bulb1, bulb2], car.all_bulbs.sort_by(&:id)    end +  test "can unscope and where the default scope of the associated model" do +    Car.has_many :other_bulbs, -> { unscope(where: [:name]).where(name: 'other') }, class_name: "Bulb" +    car = Car.create! +    bulb1 = Bulb.create! name: "defaulty", car: car +    bulb2 = Bulb.create! name: "other",    car: car + +    assert_equal [bulb1], car.bulbs +    assert_equal [bulb2], car.other_bulbs +  end + +  test "can rewhere the default scope of the associated model" do +    Car.has_many :old_bulbs, -> { rewhere(name: 'old') }, class_name: "Bulb" +    car = Car.create! +    bulb1 = Bulb.create! name: "defaulty", car: car +    bulb2 = Bulb.create! name: "old",      car: car + +    assert_equal [bulb1], car.bulbs +    assert_equal [bulb2], car.old_bulbs +  end +    test 'unscopes the default scope of associated model when used with include' do      car = Car.create!      bulb = Bulb.create! name: "other", car: car @@ -2252,4 +2334,48 @@ class HasManyAssociationsTest < ActiveRecord::TestCase      assert_equal [first_bulb, second_bulb], car.bulbs    end + +  def test_association_force_reload_with_only_true_is_deprecated +    company = Company.find(1) + +    assert_deprecated { company.clients_of_firm(true) } +  end + +  class AuthorWithErrorDestroyingAssociation < ActiveRecord::Base +    self.table_name = "authors" +    has_many :posts_with_error_destroying, +      class_name: "PostWithErrorDestroying", +      foreign_key: :author_id, +      dependent: :destroy +  end + +  class PostWithErrorDestroying < ActiveRecord::Base +    self.table_name = "posts" +    self.inheritance_column = nil +    before_destroy -> { throw :abort } +  end + +  def test_destroy_does_not_raise_when_association_errors_on_destroy +    assert_no_difference "AuthorWithErrorDestroyingAssociation.count" do +      author = AuthorWithErrorDestroyingAssociation.first + +      assert_not author.destroy +    end +  end + +  def test_destroy_with_bang_bubbles_errors_from_associations +    error = assert_raises ActiveRecord::RecordNotDestroyed do +      AuthorWithErrorDestroyingAssociation.first.destroy! +    end + +    assert_instance_of PostWithErrorDestroying, error.record +  end + +  def test_ids_reader_memoization +    car = Car.create!(name: 'Tofaş') +    bulb = Bulb.create!(car: car) + +    assert_equal [bulb.id], car.bulb_ids +    assert_no_queries { car.bulb_ids } +  end  end diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb index c34387f06d..cf730e4fe7 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -188,7 +188,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase        assert post.people.include?(person)      end -    assert post.reload.people(true).include?(person) +    assert post.reload.people.reload.include?(person)    end    def test_delete_all_for_with_dependent_option_destroy @@ -229,7 +229,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase      post   = posts(:thinking)      post.people.concat [person]      assert_equal 1, post.people.size -    assert_equal 1, post.people(true).size +    assert_equal 1, post.people.reload.size    end    def test_associate_existing_record_twice_should_add_to_target_twice @@ -285,7 +285,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase        assert posts(:thinking).people.include?(new_person)      end -    assert posts(:thinking).reload.people(true).include?(new_person) +    assert posts(:thinking).reload.people.reload.include?(new_person)    end    def test_associate_new_by_building @@ -310,8 +310,8 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase        posts(:thinking).save      end -    assert posts(:thinking).reload.people(true).collect(&:first_name).include?("Bob") -    assert posts(:thinking).reload.people(true).collect(&:first_name).include?("Ted") +    assert posts(:thinking).reload.people.reload.collect(&:first_name).include?("Bob") +    assert posts(:thinking).reload.people.reload.collect(&:first_name).include?("Ted")    end    def test_build_then_save_with_has_many_inverse @@ -356,7 +356,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase        assert posts(:welcome).people.empty?      end -    assert posts(:welcome).reload.people(true).empty? +    assert posts(:welcome).reload.people.reload.empty?    end    def test_destroy_association @@ -367,7 +367,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase      end      assert posts(:welcome).reload.people.empty? -    assert posts(:welcome).people(true).empty? +    assert posts(:welcome).people.reload.empty?    end    def test_destroy_all @@ -378,7 +378,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase      end      assert posts(:welcome).reload.people.empty? -    assert posts(:welcome).people(true).empty? +    assert posts(:welcome).people.reload.empty?    end    def test_should_raise_exception_for_destroying_mismatching_records @@ -539,7 +539,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase    end    def test_replace_association -    assert_queries(4){posts(:welcome);people(:david);people(:michael); posts(:welcome).people(true)} +    assert_queries(4){posts(:welcome);people(:david);people(:michael); posts(:welcome).people.reload}      # 1 query to delete the existing reader (michael)      # 1 query to associate the new reader (david) @@ -552,8 +552,8 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase        assert !posts(:welcome).people.include?(people(:michael))      } -    assert posts(:welcome).reload.people(true).include?(people(:david)) -    assert !posts(:welcome).reload.people(true).include?(people(:michael)) +    assert posts(:welcome).reload.people.reload.include?(people(:david)) +    assert !posts(:welcome).reload.people.reload.include?(people(:michael))    end    def test_replace_order_is_preserved @@ -592,7 +592,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase        assert posts(:thinking).people.collect(&:first_name).include?("Jeb")      end -    assert posts(:thinking).reload.people(true).collect(&:first_name).include?("Jeb") +    assert posts(:thinking).reload.people.reload.collect(&:first_name).include?("Jeb")    end    def test_through_record_is_built_when_created_with_where @@ -668,7 +668,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase    end    def test_clear_associations -    assert_queries(2) { posts(:welcome);posts(:welcome).people(true) } +    assert_queries(2) { posts(:welcome);posts(:welcome).people.reload }      assert_queries(1) do        posts(:welcome).people.clear @@ -678,7 +678,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase        assert posts(:welcome).people.empty?      end -    assert posts(:welcome).reload.people(true).empty? +    assert posts(:welcome).reload.people.reload.empty?    end    def test_association_callback_ordering @@ -744,13 +744,14 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase    def test_get_ids_for_has_many_through_with_conditions_should_not_preload      Tagging.create!(:taggable_type => 'Post', :taggable_id => posts(:welcome).id, :tag => tags(:misc)) -    ActiveRecord::Associations::Preloader.expects(:new).never -    posts(:welcome).misc_tag_ids +    assert_not_called(ActiveRecord::Associations::Preloader, :new) do +      posts(:welcome).misc_tag_ids +    end    end    def test_get_ids_for_loaded_associations      person = people(:michael) -    person.posts(true) +    person.posts.reload      assert_queries(0) do        person.post_ids        person.post_ids @@ -765,9 +766,10 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase    end    def test_association_proxy_transaction_method_starts_transaction_in_association_class -    Tag.expects(:transaction) -    Post.first.tags.transaction do -      # nothing +    assert_called(Tag, :transaction) do +      Post.first.tags.transaction do +        # nothing +      end      end    end @@ -828,14 +830,14 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase      category = author.named_categories.build(:name => "Primary")      author.save      assert Categorization.exists?(:author_id => author.id, :named_category_name => category.name) -    assert author.named_categories(true).include?(category) +    assert author.named_categories.reload.include?(category)    end    def test_collection_create_with_nonstandard_primary_key_on_belongs_to      author   = authors(:mary)      category = author.named_categories.create(:name => "Primary")      assert Categorization.exists?(:author_id => author.id, :named_category_name => category.name) -    assert author.named_categories(true).include?(category) +    assert author.named_categories.reload.include?(category)    end    def test_collection_exists @@ -850,7 +852,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase      category = author.named_categories.create(:name => "Primary")      author.named_categories.delete(category)      assert !Categorization.exists?(:author_id => author.id, :named_category_name => category.name) -    assert author.named_categories(true).empty? +    assert author.named_categories.reload.empty?    end    def test_collection_singular_ids_getter_with_string_primary_keys @@ -871,10 +873,10 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase      assert_nothing_raised do        book = books(:awdr)        book.subscriber_ids = [subscribers(:second).nick] -      assert_equal [subscribers(:second)], book.subscribers(true) +      assert_equal [subscribers(:second)], book.subscribers.reload        book.subscriber_ids = [] -      assert_equal [], book.subscribers(true) +      assert_equal [], book.subscribers.reload      end    end @@ -1165,4 +1167,38 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase      assert_nil Club.new.special_favourites.distinct_value    end + +  def test_association_force_reload_with_only_true_is_deprecated +    post = Post.find(1) + +    assert_deprecated { post.people(true) } +  end + +  def test_has_many_through_do_not_cache_association_reader_if_the_though_method_has_default_scopes +    member = Member.create! +    club = Club.create! +    TenantMembership.create!( +      member: member, +      club: club +    ) + +    TenantMembership.current_member = member + +    tenant_clubs = member.tenant_clubs +    assert_equal [club], tenant_clubs + +    TenantMembership.current_member = nil + +    other_member = Member.create! +    other_club = Club.create! +    TenantMembership.create!( +      member: other_member, +      club: other_club +    ) + +    tenant_clubs = other_member.tenant_clubs +    assert_equal [other_club], tenant_clubs +  ensure +    TenantMembership.current_member = nil +  end  end diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb index 5c2e5e7b43..c9d9e29f09 100644 --- a/activerecord/test/cases/associations/has_one_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_associations_test.rb @@ -107,6 +107,14 @@ class HasOneAssociationsTest < ActiveRecord::TestCase      assert_nil Account.find(old_account_id).firm_id    end +  def test_nullification_on_destroyed_association +    developer = Developer.create!(name: "Someone") +    ship = Ship.create!(name: "Planet Caravan", developer: developer) +    ship.destroy +    assert !ship.persisted? +    assert !developer.persisted? +  end +    def test_natural_assignment_to_nil_after_destroy      firm = companies(:rails_core)      old_account_id = firm.account.id @@ -178,6 +186,25 @@ class HasOneAssociationsTest < ActiveRecord::TestCase      assert firm.account.present?    end +  def test_restrict_with_error_is_deprecated_using_key_one +    I18n.backend = I18n::Backend::Simple.new +    I18n.backend.store_translations :en, activerecord: { errors: { messages: { restrict_dependent_destroy: { one: 'message for deprecated key' } } } } + +    firm = RestrictedWithErrorFirm.create!(name: 'restrict') +    firm.create_account(credit_limit: 10) + +    assert_not_nil firm.account + +    assert_deprecated { firm.destroy } + +    assert !firm.errors.empty? +    assert_equal 'message for deprecated key', firm.errors[:base].first +    assert RestrictedWithErrorFirm.exists?(name: 'restrict') +    assert firm.account.present? +  ensure +    I18n.backend.reload! +  end +    def test_restrict_with_error      firm = RestrictedWithErrorFirm.create!(:name => 'restrict')      firm.create_account(:credit_limit => 10) @@ -192,6 +219,24 @@ class HasOneAssociationsTest < ActiveRecord::TestCase      assert firm.account.present?    end +  def test_restrict_with_error_with_locale +    I18n.backend = I18n::Backend::Simple.new +    I18n.backend.store_translations 'en', activerecord: {attributes: {restricted_with_error_firm: {account: 'firm account'}}} +    firm = RestrictedWithErrorFirm.create!(name: 'restrict') +    firm.create_account(credit_limit: 10) + +    assert_not_nil firm.account + +    firm.destroy + +    assert !firm.errors.empty? +    assert_equal "Cannot delete record because a dependent firm account exists", firm.errors[:base].first +    assert RestrictedWithErrorFirm.exists?(name: 'restrict') +    assert firm.account.present? +  ensure +    I18n.backend.reload! +  end +    def test_successful_build_association      firm = Firm.new("name" => "GlobalMegaCorp")      firm.save @@ -332,7 +377,8 @@ class HasOneAssociationsTest < ActiveRecord::TestCase      assert a.persisted?      assert_equal a, firm.account      assert_equal a, firm.account -    assert_equal a, firm.account(true) +    firm.association(:account).reload +    assert_equal a, firm.account    end    def test_save_still_works_after_accessing_nil_has_one @@ -607,4 +653,10 @@ class HasOneAssociationsTest < ActiveRecord::TestCase        end      end    end + +  def test_association_force_reload_with_only_true_is_deprecated +    firm = Firm.find(1) + +    assert_deprecated { firm.account(true) } +  end  end diff --git a/activerecord/test/cases/associations/has_one_through_associations_test.rb b/activerecord/test/cases/associations/has_one_through_associations_test.rb index f8772547a2..b2b46812b9 100644 --- a/activerecord/test/cases/associations/has_one_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_through_associations_test.rb @@ -16,6 +16,10 @@ require 'models/owner'  require 'models/post'  require 'models/comment'  require 'models/categorization' +require 'models/customer' +require 'models/carrier' +require 'models/shop_account' +require 'models/customer_carrier'  class HasOneThroughAssociationsTest < ActiveRecord::TestCase    fixtures :member_types, :members, :clubs, :memberships, :sponsors, :organizations, :minivans, @@ -245,12 +249,14 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase      assert_not_nil @member_detail.member_type      @member_detail.destroy      assert_queries(1) do -      assert_not_nil @member_detail.member_type(true) +      @member_detail.association(:member_type).reload +      assert_not_nil @member_detail.member_type      end      @member_detail.member.destroy      assert_queries(1) do -      assert_nil @member_detail.member_type(true) +      @member_detail.association(:member_type).reload +      assert_nil @member_detail.member_type      end    end @@ -344,4 +350,34 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase        end      end    end + +  def test_has_one_through_do_not_cache_association_reader_if_the_though_method_has_default_scopes +    customer = Customer.create! +    carrier = Carrier.create! +    customer_carrier = CustomerCarrier.create!( +      customer: customer, +      carrier: carrier, +    ) +    account = ShopAccount.create!(customer_carrier: customer_carrier) + +    CustomerCarrier.current_customer = customer + +    account_carrier = account.carrier +    assert_equal carrier, account_carrier + +    CustomerCarrier.current_customer = nil + +    other_carrier = Carrier.create! +    other_customer = Customer.create! +    other_customer_carrier = CustomerCarrier.create!( +      customer: other_customer, +      carrier: other_carrier, +    ) +    other_account = ShopAccount.create!(customer_carrier: other_customer_carrier) + +    account_carrier = other_account.carrier +    assert_equal other_carrier, account_carrier +  ensure +    CustomerCarrier.current_customer = nil +  end  end diff --git a/activerecord/test/cases/associations/inverse_associations_test.rb b/activerecord/test/cases/associations/inverse_associations_test.rb index 423b8238b1..ece4dab539 100644 --- a/activerecord/test/cases/associations/inverse_associations_test.rb +++ b/activerecord/test/cases/associations/inverse_associations_test.rb @@ -13,6 +13,9 @@ require 'models/mixed_case_monkey'  require 'models/admin'  require 'models/admin/account'  require 'models/admin/user' +require 'models/developer' +require 'models/company' +require 'models/project'  class AutomaticInverseFindingTests < ActiveRecord::TestCase    fixtures :ratings, :comments, :cars @@ -198,6 +201,16 @@ class InverseAssociationTests < ActiveRecord::TestCase      belongs_to_ref = Sponsor.reflect_on_association(:sponsor_club)      assert_nil belongs_to_ref.inverse_of    end + +  def test_this_inverse_stuff +    firm = Firm.create!(name: 'Adequate Holdings') +    Project.create!(name: 'Project 1', firm: firm) +    Developer.create!(name: 'Gorbypuff', firm: firm) + +    new_project = Project.last +    assert Project.reflect_on_association(:lead_developer).inverse_of.present?, "Expected inverse of to be present" +    assert new_project.lead_developer.present?, "Expected lead developer to be present on the project" +  end  end  class InverseHasOneTests < ActiveRecord::TestCase diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb index 5575419c35..f6dddaf5b4 100644 --- a/activerecord/test/cases/associations/join_model_test.rb +++ b/activerecord/test/cases/associations/join_model_test.rb @@ -213,7 +213,8 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase      old_count = Tagging.count      post.destroy      assert_equal old_count-1, Tagging.count -    assert_nil posts(:welcome).tagging(true) +    posts(:welcome).association(:tagging).reload +    assert_nil posts(:welcome).tagging    end    def test_delete_polymorphic_has_one_with_nullify @@ -224,7 +225,8 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase      old_count = Tagging.count      post.destroy      assert_equal old_count, Tagging.count -    assert_nil posts(:welcome).tagging(true) +    posts(:welcome).association(:tagging).reload +    assert_nil posts(:welcome).tagging    end    def test_has_many_with_piggyback @@ -461,7 +463,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase      assert saved_post.tags.include?(new_tag)      assert new_tag.persisted? -    assert saved_post.reload.tags(true).include?(new_tag) +    assert saved_post.reload.tags.reload.include?(new_tag)      new_post = Post.new(:title => "Association replacement works!", :body => "You best believe it.") @@ -474,7 +476,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase      new_post.save!      assert new_post.persisted? -    assert new_post.reload.tags(true).include?(saved_tag) +    assert new_post.reload.tags.reload.include?(saved_tag)      assert !posts(:thinking).tags.build.persisted?      assert !posts(:thinking).tags.new.persisted? @@ -490,7 +492,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase      assert_nil( wrong = post_thinking.taggings.detect { |t| t.class != Tagging },                  message = "Expected a Tagging in taggings collection, got #{wrong.class}.")      assert_equal(count + 1, post_thinking.reload.tags.size) -    assert_equal(count + 1, post_thinking.tags(true).size) +    assert_equal(count + 1, post_thinking.tags.reload.size)      assert_kind_of Tag, post_thinking.tags.create!(:name => 'foo')      assert_nil( wrong = post_thinking.tags.detect { |t| t.class != Tag }, @@ -498,7 +500,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase      assert_nil( wrong = post_thinking.taggings.detect { |t| t.class != Tagging },                  message = "Expected a Tagging in taggings collection, got #{wrong.class}.")      assert_equal(count + 2, post_thinking.reload.tags.size) -    assert_equal(count + 2, post_thinking.tags(true).size) +    assert_equal(count + 2, post_thinking.tags.reload.size)      assert_nothing_raised { post_thinking.tags.concat(Tag.create!(:name => 'abc'), Tag.create!(:name => 'def')) }      assert_nil( wrong = post_thinking.tags.detect { |t| t.class != Tag }, @@ -506,7 +508,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase      assert_nil( wrong = post_thinking.taggings.detect { |t| t.class != Tagging },                  message = "Expected a Tagging in taggings collection, got #{wrong.class}.")      assert_equal(count + 4, post_thinking.reload.tags.size) -    assert_equal(count + 4, post_thinking.tags(true).size) +    assert_equal(count + 4, post_thinking.tags.reload.size)      # Raises if the wrong reflection name is used to set the Edge belongs_to      assert_nothing_raised { vertices(:vertex_1).sinks << vertices(:vertex_5) } @@ -544,11 +546,11 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase      book = Book.create!(:name => 'Getting Real')      book_awdr = books(:awdr)      book_awdr.references << book -    assert_equal(count + 1, book_awdr.references(true).size) +    assert_equal(count + 1, book_awdr.references.reload.size)      assert_nothing_raised { book_awdr.references.delete(book) }      assert_equal(count, book_awdr.references.size) -    assert_equal(count, book_awdr.references(true).size) +    assert_equal(count, book_awdr.references.reload.size)      assert_equal(references_before.sort, book_awdr.references.sort)    end @@ -558,14 +560,14 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase      tag = Tag.create!(:name => 'doomed')      post_thinking = posts(:thinking)      post_thinking.tags << tag -    assert_equal(count + 1, post_thinking.taggings(true).size) -    assert_equal(count + 1, post_thinking.reload.tags(true).size) +    assert_equal(count + 1, post_thinking.taggings.reload.size) +    assert_equal(count + 1, post_thinking.reload.tags.reload.size)      assert_not_equal(tags_before, post_thinking.tags.sort)      assert_nothing_raised { post_thinking.tags.delete(tag) }      assert_equal(count, post_thinking.tags.size) -    assert_equal(count, post_thinking.tags(true).size) -    assert_equal(count, post_thinking.taggings(true).size) +    assert_equal(count, post_thinking.tags.reload.size) +    assert_equal(count, post_thinking.taggings.reload.size)      assert_equal(tags_before, post_thinking.tags.sort)    end @@ -577,11 +579,11 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase      quaked = Tag.create!(:name => 'quaked')      post_thinking = posts(:thinking)      post_thinking.tags << doomed << doomed2 -    assert_equal(count + 2, post_thinking.reload.tags(true).size) +    assert_equal(count + 2, post_thinking.reload.tags.reload.size)      assert_nothing_raised { post_thinking.tags.delete(doomed, doomed2, quaked) }      assert_equal(count, post_thinking.tags.size) -    assert_equal(count, post_thinking.tags(true).size) +    assert_equal(count, post_thinking.tags.reload.size)      assert_equal(tags_before, post_thinking.tags.sort)    end diff --git a/activerecord/test/cases/associations/nested_through_associations_test.rb b/activerecord/test/cases/associations/nested_through_associations_test.rb index 31b68c940e..b040485d99 100644 --- a/activerecord/test/cases/associations/nested_through_associations_test.rb +++ b/activerecord/test/cases/associations/nested_through_associations_test.rb @@ -495,7 +495,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase      groucho = members(:groucho)      founding = member_types(:founding) -    assert_raises(ActiveRecord::HasManyThroughNestedAssociationsAreReadonly) do +    assert_raises(ActiveRecord::HasOneThroughNestedAssociationsAreReadonly) do        groucho.nested_member_type = founding      end    end diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb index 3d202a5527..01a058918a 100644 --- a/activerecord/test/cases/associations_test.rb +++ b/activerecord/test/cases/associations_test.rb @@ -1,7 +1,6 @@  require "cases/helper"  require 'models/computer'  require 'models/developer' -require 'models/computer'  require 'models/project'  require 'models/company'  require 'models/categorization' @@ -13,7 +12,6 @@ require 'models/tag'  require 'models/tagging'  require 'models/person'  require 'models/reader' -require 'models/parrot'  require 'models/ship_part'  require 'models/ship'  require 'models/liquid' @@ -93,8 +91,10 @@ class AssociationsTest < ActiveRecord::TestCase      assert firm.clients.empty?, "New firm should have cached no client objects"      assert_equal 0, firm.clients.size, "New firm should have cached 0 clients count" -    assert !firm.clients(true).empty?, "New firm should have reloaded client objects" -    assert_equal 1, firm.clients(true).size, "New firm should have reloaded clients count" +    ActiveSupport::Deprecation.silence do +      assert !firm.clients(true).empty?, "New firm should have reloaded client objects" +      assert_equal 1, firm.clients(true).size, "New firm should have reloaded clients count" +    end    end    def test_using_limitable_reflections_helper @@ -110,10 +110,13 @@ class AssociationsTest < ActiveRecord::TestCase    def test_force_reload_is_uncached      firm = Firm.create!("name" => "A New Firm, Inc")      Client.create!("name" => "TheClient.com", :firm => firm) -    ActiveRecord::Base.cache do -      firm.clients.each {} -      assert_queries(0) { assert_not_nil firm.clients.each {} } -      assert_queries(1) { assert_not_nil firm.clients(true).each {} } + +    ActiveSupport::Deprecation.silence do +      ActiveRecord::Base.cache do +        firm.clients.each {} +        assert_queries(0) { assert_not_nil firm.clients.each {} } +        assert_queries(1) { assert_not_nil firm.clients(true).each {} } +      end      end    end diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index ea2b94cbf4..52d197718e 100644 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -1,7 +1,6 @@  require "cases/helper"  require 'models/minimalistic'  require 'models/developer' -require 'models/computer'  require 'models/auto_id'  require 'models/boolean'  require 'models/computer' @@ -67,8 +66,9 @@ class AttributeMethodsTest < ActiveRecord::TestCase    def test_caching_nil_primary_key      klass = Class.new(Minimalistic) -    klass.expects(:reset_primary_key).returns(nil).once -    2.times { klass.primary_key } +    assert_called(klass, :reset_primary_key, returns: nil) do +      2.times { klass.primary_key } +    end    end    def test_attribute_keys_on_new_instance @@ -175,9 +175,9 @@ class AttributeMethodsTest < ActiveRecord::TestCase      assert_equal category_attrs , category.attributes_before_type_cast    end -  if current_adapter?(:MysqlAdapter) +  if current_adapter?(:MysqlAdapter, :Mysql2Adapter)      def test_read_attributes_before_type_cast_on_boolean -      bool = Boolean.create({ "value" => false }) +      bool = Boolean.create!({ "value" => false })        if RUBY_PLATFORM =~ /java/          # JRuby will return the value before typecast as string          assert_equal "0", bool.reload.attributes_before_type_cast["value"] @@ -542,9 +542,6 @@ class AttributeMethodsTest < ActiveRecord::TestCase      developer.save! -    assert_equal "50000", developer.salary_before_type_cast -    assert_equal 1337, developer.name_before_type_cast -      assert_equal 50000, developer.salary      assert_equal "1337", developer.name    end diff --git a/activerecord/test/cases/attribute_set_test.rb b/activerecord/test/cases/attribute_set_test.rb index 9d927481ec..7a24b85a36 100644 --- a/activerecord/test/cases/attribute_set_test.rb +++ b/activerecord/test/cases/attribute_set_test.rb @@ -29,7 +29,7 @@ module ActiveRecord        assert_equal :bar, attributes[:bar].name      end -    test "duping creates a new hash and dups each attribute" do +    test "duping creates a new hash, but does not dup the attributes" do        builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::String.new)        attributes = builder.build_from_database(foo: 1, bar: 'foo') @@ -43,6 +43,24 @@ module ActiveRecord        assert_equal 1, attributes[:foo].value        assert_equal 2, duped[:foo].value +      assert_equal 'foobar', attributes[:bar].value +      assert_equal 'foobar', duped[:bar].value +    end + +    test "deep_duping creates a new hash and dups each attribute" do +      builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::String.new) +      attributes = builder.build_from_database(foo: 1, bar: 'foo') + +      # Ensure the type cast value is cached +      attributes[:foo].value +      attributes[:bar].value + +      duped = attributes.deep_dup +      duped.write_from_database(:foo, 2) +      duped[:bar].value << 'bar' + +      assert_equal 1, attributes[:foo].value +      assert_equal 2, duped[:foo].value        assert_equal 'foo', attributes[:bar].value        assert_equal 'foobar', duped[:bar].value      end @@ -160,6 +178,9 @@ module ActiveRecord          return if value.nil?          value + " from database"        end + +      def assert_valid_value(*) +      end      end      test "write_from_database sets the attribute with database typecasting" do @@ -207,5 +228,26 @@ module ActiveRecord        assert_equal [:foo], attributes.accessed      end + +    test "#map returns a new attribute set with the changes applied" do +      builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::Integer.new) +      attributes = builder.build_from_database(foo: "1", bar: "2") +      new_attributes = attributes.map do |attr| +        attr.with_cast_value(attr.value + 1) +      end + +      assert_equal 2, new_attributes.fetch_value(:foo) +      assert_equal 3, new_attributes.fetch_value(:bar) +    end + +    test "comparison for equality is correctly implemented" do +      builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::Integer.new) +      attributes = builder.build_from_database(foo: "1", bar: "2") +      attributes2 = builder.build_from_database(foo: "1", bar: "2") +      attributes3 = builder.build_from_database(foo: "2", bar: "2") + +      assert_equal attributes, attributes2 +      assert_not_equal attributes2, attributes3 +    end    end  end diff --git a/activerecord/test/cases/attribute_test.rb b/activerecord/test/cases/attribute_test.rb index aa419c7a67..a24a4fc6a4 100644 --- a/activerecord/test/cases/attribute_test.rb +++ b/activerecord/test/cases/attribute_test.rb @@ -1,11 +1,9 @@  require 'cases/helper' -require 'minitest/mock'  module ActiveRecord    class AttributeTest < ActiveRecord::TestCase      setup do        @type = Minitest::Mock.new -      @type.expect(:==, false, [false])      end      teardown do @@ -109,6 +107,9 @@ module ActiveRecord        def deserialize(value)          value + " from database"        end + +      def assert_valid_value(*) +      end      end      test "with_value_from_user returns a new attribute with the value from the user" do @@ -181,12 +182,65 @@ module ActiveRecord        assert attribute.has_been_read?      end +    test "an attribute is not changed if it hasn't been assigned or mutated" do +      attribute = Attribute.from_database(:foo, 1, Type::Value.new) + +      refute attribute.changed? +    end + +    test "an attribute is changed if it's been assigned a new value" do +      attribute = Attribute.from_database(:foo, 1, Type::Value.new) +      changed = attribute.with_value_from_user(2) + +      assert changed.changed? +    end + +    test "an attribute is not changed if it's assigned the same value" do +      attribute = Attribute.from_database(:foo, 1, Type::Value.new) +      unchanged = attribute.with_value_from_user(1) + +      refute unchanged.changed? +    end +      test "an attribute can not be mutated if it has not been read,        and skips expensive calculations" do        type_which_raises_from_all_methods = Object.new        attribute = Attribute.from_database(:foo, "bar", type_which_raises_from_all_methods) -      assert_not attribute.changed_in_place_from?("bar") +      assert_not attribute.changed_in_place? +    end + +    test "an attribute is changed if it has been mutated" do +      attribute = Attribute.from_database(:foo, "bar", Type::String.new) +      attribute.value << "!" + +      assert attribute.changed_in_place? +      assert attribute.changed? +    end + +    test "an attribute can forget its changes" do +      attribute = Attribute.from_database(:foo, "bar", Type::String.new) +      changed = attribute.with_value_from_user("foo") +      forgotten = changed.forgetting_assignment + +      assert changed.changed? # sanity check +      refute forgotten.changed? +    end + +    test "with_value_from_user validates the value" do +      type = Type::Value.new +      type.define_singleton_method(:assert_valid_value) do |value| +        if value == 1 +          raise ArgumentError +        end +      end + +      attribute = Attribute.from_database(:foo, 1, type) +      assert_equal 1, attribute.value +      assert_equal 2, attribute.with_value_from_user(2).value +      assert_raises ArgumentError do +        attribute.with_value_from_user(1) +      end      end    end  end diff --git a/activerecord/test/cases/attributes_test.rb b/activerecord/test/cases/attributes_test.rb index eeda9335ad..264b275181 100644 --- a/activerecord/test/cases/attributes_test.rb +++ b/activerecord/test/cases/attributes_test.rb @@ -142,7 +142,7 @@ module ActiveRecord      end      if current_adapter?(:PostgreSQLAdapter) -      test "arrays types can be specified" do +      test "array types can be specified" do          klass = Class.new(OverloadedType) do            attribute :my_array, :string, limit: 50, array: true            attribute :my_int_array, :integer, array: true @@ -152,7 +152,7 @@ module ActiveRecord            Type::String.new(limit: 50))          int_array = ConnectionAdapters::PostgreSQL::OID::Array.new(            Type::Integer.new) -        refute_equal string_array, int_array +        assert_not_equal string_array, int_array          assert_equal string_array, klass.type_for_attribute("my_array")          assert_equal int_array, klass.type_for_attribute("my_int_array")        end @@ -167,7 +167,7 @@ module ActiveRecord            Type::String.new(limit: 50))          int_range = ConnectionAdapters::PostgreSQL::OID::Range.new(            Type::Integer.new) -        refute_equal string_range, int_range +        assert_not_equal string_range, int_range          assert_equal string_range, klass.type_for_attribute("my_range")          assert_equal int_range, klass.type_for_attribute("my_int_range")        end diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb index 80b5a0004d..37ec3f1106 100644 --- a/activerecord/test/cases/autosave_association_test.rb +++ b/activerecord/test/cases/autosave_association_test.rb @@ -67,6 +67,14 @@ class TestAutosaveAssociationsInGeneral < ActiveRecord::TestCase      assert_no_difference_when_adding_callbacks_twice_for Pirate, :parrots    end +  def test_cyclic_autosaves_do_not_add_multiple_validations +    ship = ShipWithoutNestedAttributes.new +    ship.prisoners.build + +    assert_not ship.valid? +    assert_equal 1, ship.errors[:name].length +  end +    private    def assert_no_difference_when_adding_callbacks_twice_for(model, association_name) @@ -149,7 +157,8 @@ class TestDefaultAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCas      assert_equal a, firm.account      assert firm.save      assert_equal a, firm.account -    assert_equal a, firm.account(true) +    firm.association(:account).reload +    assert_equal a, firm.account    end    def test_assignment_before_either_saved @@ -162,7 +171,8 @@ class TestDefaultAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCas      assert firm.persisted?      assert a.persisted?      assert_equal a, firm.account -    assert_equal a, firm.account(true) +    firm.association(:account).reload +    assert_equal a, firm.account    end    def test_not_resaved_when_unchanged @@ -248,7 +258,8 @@ class TestDefaultAutosaveAssociationOnABelongsToAssociation < ActiveRecord::Test      assert apple.save      assert apple.persisted?      assert_equal apple, client.firm -    assert_equal apple, client.firm(true) +    client.association(:firm).reload +    assert_equal apple, client.firm    end    def test_assignment_before_either_saved @@ -261,7 +272,8 @@ class TestDefaultAutosaveAssociationOnABelongsToAssociation < ActiveRecord::Test      assert final_cut.persisted?      assert apple.persisted?      assert_equal apple, final_cut.firm -    assert_equal apple, final_cut.firm(true) +    final_cut.association(:firm).reload +    assert_equal apple, final_cut.firm    end    def test_store_two_association_with_one_save @@ -456,7 +468,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa      assert_equal new_client, companies(:first_firm).clients_of_firm.last      assert !companies(:first_firm).save      assert !new_client.persisted? -    assert_equal 2, companies(:first_firm).clients_of_firm(true).size +    assert_equal 2, companies(:first_firm).clients_of_firm.reload.size    end    def test_adding_before_save @@ -481,7 +493,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa      assert_equal no_of_clients + 2, Client.count  # Clients were saved to database.      assert_equal 2, new_firm.clients_of_firm.size -    assert_equal 2, new_firm.clients_of_firm(true).size +    assert_equal 2, new_firm.clients_of_firm.reload.size    end    def test_assign_ids @@ -510,7 +522,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa      company.name += '-changed'      assert_queries(2) { assert company.save }      assert new_client.persisted? -    assert_equal 3, company.clients_of_firm(true).size +    assert_equal 3, company.clients_of_firm.reload.size    end    def test_build_many_before_save @@ -519,7 +531,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa      company.name += '-changed'      assert_queries(3) { assert company.save } -    assert_equal 4, company.clients_of_firm(true).size +    assert_equal 4, company.clients_of_firm.reload.size    end    def test_build_via_block_before_save @@ -530,7 +542,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa      company.name += '-changed'      assert_queries(2) { assert company.save }      assert new_client.persisted? -    assert_equal 3, company.clients_of_firm(true).size +    assert_equal 3, company.clients_of_firm.reload.size    end    def test_build_many_via_block_before_save @@ -543,7 +555,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa      company.name += '-changed'      assert_queries(3) { assert company.save } -    assert_equal 4, company.clients_of_firm(true).size +    assert_equal 4, company.clients_of_firm.reload.size    end    def test_replace_on_new_object @@ -1142,6 +1154,13 @@ class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase    def test_should_not_load_the_associated_model      assert_queries(1) { @pirate.catchphrase = 'Arr'; @pirate.save! }    end + +  def test_mark_for_destruction_is_ignored_without_autosave_true +    ship = ShipWithoutNestedAttributes.new(name: "The Black Flag") +    ship.parts.build.mark_for_destruction + +    assert_not ship.valid? +  end  end  class TestAutosaveAssociationOnAHasOneThroughAssociation < ActiveRecord::TestCase diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index 31c31e4329..dbbcaa075d 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -  require "cases/helper" -require 'active_support/concurrency/latch'  require 'models/post'  require 'models/author'  require 'models/topic' @@ -29,6 +26,7 @@ require 'models/bird'  require 'models/car'  require 'models/bulb'  require 'rexml/document' +require 'concurrent/atomics'  class FirstAbstractClass < ActiveRecord::Base    self.abstract_class = true @@ -206,7 +204,7 @@ class BasicsTest < ActiveRecord::TestCase      )      # For adapters which support microsecond resolution. -    if current_adapter?(:PostgreSQLAdapter, :SQLite3Adapter) || mysql_56? +    if subsecond_precision_supported?        assert_equal 11, Topic.find(1).written_on.sec        assert_equal 223300, Topic.find(1).written_on.usec        assert_equal 9900, Topic.find(2).written_on.usec @@ -946,6 +944,34 @@ class BasicsTest < ActiveRecord::TestCase      assert_equal BigDecimal("1000234000567.95"), m1.big_bank_balance    end +  def test_numeric_fields_with_scale +    m = NumericData.new( +      :bank_balance => 1586.43122334, +      :big_bank_balance => BigDecimal("234000567.952344"), +      :world_population => 6000000000, +      :my_house_population => 3 +    ) +    assert m.save + +    m1 = NumericData.find(m.id) +    assert_not_nil m1 + +    # As with migration_test.rb, we should make world_population >= 2**62 +    # to cover 64-bit platforms and test it is a Bignum, but the main thing +    # is that it's an Integer. +    assert_kind_of Integer, m1.world_population +    assert_equal 6000000000, m1.world_population + +    assert_kind_of Fixnum, m1.my_house_population +    assert_equal 3, m1.my_house_population + +    assert_kind_of BigDecimal, m1.bank_balance +    assert_equal BigDecimal("1586.43"), m1.bank_balance + +    assert_kind_of BigDecimal, m1.big_bank_balance +    assert_equal BigDecimal("234000567.95"), m1.big_bank_balance +  end +    def test_auto_id      auto = AutoId.new      auto.save @@ -1271,9 +1297,10 @@ class BasicsTest < ActiveRecord::TestCase    end    def test_compute_type_no_method_error -    ActiveSupport::Dependencies.stubs(:safe_constantize).raises(NoMethodError) -    assert_raises NoMethodError do -      ActiveRecord::Base.send :compute_type, 'InvalidModel' +    ActiveSupport::Dependencies.stub(:safe_constantize, proc{ raise NoMethodError }) do +      assert_raises NoMethodError do +        ActiveRecord::Base.send :compute_type, 'InvalidModel' +      end      end    end @@ -1287,18 +1314,20 @@ class BasicsTest < ActiveRecord::TestCase        error = e      end -    ActiveSupport::Dependencies.stubs(:safe_constantize).raises(e) +    ActiveSupport::Dependencies.stub(:safe_constantize, proc{ raise e }) do -    exception = assert_raises NameError do -      ActiveRecord::Base.send :compute_type, 'InvalidModel' +      exception = assert_raises NameError do +        ActiveRecord::Base.send :compute_type, 'InvalidModel' +      end +      assert_equal error.message, exception.message      end -    assert_equal error.message, exception.message    end    def test_compute_type_argument_error -    ActiveSupport::Dependencies.stubs(:safe_constantize).raises(ArgumentError) -    assert_raises ArgumentError do -      ActiveRecord::Base.send :compute_type, 'InvalidModel' +    ActiveSupport::Dependencies.stub(:safe_constantize, proc{ raise ArgumentError }) do +      assert_raises ArgumentError do +        ActiveRecord::Base.send :compute_type, 'InvalidModel' +      end      end    end @@ -1506,20 +1535,20 @@ class BasicsTest < ActiveRecord::TestCase      orig_handler = klass.connection_handler      new_handler = ActiveRecord::ConnectionAdapters::ConnectionHandler.new      after_handler = nil -    latch1 = ActiveSupport::Concurrency::Latch.new -    latch2 = ActiveSupport::Concurrency::Latch.new +    latch1 = Concurrent::CountDownLatch.new +    latch2 = Concurrent::CountDownLatch.new      t = Thread.new do        klass.connection_handler = new_handler -      latch1.release -      latch2.await +      latch1.count_down +      latch2.wait        after_handler = klass.connection_handler      end -    latch1.await +    latch1.wait      klass.connection_handler = orig_handler -    latch2.release +    latch2.count_down      t.join      assert_equal after_handler, new_handler @@ -1542,4 +1571,22 @@ class BasicsTest < ActiveRecord::TestCase      assert_not topic.id_changed?    end + +  test "ignored columns are not present in columns_hash" do +    cache_columns = Developer.connection.schema_cache.columns_hash(Developer.table_name) +    assert_includes cache_columns.keys, 'first_name' +    refute_includes Developer.columns_hash.keys, 'first_name' +  end + +  test "ignored columns have no attribute methods" do +    refute Developer.new.respond_to?(:first_name) +    refute Developer.new.respond_to?(:first_name=) +    refute Developer.new.respond_to?(:first_name?) +  end + +  test "ignored columns don't prevent explicit declaration of attribute methods" do +    assert Developer.new.respond_to?(:last_name) +    assert Developer.new.respond_to?(:last_name=) +    assert Developer.new.respond_to?(:last_name?) +  end  end diff --git a/activerecord/test/cases/batches_test.rb b/activerecord/test/cases/batches_test.rb index 0791dde1f2..9cb70ee239 100644 --- a/activerecord/test/cases/batches_test.rb +++ b/activerecord/test/cases/batches_test.rb @@ -53,7 +53,7 @@ class EachTest < ActiveRecord::TestCase    end    def test_each_should_raise_if_select_is_set_without_id -    assert_raise(RuntimeError) do +    assert_raise(ArgumentError) do        Post.select(:title).find_each(batch_size: 1) { |post|          flunk "should not call this block"        } @@ -69,13 +69,15 @@ class EachTest < ActiveRecord::TestCase    end    def test_warn_if_limit_scope_is_set -    ActiveRecord::Base.logger.expects(:warn) -    Post.limit(1).find_each { |post| post } +    assert_called(ActiveRecord::Base.logger, :warn) do +      Post.limit(1).find_each { |post| post } +    end    end    def test_warn_if_order_scope_is_set -    ActiveRecord::Base.logger.expects(:warn) -    Post.order("title").find_each { |post| post } +    assert_called(ActiveRecord::Base.logger, :warn) do +      Post.order("title").find_each { |post| post } +    end    end    def test_logger_not_required @@ -137,14 +139,15 @@ class EachTest < ActiveRecord::TestCase    def test_find_in_batches_should_not_use_records_after_yielding_them_in_case_original_array_is_modified      not_a_post = "not a post" -    not_a_post.stubs(:id).raises(StandardError, "not_a_post had #id called on it") - -    assert_nothing_raised do -      Post.find_in_batches(:batch_size => 1) do |batch| -        assert_kind_of Array, batch -        assert_kind_of Post, batch.first +    def not_a_post.id; end +    not_a_post.stub(:id, ->{ raise StandardError.new("not_a_post had #id called on it") }) do +      assert_nothing_raised do +        Post.find_in_batches(:batch_size => 1) do |batch| +          assert_kind_of Array, batch +          assert_kind_of Post, batch.first -        batch.map! { not_a_post } +          batch.map! { not_a_post } +        end        end      end    end @@ -199,7 +202,7 @@ class EachTest < ActiveRecord::TestCase    def test_find_in_batches_should_return_an_enumerator      enum = nil -    assert_queries(0) do +    assert_no_queries do        enum = Post.find_in_batches(:batch_size => 1)      end      assert_queries(4) do @@ -210,6 +213,234 @@ class EachTest < ActiveRecord::TestCase      end    end +  def test_in_batches_should_not_execute_any_query +    assert_no_queries do +      assert_kind_of ActiveRecord::Batches::BatchEnumerator, Post.in_batches(of: 2) +    end +  end + +  def test_in_batches_should_yield_relation_if_block_given +    assert_queries(6) do +      Post.in_batches(of: 2) do |relation| +        assert_kind_of ActiveRecord::Relation, relation +      end +    end +  end + +  def test_in_batches_should_be_enumerable_if_no_block_given +    assert_queries(6) do +      Post.in_batches(of: 2).each do |relation| +        assert_kind_of ActiveRecord::Relation, relation +      end +    end +  end + +  def test_in_batches_each_record_should_yield_record_if_block_is_given +    assert_queries(6) do +      Post.in_batches(of: 2).each_record do |post| +        assert post.title.present? +        assert_kind_of Post, post +      end +    end +  end + +  def test_in_batches_each_record_should_return_enumerator_if_no_block_given +    assert_queries(6) do +      Post.in_batches(of: 2).each_record.with_index do |post, i| +        assert post.title.present? +        assert_kind_of Post, post +      end +    end +  end + +  def test_in_batches_each_record_should_be_ordered_by_id +    ids = Post.order('id ASC').pluck(:id) +    assert_queries(6) do +      Post.in_batches(of: 2).each_record.with_index do |post, i| +        assert_equal ids[i], post.id +      end +    end +  end + +  def test_in_batches_update_all_affect_all_records +    assert_queries(6 + 6) do # 6 selects, 6 updates +      Post.in_batches(of: 2).update_all(title: "updated-title") +    end +    assert_equal Post.all.pluck(:title), ["updated-title"] * Post.count +  end + +  def test_in_batches_delete_all_should_not_delete_records_in_other_batches +    not_deleted_count = Post.where('id <= 2').count +    Post.where('id > 2').in_batches(of: 2).delete_all +    assert_equal 0, Post.where('id > 2').count +    assert_equal not_deleted_count, Post.count +  end + +  def test_in_batches_should_not_be_loaded +    Post.in_batches(of: 1) do |relation| +      assert_not relation.loaded? +    end + +    Post.in_batches(of: 1, load: false) do |relation| +      assert_not relation.loaded? +    end +  end + +  def test_in_batches_should_be_loaded +    Post.in_batches(of: 1, load: true) do |relation| +      assert relation.loaded? +    end +  end + +  def test_in_batches_if_not_loaded_executes_more_queries +    assert_queries(@total + 1) do +      Post.in_batches(of: 1, load: false) do |relation| +        assert_not relation.loaded? +      end +    end +  end + +  def test_in_batches_should_return_relations +    assert_queries(@total + 1) do +      Post.in_batches(of: 1) do |relation| +        assert_kind_of ActiveRecord::Relation, relation +      end +    end +  end + +  def test_in_batches_should_start_from_the_start_option +    post = Post.order('id ASC').where('id >= ?', 2).first +    assert_queries(2) do +      relation = Post.in_batches(of: 1, begin_at: 2).first +      assert_equal post, relation.first +    end +  end + +  def test_in_batches_should_end_at_the_end_option +    post = Post.order('id DESC').where('id <= ?', 5).first +    assert_queries(7) do +      relation = Post.in_batches(of: 1, end_at: 5, load: true).reverse_each.first +      assert_equal post, relation.last +    end +  end + +  def test_in_batches_shouldnt_execute_query_unless_needed +    assert_queries(2) do +      Post.in_batches(of: @total) { |relation| assert_kind_of ActiveRecord::Relation, relation } +    end + +    assert_queries(1) do +      Post.in_batches(of: @total + 1) { |relation| assert_kind_of ActiveRecord::Relation, relation } +    end +  end + +  def test_in_batches_should_quote_batch_order +    c = Post.connection +    assert_sql(/ORDER BY #{c.quote_table_name('posts')}.#{c.quote_column_name('id')}/) do +      Post.in_batches(of: 1) do |relation| +        assert_kind_of ActiveRecord::Relation, relation +        assert_kind_of Post, relation.first +      end +    end +  end + +  def test_in_batches_should_not_use_records_after_yielding_them_in_case_original_array_is_modified +    not_a_post = "not a post" +    def not_a_post.id +      raise StandardError.new("not_a_post had #id called on it") +    end + +    assert_nothing_raised do +      Post.in_batches(of: 1) do |relation| +        assert_kind_of ActiveRecord::Relation, relation +        assert_kind_of Post, relation.first + +        relation = [not_a_post] * relation.count +      end +    end +  end + +  def test_in_batches_should_not_ignore_default_scope_without_order_statements +    special_posts_ids = SpecialPostWithDefaultScope.all.map(&:id).sort +    posts = [] +    SpecialPostWithDefaultScope.in_batches do |relation| +      posts.concat(relation) +    end +    assert_equal special_posts_ids, posts.map(&:id) +  end + +  def test_in_batches_should_not_modify_passed_options +    assert_nothing_raised do +      Post.in_batches({ of: 42, begin_at: 1 }.freeze){} +    end +  end + +  def test_in_batches_should_use_any_column_as_primary_key +    nick_order_subscribers = Subscriber.order('nick asc') +    start_nick = nick_order_subscribers.second.nick + +    subscribers = [] +    Subscriber.in_batches(of: 1, begin_at: start_nick) do |relation| +      subscribers.concat(relation) +    end + +    assert_equal nick_order_subscribers[1..-1].map(&:id), subscribers.map(&:id) +  end + +  def test_in_batches_should_use_any_column_as_primary_key_when_start_is_not_specified +    assert_queries(Subscriber.count + 1) do +      Subscriber.in_batches(of: 1, load: true) do |relation| +        assert_kind_of ActiveRecord::Relation, relation +        assert_kind_of Subscriber, relation.first +      end +    end +  end + +  def test_in_batches_should_return_an_enumerator +    enum = nil +    assert_no_queries do +      enum = Post.in_batches(of: 1) +    end +    assert_queries(4) do +      enum.first(4) do |relation| +        assert_kind_of ActiveRecord::Relation, relation +        assert_kind_of Post, relation.first +      end +    end +  end + +  def test_in_batches_relations_should_not_overlap_with_each_other +    seen_posts = [] +    Post.in_batches(of: 2, load: true) do |relation| +      relation.to_a.each do |post| +        assert_not seen_posts.include?(post) +        seen_posts << post +      end +    end +  end + +  def test_in_batches_relations_with_condition_should_not_overlap_with_each_other +    seen_posts = [] +    author_id = Post.first.author_id +    posts_by_author = Post.where(author_id: author_id) +    Post.in_batches(of: 2) do |batch| +      seen_posts += batch.where(author_id: author_id) +    end + +    assert_equal posts_by_author.pluck(:id).sort, seen_posts.map(&:id).sort +  end + +  def test_in_batches_relations_update_all_should_not_affect_matching_records_in_other_batches +    Post.update_all(author_id: 0) +    person = Post.last +    person.update_attributes(author_id: 1) + +    Post.in_batches(of: 2) do |batch| +      batch.where('author_id >= 1').update_all('author_id = author_id + 1') +    end +    assert_equal 2, person.reload.author_id # incremented only once +  end +    def test_find_in_batches_start_deprecated      assert_deprecated do        assert_queries(@total) do diff --git a/activerecord/test/cases/cache_key_test.rb b/activerecord/test/cases/cache_key_test.rb new file mode 100644 index 0000000000..bb2829b3c1 --- /dev/null +++ b/activerecord/test/cases/cache_key_test.rb @@ -0,0 +1,25 @@ +require "cases/helper" + +module ActiveRecord +  class CacheKeyTest < ActiveRecord::TestCase +    self.use_transactional_tests = false + +    class CacheMe < ActiveRecord::Base; end + +    setup do +      @connection = ActiveRecord::Base.connection +      @connection.create_table(:cache_mes) { |t| t.timestamps } +    end + +    teardown do +      @connection.drop_table :cache_mes, if_exists: true +    end + +    test "test_cache_key_format_is_not_too_precise" do +      record = CacheMe.create +      key = record.cache_key + +      assert_equal key, record.reload.cache_key +    end +  end +end diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb index aa10817527..ba69823250 100644 --- a/activerecord/test/cases/calculations_test.rb +++ b/activerecord/test/cases/calculations_test.rb @@ -102,6 +102,25 @@ class CalculationsTest < ActiveRecord::TestCase      assert_equal 60,   c[2]    end +  def test_should_generate_valid_sql_with_joins_and_group +    assert_nothing_raised ActiveRecord::StatementInvalid do +      AuditLog.joins(:developer).group(:id).count +    end +  end + +  def test_should_calculate_against_given_relation +    developer = Developer.create!(name: "developer") +    developer.audit_logs.create!(message: "first log") +    developer.audit_logs.create!(message: "second log") + +    c = developer.audit_logs.joins(:developer).group(:id).count + +    assert_equal developer.audit_logs.count, c.size +    developer.audit_logs.each do |log| +      assert_equal 1, c[log.id] +    end +  end +    def test_should_order_by_grouped_field      c = Account.group(:firm_id).order("firm_id").sum(:credit_limit)      assert_equal [1, 2, 6, 9], c.keys.compact @@ -681,4 +700,36 @@ class CalculationsTest < ActiveRecord::TestCase      end      assert block_called    end + +  def test_having_with_strong_parameters +    protected_params = Class.new do +      attr_reader :permitted +      alias :permitted? :permitted + +      def initialize(parameters) +        @parameters = parameters +        @permitted = false +      end + +      def to_h +        @parameters +      end + +      def permit! +        @permitted = true +        self +      end +    end + +    params = protected_params.new(credit_limit: '50') + +    assert_raises(ActiveModel::ForbiddenAttributesError) do +      Account.group(:id).having(params) +    end + +    result = Account.group(:id).having(params.permit!) +    assert_equal 50, result[0].credit_limit +    assert_equal 50, result[1].credit_limit +    assert_equal 50, result[2].credit_limit +  end  end diff --git a/activerecord/test/cases/collection_cache_key_test.rb b/activerecord/test/cases/collection_cache_key_test.rb new file mode 100644 index 0000000000..53058c5a4a --- /dev/null +++ b/activerecord/test/cases/collection_cache_key_test.rb @@ -0,0 +1,70 @@ +require "cases/helper" +require "models/computer" +require "models/developer" +require "models/project" +require "models/topic" +require "models/post" +require "models/comment" + +module ActiveRecord +  class CollectionCacheKeyTest < ActiveRecord::TestCase +    fixtures :developers, :projects, :developers_projects, :topics, :comments, :posts + +    test "collection_cache_key on model" do +      assert_match(/\Adevelopers\/query-(\h+)-(\d+)-(\d+)\Z/, Developer.collection_cache_key) +    end + +    test "cache_key for relation" do +      developers = Developer.where(name: "David") +      last_developer_timestamp = developers.order(updated_at: :desc).first.updated_at + +      assert_match(/\Adevelopers\/query-(\h+)-(\d+)-(\d+)\Z/, developers.cache_key) + +      /\Adevelopers\/query-(\h+)-(\d+)-(\d+)\Z/ =~ developers.cache_key + +      assert_equal Digest::MD5.hexdigest(developers.to_sql), $1 +      assert_equal developers.count.to_s, $2 +      assert_equal last_developer_timestamp.to_s(ActiveRecord::Base.cache_timestamp_format), $3 +    end + +    test "it triggers at most one query" do +      developers =  Developer.where(name: "David") + +      assert_queries(1) { developers.cache_key } +      assert_queries(0) { developers.cache_key } +    end + +    test "it doesn't trigger any query if the relation is already loaded" do +      developers =  Developer.where(name: "David").load +      assert_queries(0) { developers.cache_key } +    end + +    test "relation cache_key changes when the sql query changes" do +      developers = Developer.where(name: "David") +      other_relation =  Developer.where(name: "David").where("1 = 1") + +      assert_not_equal developers.cache_key, other_relation.cache_key +    end + +    test "cache_key for empty relation" do +      developers = Developer.where(name: "Non Existent Developer") +      assert_match(/\Adevelopers\/query-(\h+)-0\Z/, developers.cache_key) +    end + +    test "cache_key with custom timestamp column" do +      topics = Topic.where("title like ?", "%Topic%") +      last_topic_timestamp = topics(:fifth).written_on.utc.to_s(:usec) +      assert_match(last_topic_timestamp, topics.cache_key(:written_on)) +    end + +    test "cache_key with unknown timestamp column" do +      topics = Topic.where("title like ?", "%Topic%") +      assert_raises(ActiveRecord::StatementInvalid) { topics.cache_key(:published_at) } +    end + +    test "collection proxy provides a cache_key" do +      developers = projects(:active_record).developers +      assert_match(/\Adevelopers\/query-(\h+)-(\d+)-(\d+)\Z/, developers.cache_key) +    end +  end +end diff --git a/activerecord/test/cases/connection_adapters/mysql_type_lookup_test.rb b/activerecord/test/cases/connection_adapters/mysql_type_lookup_test.rb index 80244d1439..2749273884 100644 --- a/activerecord/test/cases/connection_adapters/mysql_type_lookup_test.rb +++ b/activerecord/test/cases/connection_adapters/mysql_type_lookup_test.rb @@ -22,6 +22,10 @@ module ActiveRecord          assert_lookup_type :string, "SET('one', 'two', 'three')"        end +      def test_set_type_with_value_matching_other_type +        assert_lookup_type :string, "SET('unicode', '8bit', 'none', 'time')" +      end +        def test_enum_type_with_value_matching_other_type          assert_lookup_type :string, "ENUM('unicode', '8bit', 'none')"        end diff --git a/activerecord/test/cases/connection_adapters/schema_cache_test.rb b/activerecord/test/cases/connection_adapters/schema_cache_test.rb index c7531f5418..db832fe55d 100644 --- a/activerecord/test/cases/connection_adapters/schema_cache_test.rb +++ b/activerecord/test/cases/connection_adapters/schema_cache_test.rb @@ -29,7 +29,7 @@ module ActiveRecord        def test_clearing          @cache.columns('posts')          @cache.columns_hash('posts') -        @cache.tables('posts') +        @cache.data_sources('posts')          @cache.primary_keys('posts')          @cache.clear! @@ -40,17 +40,22 @@ module ActiveRecord        def test_dump_and_load          @cache.columns('posts')          @cache.columns_hash('posts') -        @cache.tables('posts') +        @cache.data_sources('posts')          @cache.primary_keys('posts')          @cache = Marshal.load(Marshal.dump(@cache))          assert_equal 11, @cache.columns('posts').size          assert_equal 11, @cache.columns_hash('posts').size -        assert @cache.tables('posts') +        assert @cache.data_sources('posts')          assert_equal 'id', @cache.primary_keys('posts')        end +      def test_table_methods_deprecation +        assert_deprecated { assert @cache.table_exists?('posts') } +        assert_deprecated { assert @cache.tables('posts') } +        assert_deprecated { @cache.clear_table_cache!('posts') } +      end      end    end  end diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb index c905772193..7ef5c93a48 100644 --- a/activerecord/test/cases/connection_pool_test.rb +++ b/activerecord/test/cases/connection_pool_test.rb @@ -1,5 +1,5 @@  require "cases/helper" -require 'active_support/concurrency/latch' +require 'concurrent/atomics'  module ActiveRecord    module ConnectionAdapters @@ -133,15 +133,15 @@ module ActiveRecord        end        def test_reap_inactive -        ready = ActiveSupport::Concurrency::Latch.new +        ready = Concurrent::CountDownLatch.new          @pool.checkout          child = Thread.new do            @pool.checkout            @pool.checkout -          ready.release +          ready.count_down            Thread.stop          end -        ready.await +        ready.wait          assert_equal 3, active_connections(@pool).size @@ -360,13 +360,13 @@ module ActiveRecord        def test_concurrent_connection_establishment          assert_operator @pool.connections.size, :<=, 1 -        all_threads_in_new_connection = ActiveSupport::Concurrency::Latch.new(@pool.size - @pool.connections.size) -        all_go                        = ActiveSupport::Concurrency::Latch.new +        all_threads_in_new_connection = Concurrent::CountDownLatch.new(@pool.size - @pool.connections.size) +        all_go                        = Concurrent::CountDownLatch.new          @pool.singleton_class.class_eval do            define_method(:new_connection) do -            all_threads_in_new_connection.release -            all_go.await +            all_threads_in_new_connection.count_down +            all_go.wait              super()            end          end @@ -381,14 +381,14 @@ module ActiveRecord              # the kernel of the whole test is here, everything else is just scaffolding,              # this latch will not be released unless conn. pool allows for concurrent              # connection creation -            all_threads_in_new_connection.await +            all_threads_in_new_connection.wait            end          rescue Timeout::Error            flunk 'pool unable to establish connections concurrently or implementation has ' <<                  'changed, this test then needs to patch a different :new_connection method'          ensure            # clean up the threads -          all_go.release +          all_go.count_down            connecting_threads.map(&:join)          end        end @@ -441,11 +441,11 @@ module ActiveRecord          with_single_connection_pool do |pool|            [:disconnect, :disconnect!, :clear_reloadable_connections, :clear_reloadable_connections!].each do |group_action_method|              conn               = pool.connection # drain the only available connection -            second_thread_done = ActiveSupport::Concurrency::Latch.new +            second_thread_done = Concurrent::CountDownLatch.new              # create a first_thread and let it get into the FIFO queue first              first_thread = Thread.new do -              pool.with_connection { second_thread_done.await } +              pool.with_connection { second_thread_done.wait }              end              # wait for first_thread to get in queue @@ -456,7 +456,7 @@ module ActiveRecord              # first_thread when a connection is made available              second_thread = Thread.new do                pool.send(group_action_method) -              second_thread_done.release +              second_thread_done.count_down              end              # wait for second_thread to get in queue @@ -471,7 +471,7 @@ module ActiveRecord              failed = true unless second_thread.join(2)              #--- post test clean up start -            second_thread_done.release if failed +            second_thread_done.count_down if failed              # after `pool.disconnect()` the first thread will be left stuck in queue, no need to wait for              # it to timeout with ConnectionTimeoutError diff --git a/activerecord/test/cases/counter_cache_test.rb b/activerecord/test/cases/counter_cache_test.rb index 1f5055b2a2..922cb59280 100644 --- a/activerecord/test/cases/counter_cache_test.rb +++ b/activerecord/test/cases/counter_cache_test.rb @@ -1,6 +1,7 @@  require 'cases/helper'  require 'models/topic'  require 'models/car' +require 'models/aircraft'  require 'models/wheel'  require 'models/engine'  require 'models/reply' @@ -198,4 +199,16 @@ class CounterCacheTest < ActiveRecord::TestCase      assert_equal 2, car.engines_count      assert_equal 2, car.reload.engines_count    end + +  test "update counters in a polymorphic relationship" do +    aircraft = Aircraft.create! + +    assert_difference 'aircraft.reload.wheels_count' do +      aircraft.wheels << Wheel.create! +    end + +    assert_difference 'aircraft.reload.wheels_count', -1 do +      aircraft.wheels.first.destroy +    end +  end  end diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb index f5aaf22e13..cd1967c373 100644 --- a/activerecord/test/cases/dirty_test.rb +++ b/activerecord/test/cases/dirty_test.rb @@ -89,7 +89,7 @@ class DirtyTest < ActiveRecord::TestCase        target = Class.new(ActiveRecord::Base)        target.table_name = 'pirates' -      pirate = target.create +      pirate = target.create!        pirate.created_on = pirate.created_on        assert !pirate.created_on_changed?      end @@ -467,8 +467,10 @@ class DirtyTest < ActiveRecord::TestCase        topic.save!        updated_at = topic.updated_at -      topic.content[:hello] = 'world' -      topic.save! +      travel(1.second) do +        topic.content[:hello] = 'world' +        topic.save! +      end        assert_not_equal updated_at, topic.updated_at        assert_equal 'world', topic.content[:hello] @@ -521,6 +523,9 @@ class DirtyTest < ActiveRecord::TestCase      assert_equal Hash.new, pirate.previous_changes      pirate = Pirate.find_by_catchphrase("arrr") + +    travel(1.second) +      pirate.catchphrase = "Me Maties!"      pirate.save! @@ -532,6 +537,9 @@ class DirtyTest < ActiveRecord::TestCase      assert !pirate.previous_changes.key?('created_on')      pirate = Pirate.find_by_catchphrase("Me Maties!") + +    travel(1.second) +      pirate.catchphrase = "Thar She Blows!"      pirate.save @@ -542,6 +550,8 @@ class DirtyTest < ActiveRecord::TestCase      assert !pirate.previous_changes.key?('parrot_id')      assert !pirate.previous_changes.key?('created_on') +    travel(1.second) +      pirate = Pirate.find_by_catchphrase("Thar She Blows!")      pirate.update(catchphrase: "Ahoy!") @@ -552,6 +562,8 @@ class DirtyTest < ActiveRecord::TestCase      assert !pirate.previous_changes.key?('parrot_id')      assert !pirate.previous_changes.key?('created_on') +    travel(1.second) +      pirate = Pirate.find_by_catchphrase("Ahoy!")      pirate.update_attribute(:catchphrase, "Ninjas suck!") @@ -561,6 +573,8 @@ class DirtyTest < ActiveRecord::TestCase      assert_not_nil pirate.previous_changes['updated_on'][1]      assert !pirate.previous_changes.key?('parrot_id')      assert !pirate.previous_changes.key?('created_on') +  ensure +    travel_back    end    if ActiveRecord::Base.connection.supports_migrations? @@ -578,6 +592,7 @@ class DirtyTest < ActiveRecord::TestCase    end    def test_datetime_attribute_can_be_updated_with_fractional_seconds +    skip "Fractional seconds are not supported" unless subsecond_precision_supported?      in_time_zone 'Paris' do        target = Class.new(ActiveRecord::Base)        target.table_name = 'topics' diff --git a/activerecord/test/cases/enum_test.rb b/activerecord/test/cases/enum_test.rb index ba23049a92..7c930de97b 100644 --- a/activerecord/test/cases/enum_test.rb +++ b/activerecord/test/cases/enum_test.rb @@ -40,43 +40,43 @@ class EnumTest < ActiveRecord::TestCase      published, written = Book.statuses[:published], Book.statuses[:written]      assert_equal @book, Book.where(status: published).first -    refute_equal @book, Book.where(status: written).first +    assert_not_equal @book, Book.where(status: written).first      assert_equal @book, Book.where(status: [published]).first -    refute_equal @book, Book.where(status: [written]).first -    refute_equal @book, Book.where("status <> ?", published).first +    assert_not_equal @book, Book.where(status: [written]).first +    assert_not_equal @book, Book.where("status <> ?", published).first      assert_equal @book, Book.where("status <> ?", written).first    end    test "find via where with symbols" do      assert_equal @book, Book.where(status: :published).first -    refute_equal @book, Book.where(status: :written).first +    assert_not_equal @book, Book.where(status: :written).first      assert_equal @book, Book.where(status: [:published]).first -    refute_equal @book, Book.where(status: [:written]).first -    refute_equal @book, Book.where.not(status: :published).first +    assert_not_equal @book, Book.where(status: [:written]).first +    assert_not_equal @book, Book.where.not(status: :published).first      assert_equal @book, Book.where.not(status: :written).first    end    test "find via where with strings" do      assert_equal @book, Book.where(status: "published").first -    refute_equal @book, Book.where(status: "written").first +    assert_not_equal @book, Book.where(status: "written").first      assert_equal @book, Book.where(status: ["published"]).first -    refute_equal @book, Book.where(status: ["written"]).first -    refute_equal @book, Book.where.not(status: "published").first +    assert_not_equal @book, Book.where(status: ["written"]).first +    assert_not_equal @book, Book.where.not(status: "published").first      assert_equal @book, Book.where.not(status: "written").first    end    test "build from scope" do      assert Book.written.build.written? -    refute Book.written.build.proposed? +    assert_not Book.written.build.proposed?    end    test "build from where" do      assert Book.where(status: Book.statuses[:written]).build.written? -    refute Book.where(status: Book.statuses[:written]).build.proposed? +    assert_not Book.where(status: Book.statuses[:written]).build.proposed?      assert Book.where(status: :written).build.written? -    refute Book.where(status: :written).build.proposed? +    assert_not Book.where(status: :written).build.proposed?      assert Book.where(status: "written").build.written? -    refute Book.where(status: "written").build.proposed? +    assert_not Book.where(status: "written").build.proposed?    end    test "update by declaration" do diff --git a/activerecord/test/cases/errors_test.rb b/activerecord/test/cases/errors_test.rb new file mode 100644 index 0000000000..0711a372f2 --- /dev/null +++ b/activerecord/test/cases/errors_test.rb @@ -0,0 +1,16 @@ +require_relative "../cases/helper" + +class ErrorsTest < ActiveRecord::TestCase +  def test_can_be_instantiated_with_no_args +    base = ActiveRecord::ActiveRecordError +    error_klasses = ObjectSpace.each_object(Class).select { |klass| klass < base } + +    error_klasses.each do |error_klass| +      begin +        error_klass.new.inspect +      rescue ArgumentError +        raise "Instance of #{error_klass} can't be initialized with no arguments" +      end +    end +  end +end diff --git a/activerecord/test/cases/explain_test.rb b/activerecord/test/cases/explain_test.rb index f1d5511bb8..64dfd86ce2 100644 --- a/activerecord/test/cases/explain_test.rb +++ b/activerecord/test/cases/explain_test.rb @@ -39,38 +39,49 @@ if ActiveRecord::Base.connection.supports_explain?        binds   = [[], []]        queries = sqls.zip(binds) -      connection.stubs(:explain).returns('query plan foo', 'query plan bar') -      expected = sqls.map {|sql| "EXPLAIN for: #{sql}\nquery plan #{sql}"}.join("\n") -      assert_equal expected, base.exec_explain(queries) +      stub_explain_for_query_plans do +        expected = sqls.map {|sql| "EXPLAIN for: #{sql}\nquery plan #{sql}"}.join("\n") +        assert_equal expected, base.exec_explain(queries) +      end      end      def test_exec_explain_with_binds -      cols = [Object.new, Object.new] -      cols[0].expects(:name).returns('wadus') -      cols[1].expects(:name).returns('chaflan') +      object = Struct.new(:name) +      cols = [object.new('wadus'), object.new('chaflan')]        sqls    = %w(foo bar)        binds   = [[[cols[0], 1]], [[cols[1], 2]]]        queries = sqls.zip(binds) -      connection.stubs(:explain).returns("query plan foo\n", "query plan bar\n") -      expected = <<-SQL.strip_heredoc -        EXPLAIN for: #{sqls[0]} [["wadus", 1]] -        query plan foo +      stub_explain_for_query_plans(["query plan foo\n", "query plan bar\n"]) do +        expected = <<-SQL.strip_heredoc +          EXPLAIN for: #{sqls[0]} [["wadus", 1]] +          query plan foo -        EXPLAIN for: #{sqls[1]} [["chaflan", 2]] -        query plan bar -      SQL -      assert_equal expected, base.exec_explain(queries) +          EXPLAIN for: #{sqls[1]} [["chaflan", 2]] +          query plan bar +        SQL +        assert_equal expected, base.exec_explain(queries) +      end      end      def test_unsupported_connection_adapter -      connection.stubs(:supports_explain?).returns(false) +      connection.stub(:supports_explain?, false) do +        assert_not_called(base.logger, :warn) do +          Car.where(:name => 'honda').to_a +        end +      end +    end -      base.logger.expects(:warn).never +    private -      Car.where(:name => 'honda').to_a -    end +      def stub_explain_for_query_plans(query_plans = ['query plan foo', 'query plan bar']) +        explain_called = 0 + +        connection.stub(:explain, proc{ explain_called += 1; query_plans[explain_called - 1] }) do +          yield +        end +      end    end  end diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index 4b819a82e8..6686ce012d 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -178,8 +178,9 @@ class FinderTest < ActiveRecord::TestCase    end    def test_exists_does_not_instantiate_records -    Developer.expects(:instantiate).never -    Developer.exists? +    assert_not_called(Developer, :instantiate) do +      Developer.exists? +    end    end    def test_find_by_array_of_one_id @@ -264,6 +265,12 @@ class FinderTest < ActiveRecord::TestCase      assert_equal [Account], accounts.collect(&:class).uniq    end +  def test_find_by_association_subquery +    author = authors(:david) +    assert_equal author.post, Post.find_by(author: Author.where(id: author)) +    assert_equal author.post, Post.find_by(author_id: Author.where(id: author)) +  end +    def test_take      assert_equal topics(:first), Topic.take    end @@ -700,12 +707,12 @@ class FinderTest < ActiveRecord::TestCase    end    def test_bind_arity -    assert_nothing_raised                                 { bind '' } +    assert_nothing_raised                                { bind '' }      assert_raise(ActiveRecord::PreparedStatementInvalid) { bind '', 1 }      assert_raise(ActiveRecord::PreparedStatementInvalid) { bind '?' } -    assert_nothing_raised                                 { bind '?', 1 } -    assert_raise(ActiveRecord::PreparedStatementInvalid) { bind '?', 1, 1  } +    assert_nothing_raised                                { bind '?', 1 } +    assert_raise(ActiveRecord::PreparedStatementInvalid) { bind '?', 1, 1 }    end    def test_named_bind_variables @@ -720,6 +727,12 @@ class FinderTest < ActiveRecord::TestCase      assert_kind_of Time, Topic.where(["id = :id", { id: 1 }]).first.written_on    end +  def test_named_bind_arity +    assert_nothing_raised                                { bind "name = :name", { name: "37signals" } } +    assert_nothing_raised                                { bind "name = :name", { name: "37signals", id: 1 } } +    assert_raise(ActiveRecord::PreparedStatementInvalid) { bind "name = :name", { id: 1 } } +  end +    class SimpleEnumerable      include Enumerable diff --git a/activerecord/test/cases/fixture_set/file_test.rb b/activerecord/test/cases/fixture_set/file_test.rb index 92efa8aca7..242e7a9bec 100644 --- a/activerecord/test/cases/fixture_set/file_test.rb +++ b/activerecord/test/cases/fixture_set/file_test.rb @@ -123,6 +123,18 @@ END          end        end +      def test_removes_fixture_config_row +        File.open(::File.join(FIXTURES_ROOT, 'other_posts.yml')) do |fh| +          assert_equal(['second_welcome'], fh.each.map { |name, _| name }) +        end +      end + +      def test_extracts_model_class_from_config_row +        File.open(::File.join(FIXTURES_ROOT, 'other_posts.yml')) do |fh| +          assert_equal 'Post', fh.model_class +        end +      end +        private        def tmp_yaml(name, contents)          t = Tempfile.new name diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb index 03a187ae92..a0eaa66e94 100644 --- a/activerecord/test/cases/fixtures_test.rb +++ b/activerecord/test/cases/fixtures_test.rb @@ -7,16 +7,16 @@ require 'models/binary'  require 'models/book'  require 'models/bulb'  require 'models/category' +require 'models/comment'  require 'models/company'  require 'models/computer'  require 'models/course'  require 'models/developer' -require 'models/computer' +require 'models/doubloon'  require 'models/joke'  require 'models/matey'  require 'models/parrot'  require 'models/pirate' -require 'models/doubloon'  require 'models/post'  require 'models/randomly_named_c1'  require 'models/reply' @@ -217,6 +217,13 @@ class FixturesTest < ActiveRecord::TestCase      end    end +  def test_yaml_file_with_invalid_column +    e = assert_raise(ActiveRecord::Fixture::FixtureError) do +      ActiveRecord::FixtureSet.create_fixtures(FIXTURES_ROOT + "/naked/yml", "parrots") +    end +    assert_equal(%(table "parrots" has no column named "arrr".), e.message) +  end +    def test_omap_fixtures      assert_nothing_raised do        fixtures = ActiveRecord::FixtureSet.new(Account.connection, 'categories', Category, FIXTURES_ROOT + "/categories_ordered") @@ -252,18 +259,19 @@ class FixturesTest < ActiveRecord::TestCase    def test_fixtures_are_set_up_with_database_env_variable      db_url_tmp = ENV['DATABASE_URL']      ENV['DATABASE_URL'] = "sqlite3::memory:" -    ActiveRecord::Base.stubs(:configurations).returns({}) -    test_case = Class.new(ActiveRecord::TestCase) do -      fixtures :accounts +    ActiveRecord::Base.stub(:configurations, {}) do +      test_case = Class.new(ActiveRecord::TestCase) do +        fixtures :accounts -      def test_fixtures -        assert accounts(:signals37) +        def test_fixtures +          assert accounts(:signals37) +        end        end -    end -    result = test_case.new(:test_fixtures).run +      result = test_case.new(:test_fixtures).run -    assert result.passed?, "Expected #{result.name} to pass:\n#{result}" +      assert result.passed?, "Expected #{result.name} to pass:\n#{result}" +    end    ensure      ENV['DATABASE_URL'] = db_url_tmp    end @@ -401,9 +409,11 @@ class FixturesWithoutInstantiationTest < ActiveRecord::TestCase    end    def test_reloading_fixtures_through_accessor_methods +    topic = Struct.new(:title)      assert_equal "The First Topic", topics(:first).title -    @loaded_fixtures['topics']['first'].expects(:find).returns(stub(:title => "Fresh Topic!")) -    assert_equal "Fresh Topic!", topics(:first, true).title +    assert_called(@loaded_fixtures['topics']['first'], :find, returns: topic.new("Fresh Topic!")) do +      assert_equal "Fresh Topic!", topics(:first, true).title +    end    end  end @@ -507,6 +517,38 @@ class OverRideFixtureMethodTest < ActiveRecord::TestCase    end  end +class FixtureWithSetModelClassTest < ActiveRecord::TestCase +  fixtures :other_posts, :other_comments + +  # Set to false to blow away fixtures cache and ensure our fixtures are loaded +  # and thus takes into account the +set_model_class+. +  self.use_transactional_tests = false + +  def test_uses_fixture_class_defined_in_yaml +    assert_kind_of Post, other_posts(:second_welcome) +  end + +  def test_loads_the_associations_to_fixtures_with_set_model_class +    post = other_posts(:second_welcome) +    comment = other_comments(:second_greetings) +    assert_equal [comment], post.comments +    assert_equal post, comment.post +  end +end + +class SetFixtureClassPrevailsTest < ActiveRecord::TestCase +  set_fixture_class bad_posts: Post +  fixtures :bad_posts + +  # Set to false to blow away fixtures cache and ensure our fixtures are loaded +  # and thus takes into account the +set_model_class+. +  self.use_transactional_tests = false + +  def test_uses_set_fixture_class +    assert_kind_of Post, bad_posts(:bad_welcome) +  end +end +  class CheckSetTableNameFixturesTest < ActiveRecord::TestCase    set_fixture_class :funny_jokes => Joke    fixtures :funny_jokes diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb index 12c793c408..d82a3040fc 100644 --- a/activerecord/test/cases/helper.rb +++ b/activerecord/test/cases/helper.rb @@ -3,6 +3,7 @@ require File.expand_path('../../../../load_paths', __FILE__)  require 'config'  require 'active_support/testing/autorun' +require 'active_support/testing/method_call_assertions'  require 'stringio'  require 'active_record' @@ -45,9 +46,8 @@ def in_memory_db?    ActiveRecord::Base.connection_pool.spec.config[:database] == ":memory:"  end -def mysql_56? -  current_adapter?(:MysqlAdapter, :Mysql2Adapter) && -    ActiveRecord::Base.connection.send(:version).join(".") >= "5.6.0" +def subsecond_precision_supported? +  !current_adapter?(:MysqlAdapter, :Mysql2Adapter) || ActiveRecord::Base.connection.version >= '5.6.4'  end  def mysql_enforcing_gtid_consistency? @@ -140,6 +140,7 @@ require "cases/validations_repair_helper"  class ActiveSupport::TestCase    include ActiveRecord::TestFixtures    include ActiveRecord::ValidationsRepairHelper +  include ActiveSupport::Testing::MethodCallAssertions    self.fixture_path = FIXTURES_ROOT    self.use_instantiated_fixtures  = false diff --git a/activerecord/test/cases/integration_test.rb b/activerecord/test/cases/integration_test.rb index 018b7b0d8f..08a186ae07 100644 --- a/activerecord/test/cases/integration_test.rb +++ b/activerecord/test/cases/integration_test.rb @@ -81,7 +81,7 @@ class IntegrationTest < ActiveRecord::TestCase    def test_cache_key_format_for_existing_record_with_updated_at      dev = Developer.first -    assert_equal "developers/#{dev.id}-#{dev.updated_at.utc.to_s(:nsec)}", dev.cache_key +    assert_equal "developers/#{dev.id}-#{dev.updated_at.utc.to_s(:usec)}", dev.cache_key    end    def test_cache_key_format_for_existing_record_with_updated_at_and_custom_cache_timestamp_format @@ -96,7 +96,9 @@ class IntegrationTest < ActiveRecord::TestCase      owner.update_column :updated_at, Time.current      key = owner.cache_key -    assert pet.touch +    travel(1.second) do +      assert pet.touch +    end      assert_not_equal key, owner.reload.cache_key    end @@ -109,30 +111,39 @@ class IntegrationTest < ActiveRecord::TestCase    def test_cache_key_for_updated_on      dev = Developer.first      dev.updated_at = nil -    assert_equal "developers/#{dev.id}-#{dev.updated_on.utc.to_s(:nsec)}", dev.cache_key +    assert_equal "developers/#{dev.id}-#{dev.updated_on.utc.to_s(:usec)}", dev.cache_key    end    def test_cache_key_for_newer_updated_at      dev = Developer.first      dev.updated_at += 3600 -    assert_equal "developers/#{dev.id}-#{dev.updated_at.utc.to_s(:nsec)}", dev.cache_key +    assert_equal "developers/#{dev.id}-#{dev.updated_at.utc.to_s(:usec)}", dev.cache_key    end    def test_cache_key_for_newer_updated_on      dev = Developer.first      dev.updated_on += 3600 -    assert_equal "developers/#{dev.id}-#{dev.updated_on.utc.to_s(:nsec)}", dev.cache_key +    assert_equal "developers/#{dev.id}-#{dev.updated_on.utc.to_s(:usec)}", dev.cache_key    end    def test_cache_key_format_is_precise_enough +    skip("Subsecond precision is not supported") unless subsecond_precision_supported?      dev = Developer.first      key = dev.cache_key      dev.touch      assert_not_equal key, dev.cache_key    end +  def test_cache_key_format_is_not_too_precise +    skip("Subsecond precision is not supported") unless subsecond_precision_supported? +    dev = Developer.first +    dev.touch +    key = dev.cache_key +    assert_equal key, dev.reload.cache_key +  end +    def test_named_timestamps_for_cache_key      owner = owners(:blackbeard) -    assert_equal "owners/#{owner.id}-#{owner.happy_at.utc.to_s(:nsec)}", owner.cache_key(:updated_at, :happy_at) +    assert_equal "owners/#{owner.id}-#{owner.happy_at.utc.to_s(:usec)}", owner.cache_key(:updated_at, :happy_at)    end  end diff --git a/activerecord/test/cases/invertible_migration_test.rb b/activerecord/test/cases/invertible_migration_test.rb index 99230aa3d5..84b0ff8fcb 100644 --- a/activerecord/test/cases/invertible_migration_test.rb +++ b/activerecord/test/cases/invertible_migration_test.rb @@ -1,5 +1,8 @@  require "cases/helper" +class Horse < ActiveRecord::Base +end +  module ActiveRecord    class InvertibleMigrationTest < ActiveRecord::TestCase      class SilentMigration < ActiveRecord::Migration @@ -76,6 +79,32 @@ module ActiveRecord        end      end +    class ChangeColumnDefault1 < SilentMigration +      def change +        create_table("horses") do |t| +          t.column :name, :string, default: "Sekitoba" +        end +      end +    end + +    class ChangeColumnDefault2 < SilentMigration +      def change +        change_column_default :horses, :name, from: "Sekitoba", to: "Diomed" +      end +    end + +    class DisableExtension1 < SilentMigration +      def change +        enable_extension "hstore" +      end +    end + +    class DisableExtension2 < SilentMigration +      def change +        disable_extension "hstore" +      end +    end +      class LegacyMigration < ActiveRecord::Migration        def self.up          create_table("horses") do |t| @@ -223,6 +252,42 @@ module ActiveRecord        assert !revert.connection.table_exists?("horses")      end +    def test_migrate_revert_change_column_default +      migration1 = ChangeColumnDefault1.new +      migration1.migrate(:up) +      assert_equal "Sekitoba", Horse.new.name + +      migration2 = ChangeColumnDefault2.new +      migration2.migrate(:up) +      Horse.reset_column_information +      assert_equal "Diomed", Horse.new.name + +      migration2.migrate(:down) +      Horse.reset_column_information +      assert_equal "Sekitoba", Horse.new.name +    end + +    if current_adapter?(:PostgreSQLAdapter) +      def test_migrate_enable_and_disable_extension +        migration1 = InvertibleMigration.new +        migration2 = DisableExtension1.new +        migration3 = DisableExtension2.new + +        migration1.migrate(:up) +        migration2.migrate(:up) +        assert_equal true, Horse.connection.extension_enabled?('hstore') + +        migration3.migrate(:up) +        assert_equal false, Horse.connection.extension_enabled?('hstore') + +        migration3.migrate(:down) +        assert_equal true, Horse.connection.extension_enabled?('hstore') + +        migration2.migrate(:down) +        assert_equal false, Horse.connection.extension_enabled?('hstore') +      end +    end +      def test_revert_order        block = Proc.new{|t| t.string :name }        recorder = ActiveRecord::Migration::CommandRecorder.new(ActiveRecord::Base.connection) diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb index dbdcc84b7d..2e1363334d 100644 --- a/activerecord/test/cases/locking_test.rb +++ b/activerecord/test/cases/locking_test.rb @@ -270,7 +270,7 @@ class OptimisticLockingTest < ActiveRecord::TestCase        car.wheels << Wheel.create!      end      assert_difference 'car.wheels.count', -1  do -      car.destroy +      car.reload.destroy      end      assert car.destroyed?    end diff --git a/activerecord/test/cases/log_subscriber_test.rb b/activerecord/test/cases/log_subscriber_test.rb index 4192d12ff4..3846ba8e7f 100644 --- a/activerecord/test/cases/log_subscriber_test.rb +++ b/activerecord/test/cases/log_subscriber_test.rb @@ -7,6 +7,20 @@ require "active_support/log_subscriber/test_helper"  class LogSubscriberTest < ActiveRecord::TestCase    include ActiveSupport::LogSubscriber::TestHelper    include ActiveSupport::Logger::Severity +  REGEXP_CLEAR = Regexp.escape(ActiveRecord::LogSubscriber::CLEAR) +  REGEXP_BOLD = Regexp.escape(ActiveRecord::LogSubscriber::BOLD) +  REGEXP_MAGENTA = Regexp.escape(ActiveRecord::LogSubscriber::MAGENTA) +  REGEXP_CYAN = Regexp.escape(ActiveRecord::LogSubscriber::CYAN) +  SQL_COLORINGS = { +      SELECT: Regexp.escape(ActiveRecord::LogSubscriber::BLUE), +      INSERT: Regexp.escape(ActiveRecord::LogSubscriber::GREEN), +      UPDATE: Regexp.escape(ActiveRecord::LogSubscriber::YELLOW), +      DELETE: Regexp.escape(ActiveRecord::LogSubscriber::RED), +      LOCK: Regexp.escape(ActiveRecord::LogSubscriber::WHITE), +      ROLLBACK: Regexp.escape(ActiveRecord::LogSubscriber::RED), +      TRANSACTION: REGEXP_CYAN, +      OTHER: REGEXP_MAGENTA +  }    class TestDebugLogSubscriber < ActiveRecord::LogSubscriber      attr_reader :debugs @@ -71,6 +85,90 @@ class LogSubscriberTest < ActiveRecord::TestCase      assert_match(/SELECT .*?FROM .?developers.?/i, @logger.logged(:debug).last)    end +  def test_basic_query_logging_coloration +    event = Struct.new(:duration, :payload) +    logger = TestDebugLogSubscriber.new +    logger.colorize_logging = true +    SQL_COLORINGS.each do |verb, color_regex| +      logger.sql(event.new(0, sql: verb.to_s)) +      assert_match(/#{REGEXP_BOLD}#{color_regex}#{verb}#{REGEXP_CLEAR}/i, logger.debugs.last) +    end +  end + +  def test_basic_payload_name_logging_coloration_generic_sql +    event = Struct.new(:duration, :payload) +    logger = TestDebugLogSubscriber.new +    logger.colorize_logging = true +    SQL_COLORINGS.each do |verb, _| +      logger.sql(event.new(0, sql: verb.to_s)) +      assert_match(/#{REGEXP_BOLD}#{REGEXP_MAGENTA} \(0.0ms\)#{REGEXP_CLEAR}/i, logger.debugs.last) + +      logger.sql(event.new(0, {sql: verb.to_s, name: "SQL"})) +      assert_match(/#{REGEXP_BOLD}#{REGEXP_MAGENTA}SQL \(0.0ms\)#{REGEXP_CLEAR}/i, logger.debugs.last) +    end +  end + +  def test_basic_payload_name_logging_coloration_named_sql +    event = Struct.new(:duration, :payload) +    logger = TestDebugLogSubscriber.new +    logger.colorize_logging = true +    SQL_COLORINGS.each do |verb, _| +      logger.sql(event.new(0, {sql: verb.to_s, name: "Model Load"})) +      assert_match(/#{REGEXP_BOLD}#{REGEXP_CYAN}Model Load \(0.0ms\)#{REGEXP_CLEAR}/i, logger.debugs.last) + +      logger.sql(event.new(0, {sql: verb.to_s, name: "Model Exists"})) +      assert_match(/#{REGEXP_BOLD}#{REGEXP_CYAN}Model Exists \(0.0ms\)#{REGEXP_CLEAR}/i, logger.debugs.last) + +      logger.sql(event.new(0, {sql: verb.to_s, name: "ANY SPECIFIC NAME"})) +      assert_match(/#{REGEXP_BOLD}#{REGEXP_CYAN}ANY SPECIFIC NAME \(0.0ms\)#{REGEXP_CLEAR}/i, logger.debugs.last) +    end +  end + +  def test_query_logging_coloration_with_nested_select +    event = Struct.new(:duration, :payload) +    logger = TestDebugLogSubscriber.new +    logger.colorize_logging = true +    SQL_COLORINGS.slice(:SELECT, :INSERT, :UPDATE, :DELETE).each do |verb, color_regex| +      logger.sql(event.new(0, sql: "#{verb} WHERE ID IN SELECT")) +      assert_match(/#{REGEXP_BOLD}#{REGEXP_MAGENTA} \(0.0ms\)#{REGEXP_CLEAR}  #{REGEXP_BOLD}#{color_regex}#{verb} WHERE ID IN SELECT#{REGEXP_CLEAR}/i, logger.debugs.last) +    end +  end + +  def test_query_logging_coloration_with_multi_line_nested_select +    event = Struct.new(:duration, :payload) +    logger = TestDebugLogSubscriber.new +    logger.colorize_logging = true +    SQL_COLORINGS.slice(:SELECT, :INSERT, :UPDATE, :DELETE).each do |verb, color_regex| +      sql = <<-EOS +        #{verb} +        WHERE ID IN ( +          SELECT ID FROM THINGS +        ) +      EOS +      logger.sql(event.new(0, sql: sql)) +      assert_match(/#{REGEXP_BOLD}#{REGEXP_MAGENTA} \(0.0ms\)#{REGEXP_CLEAR}  #{REGEXP_BOLD}#{color_regex}.*#{verb}.*#{REGEXP_CLEAR}/mi, logger.debugs.last) +    end +  end + +  def test_query_logging_coloration_with_lock +    event = Struct.new(:duration, :payload) +    logger = TestDebugLogSubscriber.new +    logger.colorize_logging = true +    sql = <<-EOS +      SELECT * FROM +        (SELECT * FROM mytable FOR UPDATE) ss +      WHERE col1 = 5; +    EOS +    logger.sql(event.new(0, sql: sql)) +    assert_match(/#{REGEXP_BOLD}#{REGEXP_MAGENTA} \(0.0ms\)#{REGEXP_CLEAR}  #{REGEXP_BOLD}#{SQL_COLORINGS[:LOCK]}.*FOR UPDATE.*#{REGEXP_CLEAR}/mi, logger.debugs.last) + +    sql = <<-EOS +      LOCK TABLE films IN SHARE MODE; +    EOS +    logger.sql(event.new(0, sql: sql)) +    assert_match(/#{REGEXP_BOLD}#{REGEXP_MAGENTA} \(0.0ms\)#{REGEXP_CLEAR}  #{REGEXP_BOLD}#{SQL_COLORINGS[:LOCK]}.*LOCK TABLE.*#{REGEXP_CLEAR}/mi, logger.debugs.last) +  end +    def test_exists_query_logging      Developer.exists? 1      wait diff --git a/activerecord/test/cases/migration/change_table_test.rb b/activerecord/test/cases/migration/change_table_test.rb index 2ffe7a1b0d..2f9c50141f 100644 --- a/activerecord/test/cases/migration/change_table_test.rb +++ b/activerecord/test/cases/migration/change_table_test.rb @@ -1,5 +1,4 @@  require "cases/migration/helper" -require "minitest/mock"  module ActiveRecord    class Migration diff --git a/activerecord/test/cases/migration/command_recorder_test.rb b/activerecord/test/cases/migration/command_recorder_test.rb index 99f1dc65b0..1e3529db54 100644 --- a/activerecord/test/cases/migration/command_recorder_test.rb +++ b/activerecord/test/cases/migration/command_recorder_test.rb @@ -31,7 +31,8 @@ module ActiveRecord        end        def test_unknown_commands_delegate -        recorder = CommandRecorder.new(stub(:foo => 'bar')) +        recorder = Struct.new(:foo) +        recorder = CommandRecorder.new(recorder.new('bar'))          assert_equal 'bar', recorder.foo        end diff --git a/activerecord/test/cases/migration/helper.rb b/activerecord/test/cases/migration/helper.rb index 5bc0898f33..ad85684c0b 100644 --- a/activerecord/test/cases/migration/helper.rb +++ b/activerecord/test/cases/migration/helper.rb @@ -28,7 +28,7 @@ module ActiveRecord          super          TestModel.reset_table_name          TestModel.reset_sequence_name -        connection.drop_table :test_models rescue nil +        connection.drop_table :test_models, if_exists: true        end        private diff --git a/activerecord/test/cases/migration/pending_migrations_test.rb b/activerecord/test/cases/migration/pending_migrations_test.rb index 7afac83bd2..4f5589f32a 100644 --- a/activerecord/test/cases/migration/pending_migrations_test.rb +++ b/activerecord/test/cases/migration/pending_migrations_test.rb @@ -1,5 +1,4 @@  require 'cases/helper' -require "minitest/mock"  module ActiveRecord    class Migration diff --git a/activerecord/test/cases/migration/postgresql_geometric_types_test.rb b/activerecord/test/cases/migration/postgresql_geometric_types_test.rb new file mode 100644 index 0000000000..e4772905bb --- /dev/null +++ b/activerecord/test/cases/migration/postgresql_geometric_types_test.rb @@ -0,0 +1,93 @@ +require 'cases/helper' + +module ActiveRecord +  class Migration +    class PostgreSQLGeometricTypesTest < ActiveRecord::TestCase +      attr_reader :connection, :table_name + +      def setup +        super +        @connection = ActiveRecord::Base.connection +        @table_name = :testings +      end + +      if current_adapter?(:PostgreSQLAdapter) +        def test_creating_column_with_point_type +          connection.create_table(table_name) do |t| +            t.point :foo_point +          end +           +          assert_column_exists(:foo_point) +          assert_type_correct(:foo_point, :point) +        end + +        def test_creating_column_with_line_type +          connection.create_table(table_name) do |t| +            t.line :foo_line +          end +           +          assert_column_exists(:foo_line) +          assert_type_correct(:foo_line, :line) +        end + +        def test_creating_column_with_lseg_type +          connection.create_table(table_name) do |t| +            t.lseg :foo_lseg +          end +           +          assert_column_exists(:foo_lseg) +          assert_type_correct(:foo_lseg, :lseg) +        end + +        def test_creating_column_with_box_type +          connection.create_table(table_name) do |t| +            t.box :foo_box +          end +           +          assert_column_exists(:foo_box) +          assert_type_correct(:foo_box, :box) +        end + +        def test_creating_column_with_path_type +          connection.create_table(table_name) do |t| +            t.path :foo_path +          end +           +          assert_column_exists(:foo_path) +          assert_type_correct(:foo_path, :path) +        end + +        def test_creating_column_with_polygon_type +          connection.create_table(table_name) do |t| +            t.polygon :foo_polygon +          end +           +          assert_column_exists(:foo_polygon) +          assert_type_correct(:foo_polygon, :polygon) +        end + +        def test_creating_column_with_circle_type +          connection.create_table(table_name) do |t| +            t.circle :foo_circle +          end +           +          assert_column_exists(:foo_circle) +          assert_type_correct(:foo_circle, :circle) +        end +      end + +      private +        def assert_column_exists(column_name) +          columns = connection.columns(table_name) +          assert columns.map(&:name).include?(column_name.to_s) +        end + +        def assert_type_correct(column_name, type) +          columns = connection.columns(table_name) +          column = columns.select{ |c| c.name == column_name.to_s }.first +          assert_equal type.to_s, column.sql_type +        end + +    end +  end +end
\ No newline at end of file diff --git a/activerecord/test/cases/migration/references_foreign_key_test.rb b/activerecord/test/cases/migration/references_foreign_key_test.rb index 1594f99852..84ec657398 100644 --- a/activerecord/test/cases/migration/references_foreign_key_test.rb +++ b/activerecord/test/cases/migration/references_foreign_key_test.rb @@ -32,6 +32,14 @@ module ActiveRecord          assert_equal [], @connection.foreign_keys("testings")        end +      test "foreign keys can be created in one query" do +        assert_queries(1) do +          @connection.create_table :testings do |t| +            t.references :testing_parent, foreign_key: true +          end +        end +      end +        test "options hash can be passed" do          @connection.change_table :testing_parents do |t|            t.integer :other_id @@ -45,6 +53,15 @@ module ActiveRecord          assert_equal "other_id", fk.primary_key        end +      test "to_table option can be passed" do +        @connection.create_table :testings do |t| +          t.references :parent, foreign_key: { to_table: :testing_parents } +        end +        fks = @connection.foreign_keys("testings") +        assert_equal([["testings", "testing_parents", "parent_id"]], +                     fks.map {|fk| [fk.from_table, fk.to_table, fk.column] }) +      end +        test "foreign keys cannot be added to polymorphic relations when creating the table" do          @connection.create_table :testings do |t|            assert_raises(ArgumentError) do diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index b2f209fe97..10f1c7216f 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -75,15 +75,13 @@ class MigrationTest < ActiveRecord::TestCase      ActiveRecord::Migrator.up(migrations_path)      assert_equal 3, ActiveRecord::Migrator.current_version -    assert_equal 3, ActiveRecord::Migrator.last_version      assert_equal false, ActiveRecord::Migrator.needs_migration?      ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid")      assert_equal 0, ActiveRecord::Migrator.current_version -    assert_equal 3, ActiveRecord::Migrator.last_version      assert_equal true, ActiveRecord::Migrator.needs_migration? -    ActiveRecord::SchemaMigration.create!(:version => ActiveRecord::Migrator.last_version) +    ActiveRecord::SchemaMigration.create!(version: 3)      assert_equal true, ActiveRecord::Migrator.needs_migration?    ensure      ActiveRecord::Migrator.migrations_paths = old_path @@ -115,7 +113,7 @@ class MigrationTest < ActiveRecord::TestCase    end    def test_migration_version -    ActiveRecord::Migrator.run(:up, MIGRATIONS_ROOT + "/version_check", 20131219224947) +    assert_nothing_raised { ActiveRecord::Migrator.run(:up, MIGRATIONS_ROOT + "/version_check", 20131219224947) }    end    def test_create_table_with_force_true_does_not_drop_nonexisting_table @@ -132,13 +130,9 @@ class MigrationTest < ActiveRecord::TestCase      Person.connection.drop_table :testings2, if_exists: true    end -  def connection -    ActiveRecord::Base.connection -  end -    def test_migration_instance_has_connection      migration = Class.new(ActiveRecord::Migration).new -    assert_equal connection, migration.connection +    assert_equal ActiveRecord::Base.connection, migration.connection    end    def test_method_missing_delegates_to_connection diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb index d72225f3d3..93cb631a04 100644 --- a/activerecord/test/cases/nested_attributes_test.rb +++ b/activerecord/test/cases/nested_attributes_test.rb @@ -273,10 +273,11 @@ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase    end    def test_should_modify_an_existing_record_if_there_is_a_matching_composite_id -    @ship.stubs(:id).returns('ABC1X') -    @pirate.ship_attributes = { :id => @ship.id, :name => 'Davy Jones Gold Dagger' } +    @ship.stub(:id, 'ABC1X') do +      @pirate.ship_attributes = { :id => @ship.id, :name => 'Davy Jones Gold Dagger' } -    assert_equal 'Davy Jones Gold Dagger', @pirate.ship.name +      assert_equal 'Davy Jones Gold Dagger', @pirate.ship.name +    end    end    def test_should_destroy_an_existing_record_if_there_is_a_matching_id_and_destroy_is_truthy @@ -457,10 +458,11 @@ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase    end    def test_should_modify_an_existing_record_if_there_is_a_matching_composite_id -    @pirate.stubs(:id).returns('ABC1X') -    @ship.pirate_attributes = { :id => @pirate.id, :catchphrase => 'Arr' } +    @pirate.stub(:id, 'ABC1X') do +      @ship.pirate_attributes = { :id => @pirate.id, :catchphrase => 'Arr' } -    assert_equal 'Arr', @ship.pirate.catchphrase +      assert_equal 'Arr', @ship.pirate.catchphrase +    end    end    def test_should_destroy_an_existing_record_if_there_is_a_matching_id_and_destroy_is_truthy @@ -638,17 +640,19 @@ module NestedAttributesOnACollectionAssociationTests    end    def test_should_take_a_hash_with_composite_id_keys_and_assign_the_attributes_to_the_associated_models -    @child_1.stubs(:id).returns('ABC1X') -    @child_2.stubs(:id).returns('ABC2X') - -    @pirate.attributes = { -      association_getter => [ -        { :id => @child_1.id, :name => 'Grace OMalley' }, -        { :id => @child_2.id, :name => 'Privateers Greed' } -      ] -    } +    @child_1.stub(:id, 'ABC1X') do +      @child_2.stub(:id, 'ABC2X') do + +        @pirate.attributes = { +          association_getter => [ +            { :id => @child_1.id, :name => 'Grace OMalley' }, +            { :id => @child_2.id, :name => 'Privateers Greed' } +          ] +        } -    assert_equal ['Grace OMalley', 'Privateers Greed'], [@child_1.name, @child_2.name] +        assert_equal ['Grace OMalley', 'Privateers Greed'], [@child_1.name, @child_2.name] +      end +    end    end    def test_should_raise_RecordNotFound_if_an_id_is_given_but_doesnt_return_a_record @@ -1064,4 +1068,39 @@ class TestHasManyAutosaveAssociationWhichItselfHasAutosaveAssociations < ActiveR      assert_not part.valid?      assert_equal ["Ship name can't be blank"], part.errors.full_messages    end + +  class ProtectedParameters +    def initialize(hash) +      @hash = hash +    end + +    def permitted? +      true +    end + +    def [](key) +      @hash[key] +    end + +    def to_h +      @hash +    end +  end + +  test "strong params style objects can be assigned for singular associations" do +    params = { name: "Stern", ship_attributes: +               ProtectedParameters.new(name: "The Black Rock") } +    part = ShipPart.new(params) + +    assert_equal "Stern", part.name +    assert_equal "The Black Rock", part.ship.name +  end + +  test "strong params style objects can be assigned for collection associations" do +    params = { trinkets_attributes: ProtectedParameters.new("0" => ProtectedParameters.new(name: "Necklace"), "1" => ProtectedParameters.new(name: "Spoon")) } +    part = ShipPart.new(params) + +    assert_equal "Necklace", part.trinkets[0].name +    assert_equal "Spoon", part.trinkets[1].name +  end  end diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb index 42e7507631..31686bde3f 100644 --- a/activerecord/test/cases/persistence_test.rb +++ b/activerecord/test/cases/persistence_test.rb @@ -17,6 +17,7 @@ require 'models/minivan'  require 'models/owner'  require 'models/person'  require 'models/pet' +require 'models/ship'  require 'models/toy'  require 'rexml/document' @@ -119,13 +120,22 @@ class PersistenceTest < ActiveRecord::TestCase      assert_equal 59, accounts(:signals37, :reload).credit_limit    end +  def test_increment_updates_counter_in_db_using_offset +    a1 = accounts(:signals37) +    initial_credit = a1.credit_limit +    a2 = Account.find(accounts(:signals37).id) +    a1.increment!(:credit_limit) +    a2.increment!(:credit_limit) +    assert_equal initial_credit + 2, a1.reload.credit_limit +  end +    def test_destroy_all      conditions = "author_name = 'Mary'"      topics_by_mary = Topic.all.merge!(:where => conditions, :order => 'id').to_a      assert ! topics_by_mary.empty?      assert_difference('Topic.count', -topics_by_mary.size) do -      destroyed = Topic.destroy_all(conditions).sort_by(&:id) +      destroyed = Topic.where(conditions).destroy_all.sort_by(&:id)        assert_equal topics_by_mary, destroyed        assert destroyed.all?(&:frozen?), "destroyed topics should be frozen"      end diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb index 0745a37ee9..5e4ba47988 100644 --- a/activerecord/test/cases/primary_keys_test.rb +++ b/activerecord/test/cases/primary_keys_test.rb @@ -241,6 +241,33 @@ class PrimaryKeyAnyTypeTest < ActiveRecord::TestCase    end  end +class CompositePrimaryKeyTest < ActiveRecord::TestCase +  include SchemaDumpingHelper + +  self.use_transactional_tests = false + +  def setup +    @connection = ActiveRecord::Base.connection +    @connection.create_table(:barcodes, primary_key: ["region", "code"], force: true) do |t| +      t.string :region +      t.integer :code +    end +  end + +  def teardown +    @connection.drop_table(:barcodes, if_exists: true) +  end + +  def test_composite_primary_key +    assert_equal ["region", "code"], @connection.primary_keys("barcodes") +  end + +  def test_collectly_dump_composite_primary_key +    schema = dump_table_schema "barcodes" +    assert_match %r{create_table "barcodes", primary_key: \["region", "code"\]}, schema +  end +end +  if current_adapter?(:MysqlAdapter, :Mysql2Adapter)    class PrimaryKeyWithAnsiQuotesTest < ActiveRecord::TestCase      self.use_transactional_tests = false @@ -300,11 +327,12 @@ if current_adapter?(:PostgreSQLAdapter, :MysqlAdapter, :Mysql2Adapter)      if current_adapter?(:MysqlAdapter, :Mysql2Adapter)        test "primary key column type with options" do -        @connection.create_table(:widgets, id: :primary_key, limit: 8, force: true) +        @connection.create_table(:widgets, id: :primary_key, limit: 8, unsigned: true, force: true)          column = @connection.columns(:widgets).find { |c| c.name == 'id' }          assert column.auto_increment?          assert_equal :integer, column.type          assert_equal 8, column.limit +        assert column.unsigned?        end      end    end diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb index 2f0b5df286..d84653e4c9 100644 --- a/activerecord/test/cases/query_cache_test.rb +++ b/activerecord/test/cases/query_cache_test.rb @@ -262,61 +262,66 @@ class QueryCacheExpiryTest < ActiveRecord::TestCase    end    def test_find -    Task.connection.expects(:clear_query_cache).times(1) +    assert_called(Task.connection, :clear_query_cache) do +      assert !Task.connection.query_cache_enabled +      Task.cache do +        assert Task.connection.query_cache_enabled +        Task.find(1) -    assert !Task.connection.query_cache_enabled -    Task.cache do -      assert Task.connection.query_cache_enabled -      Task.find(1) +        Task.uncached do +          assert !Task.connection.query_cache_enabled +          Task.find(1) +        end -      Task.uncached do -        assert !Task.connection.query_cache_enabled -        Task.find(1) +        assert Task.connection.query_cache_enabled        end - -      assert Task.connection.query_cache_enabled +      assert !Task.connection.query_cache_enabled      end -    assert !Task.connection.query_cache_enabled    end    def test_update -    Task.connection.expects(:clear_query_cache).times(2) -    Task.cache do -      task = Task.find(1) -      task.starting = Time.now.utc -      task.save! +    assert_called(Task.connection, :clear_query_cache, times: 2) do +      Task.cache do +        task = Task.find(1) +        task.starting = Time.now.utc +        task.save! +      end      end    end    def test_destroy -    Task.connection.expects(:clear_query_cache).times(2) -    Task.cache do -      Task.find(1).destroy +    assert_called(Task.connection, :clear_query_cache, times: 2) do +      Task.cache do +        Task.find(1).destroy +      end      end    end    def test_insert -    ActiveRecord::Base.connection.expects(:clear_query_cache).times(2) -    Task.cache do -      Task.create! +    assert_called(ActiveRecord::Base.connection, :clear_query_cache, times: 2) do +      Task.cache do +        Task.create! +      end      end    end    def test_cache_is_expired_by_habtm_update -    ActiveRecord::Base.connection.expects(:clear_query_cache).times(2) -    ActiveRecord::Base.cache do -      c = Category.first -      p = Post.first -      p.categories << c +    assert_called(ActiveRecord::Base.connection, :clear_query_cache, times: 2) do +      ActiveRecord::Base.cache do +        c = Category.first +        p = Post.first +        p.categories << c +      end      end    end    def test_cache_is_expired_by_habtm_delete -    ActiveRecord::Base.connection.expects(:clear_query_cache).times(2) -    ActiveRecord::Base.cache do -      p = Post.find(1) -      assert p.categories.any? -      p.categories.delete_all +    assert_called(ActiveRecord::Base.connection, :clear_query_cache, times: 2) do +      ActiveRecord::Base.cache do +        p = Post.find(1) +        assert p.categories.any? +        p.categories.delete_all +      end      end    end  end diff --git a/activerecord/test/cases/readonly_test.rb b/activerecord/test/cases/readonly_test.rb index 1c919f0b57..5f6eb41240 100644 --- a/activerecord/test/cases/readonly_test.rb +++ b/activerecord/test/cases/readonly_test.rb @@ -7,6 +7,7 @@ require 'models/computer'  require 'models/project'  require 'models/reader'  require 'models/person' +require 'models/ship'  class ReadOnlyTest < ActiveRecord::TestCase    fixtures :authors, :posts, :comments, :developers, :projects, :developers_projects, :people, :readers diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb index 7b47c80331..9c04a41e69 100644 --- a/activerecord/test/cases/reflection_test.rb +++ b/activerecord/test/cases/reflection_test.rb @@ -393,12 +393,14 @@ class ReflectionTest < ActiveRecord::TestCase      product = Struct.new(:table_name, :pluralize_table_names).new('products', true)      reflection = ActiveRecord::Reflection.create(:has_many, :categories, nil, {}, product) -    reflection.stubs(:klass).returns(category) -    assert_equal 'categories_products', reflection.join_table +    reflection.stub(:klass, category) do +      assert_equal 'categories_products', reflection.join_table +    end      reflection = ActiveRecord::Reflection.create(:has_many, :products, nil, {}, category) -    reflection.stubs(:klass).returns(product) -    assert_equal 'categories_products', reflection.join_table +    reflection.stub(:klass, product) do +      assert_equal 'categories_products', reflection.join_table +    end    end    def test_join_table_with_common_prefix @@ -406,12 +408,14 @@ class ReflectionTest < ActiveRecord::TestCase      product = Struct.new(:table_name, :pluralize_table_names).new('catalog_products', true)      reflection = ActiveRecord::Reflection.create(:has_many, :categories, nil, {}, product) -    reflection.stubs(:klass).returns(category) -    assert_equal 'catalog_categories_products', reflection.join_table +    reflection.stub(:klass, category) do +      assert_equal 'catalog_categories_products', reflection.join_table +    end      reflection = ActiveRecord::Reflection.create(:has_many, :products, nil, {}, category) -    reflection.stubs(:klass).returns(product) -    assert_equal 'catalog_categories_products', reflection.join_table +    reflection.stub(:klass, product) do +      assert_equal 'catalog_categories_products', reflection.join_table +    end    end    def test_join_table_with_different_prefix @@ -419,12 +423,14 @@ class ReflectionTest < ActiveRecord::TestCase      page = Struct.new(:table_name, :pluralize_table_names).new('content_pages', true)      reflection = ActiveRecord::Reflection.create(:has_many, :categories, nil, {}, page) -    reflection.stubs(:klass).returns(category) -    assert_equal 'catalog_categories_content_pages', reflection.join_table +    reflection.stub(:klass, category) do +      assert_equal 'catalog_categories_content_pages', reflection.join_table +    end      reflection = ActiveRecord::Reflection.create(:has_many, :pages, nil, {}, category) -    reflection.stubs(:klass).returns(page) -    assert_equal 'catalog_categories_content_pages', reflection.join_table +    reflection.stub(:klass, page) do +      assert_equal 'catalog_categories_content_pages', reflection.join_table +    end    end    def test_join_table_can_be_overridden @@ -432,12 +438,14 @@ class ReflectionTest < ActiveRecord::TestCase      product = Struct.new(:table_name, :pluralize_table_names).new('products', true)      reflection = ActiveRecord::Reflection.create(:has_many, :categories, nil, { :join_table => 'product_categories' }, product) -    reflection.stubs(:klass).returns(category) -    assert_equal 'product_categories', reflection.join_table +    reflection.stub(:klass, category) do +      assert_equal 'product_categories', reflection.join_table +    end      reflection = ActiveRecord::Reflection.create(:has_many, :products, nil, { :join_table => 'product_categories' }, category) -    reflection.stubs(:klass).returns(product) -    assert_equal 'product_categories', reflection.join_table +    reflection.stub(:klass, product) do +      assert_equal 'product_categories', reflection.join_table +    end    end    def test_includes_accepts_symbols diff --git a/activerecord/test/cases/relation/mutation_test.rb b/activerecord/test/cases/relation/mutation_test.rb index ba4d9d2503..88d2dd55ab 100644 --- a/activerecord/test/cases/relation/mutation_test.rb +++ b/activerecord/test/cases/relation/mutation_test.rb @@ -55,9 +55,10 @@ module ActiveRecord      test '#order! on non-string does not attempt regexp match for references' do        obj = Object.new -      obj.expects(:=~).never -      assert relation.order!(obj) -      assert_equal [obj], relation.order_values +      assert_not_called(obj, :=~) do +        assert relation.order!(obj) +        assert_equal [obj], relation.order_values +      end      end      test '#references!' do diff --git a/activerecord/test/cases/relation/where_test.rb b/activerecord/test/cases/relation/where_test.rb index 6af31017d6..bc6378b90e 100644 --- a/activerecord/test/cases/relation/where_test.rb +++ b/activerecord/test/cases/relation/where_test.rb @@ -276,5 +276,35 @@ module ActiveRecord        assert_equal essays(:david_modest_proposal), essay      end + +    def test_where_with_strong_parameters +      protected_params = Class.new do +        attr_reader :permitted +        alias :permitted? :permitted + +        def initialize(parameters) +          @parameters = parameters +          @permitted = false +        end + +        def to_h +          @parameters +        end + +        def permit! +          @permitted = true +          self +        end +      end + +      author = authors(:david) +      params = protected_params.new(name: author.name) +      assert_raises(ActiveModel::ForbiddenAttributesError) { Author.where(params) } +      assert_equal author, Author.where(params.permit!).first +    end + +    def test_where_with_unsupported_arguments +      assert_raises(ArgumentError) { Author.where(42) } +    end    end  end diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb index 37d3965022..675149556f 100644 --- a/activerecord/test/cases/relation_test.rb +++ b/activerecord/test/cases/relation_test.rb @@ -57,9 +57,6 @@ module ActiveRecord      def test_empty_where_values_hash        relation = Relation.new(FakeKlass, :b, nil)        assert_equal({}, relation.where_values_hash) - -      relation.where! :hello -      assert_equal({}, relation.where_values_hash)      end      def test_has_values @@ -153,10 +150,10 @@ module ActiveRecord      end      test 'merging a hash into a relation' do -      relation = Relation.new(FakeKlass, :b, nil) -      relation = relation.merge where: :lol, readonly: true +      relation = Relation.new(Post, Post.arel_table, Post.predicate_builder) +      relation = relation.merge where: {name: :lol}, readonly: true -      assert_equal Relation::WhereClause.new([:lol], []), relation.where_clause +      assert_equal({"name"=>:lol}, relation.where_clause.to_h)        assert_equal true, relation.readonly_value      end @@ -185,7 +182,7 @@ module ActiveRecord      end      test '#values returns a dup of the values' do -      relation = Relation.new(FakeKlass, :b, nil).where! :foo +      relation = Relation.new(Post, Post.arel_table, Post.predicate_builder).where!(name: :foo)        values   = relation.values        values[:where] = nil @@ -243,18 +240,23 @@ module ActiveRecord      end      def test_select_quotes_when_using_from_clause -      if sqlite3_version_includes_quoting_bug? -        skip <<-ERROR.squish -          You are using an outdated version of SQLite3 which has a bug in -          quoted column names. Please update SQLite3 and rebuild the sqlite3 -          ruby gem -        ERROR -      end +      skip_if_sqlite3_version_includes_quoting_bug        quoted_join = ActiveRecord::Base.connection.quote_table_name("join")        selected = Post.select(:join).from(Post.select("id as #{quoted_join}")).map(&:join)        assert_equal Post.pluck(:id), selected      end +    def test_selecting_aliased_attribute_quotes_column_name_when_from_is_used +      skip_if_sqlite3_version_includes_quoting_bug +      klass = Class.new(ActiveRecord::Base) do +        self.table_name = :test_with_keyword_column_name +        alias_attribute :description, :desc +      end +      klass.create!(description: "foo") + +      assert_equal ["foo"], klass.select(:description).from(klass.all).map(&:desc) +    end +      def test_relation_merging_with_merged_joins_as_strings        join_string = "LEFT OUTER JOIN #{Rating.quoted_table_name} ON #{SpecialComment.quoted_table_name}.id = #{Rating.quoted_table_name}.comment_id"        special_comments_with_ratings = SpecialComment.joins join_string @@ -292,6 +294,16 @@ module ActiveRecord      private +    def skip_if_sqlite3_version_includes_quoting_bug +      if sqlite3_version_includes_quoting_bug? +        skip <<-ERROR.squish +          You are using an outdated version of SQLite3 which has a bug in +          quoted column names. Please update SQLite3 and rebuild the sqlite3 +          ruby gem +        ERROR +      end +    end +      def sqlite3_version_includes_quoting_bug?        if current_adapter?(:SQLite3Adapter)          selected_quoted_column_names = ActiveRecord::Base.connection.exec_query( diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index acbf85d398..7521f0573a 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -647,6 +647,25 @@ class RelationTest < ActiveRecord::TestCase      end    end +  def test_preloading_with_associations_default_scopes_and_merges +    post = Post.create! title: 'Uhuu', body: 'body' +    reader = Reader.create! post_id: post.id, person_id: 1 + +    post_rel = PostWithPreloadDefaultScope.preload(:readers).joins(:readers).where(title: 'Uhuu') +    result_post = PostWithPreloadDefaultScope.all.merge(post_rel).to_a.first + +    assert_no_queries do +      assert_equal [reader], result_post.readers.to_a +    end + +    post_rel = PostWithIncludesDefaultScope.includes(:readers).where(title: 'Uhuu') +    result_post = PostWithIncludesDefaultScope.all.merge(post_rel).to_a.first + +    assert_no_queries do +      assert_equal [reader], result_post.readers.to_a +    end +  end +    def test_loading_with_one_association      posts = Post.preload(:comments)      post = posts.find { |p| p.id == 1 } @@ -912,6 +931,12 @@ class RelationTest < ActiveRecord::TestCase      assert davids.loaded?    end +  def test_destroy_all_with_conditions_is_deprecated +    assert_deprecated do +      assert_difference('Author.count', -1) { Author.destroy_all(name: 'David') } +    end +  end +    def test_delete_all      davids = Author.where(:name => 'David') @@ -919,6 +944,12 @@ class RelationTest < ActiveRecord::TestCase      assert ! davids.loaded?    end +  def test_delete_all_with_conditions_is_deprecated +    assert_deprecated do +      assert_difference('Author.count', -1) { Author.delete_all(name: 'David') } +    end +  end +    def test_delete_all_loaded      davids = Author.where(:name => 'David') @@ -1510,6 +1541,13 @@ class RelationTest < ActiveRecord::TestCase      assert_equal 'David', topic2.reload.author_name    end +  def test_update_on_relation_passing_active_record_object_is_deprecated +    topic = Topic.create!(title: 'Foo', author_name: nil) +    assert_deprecated(/update/) do +      Topic.where(id: topic.id).update(topic, title: 'Bar') +    end +  end +    def test_distinct      tag1 = Tag.create(:name => 'Foo')      tag2 = Tag.create(:name => 'Foo') diff --git a/activerecord/test/cases/sanitize_test.rb b/activerecord/test/cases/sanitize_test.rb index 262e0abc22..14e392ac30 100644 --- a/activerecord/test/cases/sanitize_test.rb +++ b/activerecord/test/cases/sanitize_test.rb @@ -9,11 +9,11 @@ class SanitizeTest < ActiveRecord::TestCase    def test_sanitize_sql_array_handles_string_interpolation      quoted_bambi = ActiveRecord::Base.connection.quote_string("Bambi") -    assert_equal "name=#{quoted_bambi}", Binary.send(:sanitize_sql_array, ["name=%s", "Bambi"]) -    assert_equal "name=#{quoted_bambi}", Binary.send(:sanitize_sql_array, ["name=%s", "Bambi".mb_chars]) +    assert_equal "name='#{quoted_bambi}'", Binary.send(:sanitize_sql_array, ["name='%s'", "Bambi"]) +    assert_equal "name='#{quoted_bambi}'", Binary.send(:sanitize_sql_array, ["name='%s'", "Bambi".mb_chars])      quoted_bambi_and_thumper = ActiveRecord::Base.connection.quote_string("Bambi\nand\nThumper") -    assert_equal "name=#{quoted_bambi_and_thumper}",Binary.send(:sanitize_sql_array, ["name=%s", "Bambi\nand\nThumper"]) -    assert_equal "name=#{quoted_bambi_and_thumper}",Binary.send(:sanitize_sql_array, ["name=%s", "Bambi\nand\nThumper".mb_chars]) +    assert_equal "name='#{quoted_bambi_and_thumper}'",Binary.send(:sanitize_sql_array, ["name='%s'", "Bambi\nand\nThumper"]) +    assert_equal "name='#{quoted_bambi_and_thumper}'",Binary.send(:sanitize_sql_array, ["name='%s'", "Bambi\nand\nThumper".mb_chars])    end    def test_sanitize_sql_array_handles_bind_variables diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb index feb1c29656..2a2c2bc8d0 100644 --- a/activerecord/test/cases/schema_dumper_test.rb +++ b/activerecord/test/cases/schema_dumper_test.rb @@ -215,7 +215,7 @@ class SchemaDumperTest < ActiveRecord::TestCase      def test_schema_dump_includes_length_for_mysql_blob_and_text_fields        output = standard_dump -      assert_match %r{t\.binary\s+"tiny_blob",\s+limit: 255$}, output +      assert_match %r{t\.blob\s+"tiny_blob",\s+limit: 255$}, output        assert_match %r{t\.binary\s+"normal_blob",\s+limit: 65535$}, output        assert_match %r{t\.binary\s+"medium_blob",\s+limit: 16777215$}, output        assert_match %r{t\.binary\s+"long_blob",\s+limit: 4294967295$}, output diff --git a/activerecord/test/cases/scoping/default_scoping_test.rb b/activerecord/test/cases/scoping/default_scoping_test.rb index 0dbc60940e..86316ab476 100644 --- a/activerecord/test/cases/scoping/default_scoping_test.rb +++ b/activerecord/test/cases/scoping/default_scoping_test.rb @@ -3,6 +3,7 @@ require 'models/post'  require 'models/comment'  require 'models/developer'  require 'models/computer' +require 'models/vehicle'  class DefaultScopingTest < ActiveRecord::TestCase    fixtures :developers, :posts, :comments @@ -453,4 +454,9 @@ class DefaultScopingTest < ActiveRecord::TestCase      assert_equal 1, scope.where_clause.ast.children.length      assert_equal Developer.where(name: "David"), scope    end + +  def test_with_abstract_class_where_clause_should_not_be_duplicated +    scope = Bus.all +    assert_equal scope.where_clause.ast.children.length, 1 +  end  end diff --git a/activerecord/test/cases/scoping/named_scoping_test.rb b/activerecord/test/cases/scoping/named_scoping_test.rb index e4cc533517..7a8eaeccb7 100644 --- a/activerecord/test/cases/scoping/named_scoping_test.rb +++ b/activerecord/test/cases/scoping/named_scoping_test.rb @@ -188,8 +188,9 @@ class NamedScopingTest < ActiveRecord::TestCase    def test_any_should_call_proxy_found_if_using_a_block      topics = Topic.base      assert_queries(1) do -      topics.expects(:empty?).never -      topics.any? { true } +      assert_not_called(topics, :empty?) do +        topics.any? { true } +      end      end    end @@ -217,8 +218,9 @@ class NamedScopingTest < ActiveRecord::TestCase    def test_many_should_call_proxy_found_if_using_a_block      topics = Topic.base      assert_queries(1) do -      topics.expects(:size).never -      topics.many? { true } +      assert_not_called(topics, :size) do +        topics.many? { true } +      end      end    end diff --git a/activerecord/test/cases/serialization_test.rb b/activerecord/test/cases/serialization_test.rb index 35b13ea247..14b80f4df4 100644 --- a/activerecord/test/cases/serialization_test.rb +++ b/activerecord/test/cases/serialization_test.rb @@ -8,7 +8,7 @@ require 'models/post'  class SerializationTest < ActiveRecord::TestCase    fixtures :books -  FORMATS = [ :xml, :json ] +  FORMATS = [ :json ]    def setup      @contact_attributes = { diff --git a/activerecord/test/cases/tasks/database_tasks_test.rb b/activerecord/test/cases/tasks/database_tasks_test.rb index 38164b2228..c8f4179313 100644 --- a/activerecord/test/cases/tasks/database_tasks_test.rb +++ b/activerecord/test/cases/tasks/database_tasks_test.rb @@ -277,12 +277,14 @@ module ActiveRecord      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(ActiveRecord::Migrator.migrations_paths, 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    end diff --git a/activerecord/test/cases/tasks/mysql_rake_test.rb b/activerecord/test/cases/tasks/mysql_rake_test.rb index d0deb4c273..a93fa57257 100644 --- a/activerecord/test/cases/tasks/mysql_rake_test.rb +++ b/activerecord/test/cases/tasks/mysql_rake_test.rb @@ -270,15 +270,16 @@ module ActiveRecord        ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, filename)      end -    def test_warn_when_external_structure_dump_fails +    def test_warn_when_external_structure_dump_command_execution_fails        filename = "awesome-file.sql" -      Kernel.expects(:system).with("mysqldump", "--result-file", filename, "--no-data", "--routines", "test-db").returns(false) +      Kernel.expects(:system) +        .with("mysqldump", "--result-file", filename, "--no-data", "--routines", "test-db") +        .returns(false) -      warnings = capture(:stderr) do +      e = assert_raise(RuntimeError) {          ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, filename) -      end - -      assert_match(/Could not dump the database structure/, warnings) +      } +      assert_match(/^failed to execute: `mysqldump`$/, e.message)      end      def test_structure_dump_with_port_number @@ -311,6 +312,7 @@ module ActiveRecord      def test_structure_load        filename = "awesome-file.sql"        Kernel.expects(:system).with('mysql', '--execute', %{SET FOREIGN_KEY_CHECKS = 0; SOURCE #{filename}; SET FOREIGN_KEY_CHECKS = 1}, "--database", "test-db") +        .returns(true)        ActiveRecord::Tasks::DatabaseTasks.structure_load(@configuration, filename)      end diff --git a/activerecord/test/cases/tasks/postgresql_rake_test.rb b/activerecord/test/cases/tasks/postgresql_rake_test.rb index 084302cde5..c3fd0b2383 100644 --- a/activerecord/test/cases/tasks/postgresql_rake_test.rb +++ b/activerecord/test/cases/tasks/postgresql_rake_test.rb @@ -204,7 +204,7 @@ module ActiveRecord      end      def test_structure_dump -      Kernel.expects(:system).with("pg_dump -i -s -x -O -f #{@filename}  my-app-db").returns(true) +      Kernel.expects(:system).with('pg_dump', '-s', '-x', '-O', '-f', @filename, 'my-app-db').returns(true)        ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, @filename)      end @@ -212,7 +212,7 @@ module ActiveRecord      def test_structure_dump_with_schema_search_path        @configuration['schema_search_path'] = 'foo,bar' -      Kernel.expects(:system).with("pg_dump -i -s -x -O -f #{@filename} --schema=foo --schema=bar my-app-db").returns(true) +      Kernel.expects(:system).with('pg_dump', '-s', '-x', '-O', '-f', @filename, '--schema=foo --schema=bar', 'my-app-db').returns(true)        ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, @filename)      end @@ -220,7 +220,7 @@ module ActiveRecord      def test_structure_dump_with_schema_search_path_and_dump_schemas_all        @configuration['schema_search_path'] = 'foo,bar' -      Kernel.expects(:system).with("pg_dump -i -s -x -O -f #{@filename}  my-app-db").returns(true) +      Kernel.expects(:system).with("pg_dump", '-s', '-x', '-O', '-f', @filename,  'my-app-db').returns(true)        with_dump_schemas(:all) do          ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, @filename) @@ -228,7 +228,7 @@ module ActiveRecord      end      def test_structure_dump_with_dump_schemas_string -      Kernel.expects(:system).with("pg_dump -i -s -x -O -f #{@filename} --schema=foo --schema=bar my-app-db").returns(true) +      Kernel.expects(:system).with("pg_dump", '-s', '-x', '-O', '-f', @filename, '--schema=foo --schema=bar', "my-app-db").returns(true)        with_dump_schemas('foo,bar') do          ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, @filename) @@ -261,14 +261,14 @@ module ActiveRecord      def test_structure_load        filename = "awesome-file.sql" -      Kernel.expects(:system).with("psql -X -q -f #{filename} my-app-db") +      Kernel.expects(:system).with('psql', '-q', '-f', filename, @configuration['database']).returns(true)        ActiveRecord::Tasks::DatabaseTasks.structure_load(@configuration, filename)      end      def test_structure_load_accepts_path_with_spaces        filename = "awesome file.sql" -      Kernel.expects(:system).with("psql -X -q -f awesome\\ file.sql my-app-db") +      Kernel.expects(:system).with('psql', '-q', '-f', filename, @configuration['database']).returns(true)        ActiveRecord::Tasks::DatabaseTasks.structure_load(@configuration, filename)      end diff --git a/activerecord/test/cases/test_case.rb b/activerecord/test/cases/test_case.rb index 7761ea5612..6020a3c832 100644 --- a/activerecord/test/cases/test_case.rb +++ b/activerecord/test/cases/test_case.rb @@ -103,7 +103,7 @@ module ActiveRecord      # ignored SQL, or better yet, use a different notification for the queries      # instead examining the SQL content.      oracle_ignored     = [/^select .*nextval/i, /^SAVEPOINT/, /^ROLLBACK TO/, /^\s*select .* from all_triggers/im, /^\s*select .* from all_constraints/im, /^\s*select .* from all_tab_cols/im] -    mysql_ignored      = [/^SHOW TABLES/i, /^SHOW FULL FIELDS/, /^SHOW CREATE TABLE /i, /^SHOW VARIABLES /] +    mysql_ignored      = [/^SHOW FULL TABLES/i, /^SHOW FULL FIELDS/, /^SHOW CREATE TABLE /i, /^SHOW VARIABLES /, /^SELECT DATABASE\(\) as db$/, /^\s*SELECT (?:column_name|table_name)\b.*\bFROM information_schema\.(?:key_column_usage|tables)\b/im]      postgresql_ignored = [/^\s*select\b.*\bfrom\b.*pg_namespace\b/im, /^\s*select tablename\b.*from pg_tables\b/im, /^\s*select\b.*\battname\b.*\bfrom\b.*\bpg_attribute\b/im, /^SHOW search_path/i]      sqlite3_ignored =    [/^\s*SELECT name\b.*\bFROM sqlite_master/im, /^\s*SELECT sql\b.*\bFROM sqlite_master/im] diff --git a/activerecord/test/cases/test_fixtures_test.rb b/activerecord/test/cases/test_fixtures_test.rb index 3f4baf8378..1970fe82d0 100644 --- a/activerecord/test/cases/test_fixtures_test.rb +++ b/activerecord/test/cases/test_fixtures_test.rb @@ -28,7 +28,7 @@ class TestFixturesTest < ActiveRecord::TestCase      assert_equal true, @klass.use_transactional_tests    end -  def test_use_transactional_tests_can_be_overriden +  def test_use_transactional_tests_can_be_overridden      @klass.use_transactional_tests = "foobar"      assert_equal "foobar", @klass.use_transactional_tests diff --git a/activerecord/test/cases/timestamp_test.rb b/activerecord/test/cases/timestamp_test.rb index 5dab32995c..970f6bcf4a 100644 --- a/activerecord/test/cases/timestamp_test.rb +++ b/activerecord/test/cases/timestamp_test.rb @@ -84,7 +84,9 @@ class TimestampTest < ActiveRecord::TestCase    def test_touching_an_attribute_updates_timestamp      previously_created_at = @developer.created_at -    @developer.touch(:created_at) +    travel(1.second) do +      @developer.touch(:created_at) +    end      assert !@developer.created_at_changed? , 'created_at should not be changed'      assert !@developer.changed?, 'record should not be changed' @@ -199,8 +201,10 @@ class TimestampTest < ActiveRecord::TestCase      owner = pet.owner      previously_owner_updated_at = owner.updated_at -    pet.name = "Fluffy the Third" -    pet.save +    travel(1.second) do +      pet.name = "Fluffy the Third" +      pet.save +    end      assert_not_equal previously_owner_updated_at, pet.owner.updated_at    end @@ -210,7 +214,9 @@ class TimestampTest < ActiveRecord::TestCase      owner = pet.owner      previously_owner_updated_at = owner.updated_at -    pet.destroy +    travel(1.second) do +      pet.destroy +    end      assert_not_equal previously_owner_updated_at, pet.owner.updated_at    end @@ -254,8 +260,10 @@ class TimestampTest < ActiveRecord::TestCase      owner.update_columns(happy_at: 3.days.ago)      previously_owner_updated_at = owner.updated_at -    pet.name = "I'm a parrot" -    pet.save +    travel(1.second) do +      pet.name = "I'm a parrot" +      pet.save +    end      assert_not_equal previously_owner_updated_at, pet.owner.updated_at    end diff --git a/activerecord/test/cases/touch_later_test.rb b/activerecord/test/cases/touch_later_test.rb index 49ada22529..7058f4fbe2 100644 --- a/activerecord/test/cases/touch_later_test.rb +++ b/activerecord/test/cases/touch_later_test.rb @@ -11,7 +11,7 @@ class TouchLaterTest < ActiveRecord::TestCase    def test_touch_laster_raise_if_non_persisted      invoice = Invoice.new      Invoice.transaction do -      refute invoice.persisted? +      assert_not invoice.persisted?        assert_raises(ActiveRecord::ActiveRecordError) do          invoice.touch_later        end @@ -21,7 +21,7 @@ class TouchLaterTest < ActiveRecord::TestCase    def test_touch_later_dont_set_dirty_attributes      invoice = Invoice.create!      invoice.touch_later -    refute invoice.changed? +    assert_not invoice.changed?    end    def test_touch_later_update_the_attributes @@ -72,7 +72,7 @@ class TouchLaterTest < ActiveRecord::TestCase    end    def test_touch_touches_immediately_with_a_custom_time -    time = Time.now.utc - 25.days +    time = (Time.now.utc - 25.days).change(nsec: 0)      topic = Topic.create!(updated_at: time, created_at: time)      assert_equal time, topic.updated_at      assert_equal time, topic.created_at diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb index 2468a91969..ec5bdfd725 100644 --- a/activerecord/test/cases/transactions_test.rb +++ b/activerecord/test/cases/transactions_test.rb @@ -175,13 +175,20 @@ class TransactionTest < ActiveRecord::TestCase      assert topic.new_record?, "#{topic.inspect} should be new record"    end +  def test_transaction_state_is_cleared_when_record_is_persisted +    author = Author.create! name: 'foo' +    author.name = nil +    assert_not author.save +    assert_not author.new_record? +  end +    def test_update_should_rollback_on_failure      author = Author.find(1)      posts_count = author.posts.size      assert posts_count > 0      status = author.update(name: nil, post_ids: [])      assert !status -    assert_equal posts_count, author.posts(true).size +    assert_equal posts_count, author.posts.reload.size    end    def test_update_should_rollback_on_failure! @@ -191,7 +198,7 @@ class TransactionTest < ActiveRecord::TestCase      assert_raise(ActiveRecord::RecordInvalid) do        author.update!(name: nil, post_ids: [])      end -    assert_equal posts_count, author.posts(true).size +    assert_equal posts_count, author.posts.reload.size    end    def test_cancellation_from_returning_false_in_before_filter @@ -480,13 +487,17 @@ class TransactionTest < ActiveRecord::TestCase    end    def test_rollback_when_commit_raises -    Topic.connection.expects(:begin_db_transaction) -    Topic.connection.expects(:commit_db_transaction).raises('OH NOES') -    Topic.connection.expects(:rollback_db_transaction) +    assert_called(Topic.connection, :begin_db_transaction) do +      Topic.connection.stub(:commit_db_transaction, ->{ raise('OH NOES') }) do +        assert_called(Topic.connection, :rollback_db_transaction) do -    assert_raise RuntimeError do -      Topic.transaction do -        # do nothing +          e = assert_raise RuntimeError do +            Topic.transaction do +              # do nothing +            end +          end +          assert_equal 'OH NOES', e.message +        end        end      end    end diff --git a/activerecord/test/cases/type/date_time_test.rb b/activerecord/test/cases/type/date_time_test.rb new file mode 100644 index 0000000000..bc4900e1c2 --- /dev/null +++ b/activerecord/test/cases/type/date_time_test.rb @@ -0,0 +1,14 @@ +require "cases/helper" +require "models/task" + +module ActiveRecord +  module Type +    class IntegerTest < ActiveRecord::TestCase +      def test_datetime_seconds_precision_applied_to_timestamp +        skip "This test is invalid if subsecond precision isn't supported" unless subsecond_precision_supported? +        p = Task.create!(starting: ::Time.now) +        assert_equal p.starting.usec, p.reload.starting.usec +      end +    end +  end +end diff --git a/activerecord/test/cases/type/decimal_test.rb b/activerecord/test/cases/type/decimal_test.rb deleted file mode 100644 index fe49d0e79a..0000000000 --- a/activerecord/test/cases/type/decimal_test.rb +++ /dev/null @@ -1,51 +0,0 @@ -require "cases/helper" - -module ActiveRecord -  module Type -    class DecimalTest < ActiveRecord::TestCase -      def test_type_cast_decimal -        type = Decimal.new -        assert_equal BigDecimal.new("0"), type.cast(BigDecimal.new("0")) -        assert_equal BigDecimal.new("123"), type.cast(123.0) -        assert_equal BigDecimal.new("1"), type.cast(:"1") -      end - -      def test_type_cast_decimal_from_float_with_large_precision -        type = Decimal.new(precision: ::Float::DIG + 2) -        assert_equal BigDecimal.new("123.0"), type.cast(123.0) -      end - -      def test_type_cast_from_float_with_unspecified_precision -        type = Decimal.new -        assert_equal 22.68.to_d, type.cast(22.68) -      end - -      def test_type_cast_decimal_from_rational_with_precision -        type = Decimal.new(precision: 2) -        assert_equal BigDecimal("0.33"), type.cast(Rational(1, 3)) -      end - -      def test_type_cast_decimal_from_rational_without_precision_defaults_to_18_36 -        type = Decimal.new -        assert_equal BigDecimal("0.333333333333333333E0"), type.cast(Rational(1, 3)) -      end - -      def test_type_cast_decimal_from_object_responding_to_d -        value = Object.new -        def value.to_d -          BigDecimal.new("1") -        end -        type = Decimal.new -        assert_equal BigDecimal("1"), type.cast(value) -      end - -      def test_changed? -        type = Decimal.new - -        assert type.changed?(5.0, 5.0, '5.0wibble') -        assert_not type.changed?(5.0, 5.0, '5.0') -        assert_not type.changed?(-5.0, -5.0, '-5.0') -      end -    end -  end -end diff --git a/activerecord/test/cases/type/integer_test.rb b/activerecord/test/cases/type/integer_test.rb index 0dcdbd0667..c0932d5357 100644 --- a/activerecord/test/cases/type/integer_test.rb +++ b/activerecord/test/cases/type/integer_test.rb @@ -4,112 +4,12 @@ require "models/company"  module ActiveRecord    module Type      class IntegerTest < ActiveRecord::TestCase -      test "simple values" do -        type = Type::Integer.new -        assert_equal 1, type.cast(1) -        assert_equal 1, type.cast('1') -        assert_equal 1, type.cast('1ignore') -        assert_equal 0, type.cast('bad1') -        assert_equal 0, type.cast('bad') -        assert_equal 1, type.cast(1.7) -        assert_equal 0, type.cast(false) -        assert_equal 1, type.cast(true) -        assert_nil type.cast(nil) -      end - -      test "random objects cast to nil" do -        type = Type::Integer.new -        assert_nil type.cast([1,2]) -        assert_nil type.cast({1 => 2}) -        assert_nil type.cast(1..2) -      end -        test "casting ActiveRecord models" do          type = Type::Integer.new          firm = Firm.create(:name => 'Apple')          assert_nil type.cast(firm)        end -      test "casting objects without to_i" do -        type = Type::Integer.new -        assert_nil type.cast(::Object.new) -      end - -      test "casting nan and infinity" do -        type = Type::Integer.new -        assert_nil type.cast(::Float::NAN) -        assert_nil type.cast(1.0/0.0) -      end - -      test "casting booleans for database" do -        type = Type::Integer.new -        assert_equal 1, type.serialize(true) -        assert_equal 0, type.serialize(false) -      end - -      test "changed?" do -        type = Type::Integer.new - -        assert type.changed?(5, 5, '5wibble') -        assert_not type.changed?(5, 5, '5') -        assert_not type.changed?(5, 5, '5.0') -        assert_not type.changed?(-5, -5, '-5') -        assert_not type.changed?(-5, -5, '-5.0') -        assert_not type.changed?(nil, nil, nil) -      end - -      test "values below int min value are out of range" do -        assert_raises(::RangeError) do -          Integer.new.serialize(-2147483649) -        end -      end - -      test "values above int max value are out of range" do -        assert_raises(::RangeError) do -          Integer.new.serialize(2147483648) -        end -      end - -      test "very small numbers are out of range" do -        assert_raises(::RangeError) do -          Integer.new.serialize(-9999999999999999999999999999999) -        end -      end - -      test "very large numbers are out of range" do -        assert_raises(::RangeError) do -          Integer.new.serialize(9999999999999999999999999999999) -        end -      end - -      test "normal numbers are in range" do -        type = Integer.new -        assert_equal(0, type.serialize(0)) -        assert_equal(-1, type.serialize(-1)) -        assert_equal(1, type.serialize(1)) -      end - -      test "int max value is in range" do -        assert_equal(2147483647, Integer.new.serialize(2147483647)) -      end - -      test "int min value is in range" do -        assert_equal(-2147483648, Integer.new.serialize(-2147483648)) -      end - -      test "columns with a larger limit have larger ranges" do -        type = Integer.new(limit: 8) - -        assert_equal(9223372036854775807, type.serialize(9223372036854775807)) -        assert_equal(-9223372036854775808, type.serialize(-9223372036854775808)) -        assert_raises(::RangeError) do -          type.serialize(-9999999999999999999999999999999) -        end -        assert_raises(::RangeError) do -          type.serialize(9999999999999999999999999999999) -        end -      end -        test "values which are out of range can be re-assigned" do          klass = Class.new(ActiveRecord::Base) do            self.table_name = 'posts' diff --git a/activerecord/test/cases/type/string_test.rb b/activerecord/test/cases/type/string_test.rb index 56e9bf434d..6fe6d46711 100644 --- a/activerecord/test/cases/type/string_test.rb +++ b/activerecord/test/cases/type/string_test.rb @@ -2,20 +2,6 @@ require 'cases/helper'  module ActiveRecord    class StringTypeTest < ActiveRecord::TestCase -    test "type casting" do -      type = Type::String.new -      assert_equal "t", type.cast(true) -      assert_equal "f", type.cast(false) -      assert_equal "123", type.cast(123) -    end - -    test "values are duped coming out" do -      s = "foo" -      type = Type::String.new -      assert_not_same s, type.cast(s) -      assert_not_same s, type.deserialize(s) -    end -      test "string mutations are detected" do        klass = Class.new(Base)        klass.table_name = 'authors' diff --git a/activerecord/test/cases/type/unsigned_integer_test.rb b/activerecord/test/cases/type/unsigned_integer_test.rb deleted file mode 100644 index f2c910eade..0000000000 --- a/activerecord/test/cases/type/unsigned_integer_test.rb +++ /dev/null @@ -1,17 +0,0 @@ -require "cases/helper" - -module ActiveRecord -  module Type -    class UnsignedIntegerTest < ActiveRecord::TestCase -      test "unsigned int max value is in range" do -        assert_equal(4294967295, UnsignedInteger.new.serialize(4294967295)) -      end - -      test "minus value is out of range" do -        assert_raises(::RangeError) do -          UnsignedInteger.new.serialize(-1) -        end -      end -    end -  end -end diff --git a/activerecord/test/cases/types_test.rb b/activerecord/test/cases/types_test.rb index 9b1859c2ce..81fcf04a27 100644 --- a/activerecord/test/cases/types_test.rb +++ b/activerecord/test/cases/types_test.rb @@ -3,111 +3,6 @@ require "cases/helper"  module ActiveRecord    module ConnectionAdapters      class TypesTest < ActiveRecord::TestCase -      def test_type_cast_boolean -        type = Type::Boolean.new -        assert type.cast('').nil? -        assert type.cast(nil).nil? - -        assert type.cast(true) -        assert type.cast(1) -        assert type.cast('1') -        assert type.cast('t') -        assert type.cast('T') -        assert type.cast('true') -        assert type.cast('TRUE') -        assert type.cast('on') -        assert type.cast('ON') -        assert type.cast(' ') -        assert type.cast("\u3000\r\n") -        assert type.cast("\u0000") -        assert type.cast('SOMETHING RANDOM') - -        # explicitly check for false vs nil -        assert_equal false, type.cast(false) -        assert_equal false, type.cast(0) -        assert_equal false, type.cast('0') -        assert_equal false, type.cast('f') -        assert_equal false, type.cast('F') -        assert_equal false, type.cast('false') -        assert_equal false, type.cast('FALSE') -        assert_equal false, type.cast('off') -        assert_equal false, type.cast('OFF') -      end - -      def test_type_cast_float -        type = Type::Float.new -        assert_equal 1.0, type.cast("1") -      end - -      def test_changing_float -        type = Type::Float.new - -        assert type.changed?(5.0, 5.0, '5wibble') -        assert_not type.changed?(5.0, 5.0, '5') -        assert_not type.changed?(5.0, 5.0, '5.0') -        assert_not type.changed?(nil, nil, nil) -      end - -      def test_type_cast_binary -        type = Type::Binary.new -        assert_equal nil, type.cast(nil) -        assert_equal "1", type.cast("1") -        assert_equal 1, type.cast(1) -      end - -      def test_type_cast_time -        type = Type::Time.new -        assert_equal nil, type.cast(nil) -        assert_equal nil, type.cast('') -        assert_equal nil, type.cast('ABC') - -        time_string = Time.now.utc.strftime("%T") -        assert_equal time_string, type.cast(time_string).strftime("%T") -      end - -      def test_type_cast_datetime_and_timestamp -        type = Type::DateTime.new -        assert_equal nil, type.cast(nil) -        assert_equal nil, type.cast('') -        assert_equal nil, type.cast('  ') -        assert_equal nil, type.cast('ABC') - -        datetime_string = Time.now.utc.strftime("%FT%T") -        assert_equal datetime_string, type.cast(datetime_string).strftime("%FT%T") -      end - -      def test_type_cast_date -        type = Type::Date.new -        assert_equal nil, type.cast(nil) -        assert_equal nil, type.cast('') -        assert_equal nil, type.cast(' ') -        assert_equal nil, type.cast('ABC') - -        date_string = Time.now.utc.strftime("%F") -        assert_equal date_string, type.cast(date_string).strftime("%F") -      end - -      def test_type_cast_duration_to_integer -        type = Type::Integer.new -        assert_equal 1800, type.cast(30.minutes) -        assert_equal 7200, type.cast(2.hours) -      end - -      def test_string_to_time_with_timezone -        [:utc, :local].each do |zone| -          with_timezone_config default: zone do -            type = Type::DateTime.new -            assert_equal Time.utc(2013, 9, 4, 0, 0, 0), type.cast("Wed, 04 Sep 2013 03:00:00 EAT") -          end -        end -      end - -      def test_type_equality -        assert_equal Type::Value.new, Type::Value.new -        assert_not_equal Type::Value.new, Type::Integer.new -        assert_not_equal Type::Value.new(precision: 1), Type::Value.new(precision: 2) -      end -        def test_attributes_which_are_invalid_for_database_can_still_be_reassigned          type_which_cannot_go_to_the_database = Type::Value.new          def type_which_cannot_go_to_the_database.serialize(*) diff --git a/activerecord/test/cases/validations/i18n_validation_test.rb b/activerecord/test/cases/validations/i18n_validation_test.rb index 268d7914b5..981239c4d6 100644 --- a/activerecord/test/cases/validations/i18n_validation_test.rb +++ b/activerecord/test/cases/validations/i18n_validation_test.rb @@ -53,8 +53,9 @@ class I18nValidationTest < ActiveRecord::TestCase      test "validates_uniqueness_of on generated message #{name}" do        Topic.validates_uniqueness_of :title, validation_options        @topic.title = unique_topic.title -      @topic.errors.expects(:generate_message).with(:title, :taken, generate_message_options.merge(:value => 'unique!')) -      @topic.valid? +      assert_called_with(@topic.errors, :generate_message, [:title, :taken, generate_message_options.merge(:value => 'unique!')]) do +        @topic.valid? +      end      end    end @@ -63,8 +64,9 @@ class I18nValidationTest < ActiveRecord::TestCase    COMMON_CASES.each do |name, validation_options, generate_message_options|      test "validates_associated on generated message #{name}" do        Topic.validates_associated :replies, validation_options -      replied_topic.errors.expects(:generate_message).with(:replies, :invalid, generate_message_options.merge(:value => replied_topic.replies)) -      replied_topic.save +      assert_called_with(replied_topic.errors, :generate_message, [:replies, :invalid, generate_message_options.merge(:value => replied_topic.replies)]) do +        replied_topic.save +      end      end    end diff --git a/activerecord/test/cases/validations/length_validation_test.rb b/activerecord/test/cases/validations/length_validation_test.rb index f95f8f0b8f..c5d8f8895c 100644 --- a/activerecord/test/cases/validations/length_validation_test.rb +++ b/activerecord/test/cases/validations/length_validation_test.rb @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*-  require "cases/helper"  require 'models/owner'  require 'models/pet' diff --git a/activerecord/test/cases/validations/uniqueness_validation_test.rb b/activerecord/test/cases/validations/uniqueness_validation_test.rb index 2608c84be2..7502a55391 100644 --- a/activerecord/test/cases/validations/uniqueness_validation_test.rb +++ b/activerecord/test/cases/validations/uniqueness_validation_test.rb @@ -4,6 +4,7 @@ require 'models/reply'  require 'models/warehouse_thing'  require 'models/guid'  require 'models/event' +require 'models/dashboard'  class Wizard < ActiveRecord::Base    self.abstract_class = true @@ -427,4 +428,45 @@ class UniquenessValidationTest < ActiveRecord::TestCase      assert reply.valid?      assert topic.valid?    end + +  def test_validate_uniqueness_of_custom_primary_key +    klass = Class.new(ActiveRecord::Base) do +      self.table_name = "keyboards" +      self.primary_key = :key_number + +      validates_uniqueness_of :key_number + +      def self.name +        "Keyboard" +      end +    end + +    klass.create!(key_number: 10) +    key2 = klass.create!(key_number: 11) + +    key2.key_number = 10 +    assert_not key2.valid? +  end + +  def test_validate_uniqueness_without_primary_key +    klass = Class.new(ActiveRecord::Base) do +      self.table_name = "dashboards" + +      validates_uniqueness_of :dashboard_id + +      def self.name; "Dashboard" end +    end + +    abc = klass.create!(dashboard_id: "abc") +    assert klass.new(dashboard_id: "xyz").valid? +    assert_not klass.new(dashboard_id: "abc").valid? + +    abc.dashboard_id = "def" + +    e = assert_raises ActiveRecord::UnknownPrimaryKey do +      abc.save! +    end +    assert_match(/\AUnknown primary key for table dashboards in model/, e.message) +    assert_match(/Can not validate uniqueness for persisted record without primary key.\z/, e.message) +  end  end diff --git a/activerecord/test/cases/validations_test.rb b/activerecord/test/cases/validations_test.rb index f4f316f393..d04f4f7ce7 100644 --- a/activerecord/test/cases/validations_test.rb +++ b/activerecord/test/cases/validations_test.rb @@ -52,6 +52,13 @@ class ValidationsTest < ActiveRecord::TestCase      assert r.valid?(:special_case)    end +  def test_invalid_using_multiple_contexts +    r = WrongReply.new(:title => 'Wrong Create') +    assert r.invalid?([:special_case, :create]) +    assert_equal "Invalid", r.errors[:author_name].join +    assert_equal "is Wrong Create", r.errors[:title].join +  end +    def test_validate      r = WrongReply.new @@ -161,4 +168,15 @@ class ValidationsTest < ActiveRecord::TestCase    ensure      Topic.reset_column_information    end + +  def test_acceptance_validator_doesnt_require_db_connection +    klass = Class.new(ActiveRecord::Base) do +      self.table_name = 'posts' +    end +    klass.reset_column_information + +    assert_no_queries do +      klass.validates_acceptance_of(:foo) +    end +  end  end diff --git a/activerecord/test/cases/view_test.rb b/activerecord/test/cases/view_test.rb index f9dca1e196..e80d8bd584 100644 --- a/activerecord/test/cases/view_test.rb +++ b/activerecord/test/cases/view_test.rb @@ -1,7 +1,9 @@  require "cases/helper"  require "models/book" +require "support/schema_dumping_helper"  module ViewBehavior +  include SchemaDumpingHelper    extend ActiveSupport::Concern    included do @@ -31,11 +33,26 @@ module ViewBehavior      assert_equal ["Ruby for Rails"], books.map(&:name)    end +  def test_views +    assert_equal [Ebook.table_name], @connection.views +  end + +  def test_view_exists +    view_name = Ebook.table_name +    assert @connection.view_exists?(view_name), "'#{view_name}' view should exist" +  end +    def test_table_exists      view_name = Ebook.table_name +    # TODO: switch this assertion around once we changed #tables to not return views.      assert @connection.table_exists?(view_name), "'#{view_name}' table should exist"    end +  def test_views_ara_valid_data_sources +    view_name = Ebook.table_name +    assert @connection.data_source_exists?(view_name), "'#{view_name}' should be a data source" +  end +    def test_column_definitions      assert_equal([["id", :integer],                    ["name", :string], @@ -53,6 +70,11 @@ module ViewBehavior      end      assert_nil model.primary_key    end + +  def test_does_not_dump_view_as_table +    schema = dump_table_schema "ebooks" +    assert_no_match %r{create_table "ebooks"}, schema +  end  end  if ActiveRecord::Base.connection.supports_views? @@ -70,6 +92,7 @@ class ViewWithPrimaryKeyTest < ActiveRecord::TestCase  end  class ViewWithoutPrimaryKeyTest < ActiveRecord::TestCase +  include SchemaDumpingHelper    fixtures :books    class Paperback < ActiveRecord::Base; end @@ -91,6 +114,15 @@ class ViewWithoutPrimaryKeyTest < ActiveRecord::TestCase      assert_equal ["Agile Web Development with Rails"], books.map(&:name)    end +  def test_views +    assert_equal [Paperback.table_name], @connection.views +  end + +  def test_view_exists +    view_name = Paperback.table_name +    assert @connection.view_exists?(view_name), "'#{view_name}' view should exist" +  end +    def test_table_exists      view_name = Paperback.table_name      assert @connection.table_exists?(view_name), "'#{view_name}' table should exist" @@ -109,5 +141,76 @@ class ViewWithoutPrimaryKeyTest < ActiveRecord::TestCase    def test_does_not_have_a_primary_key      assert_nil Paperback.primary_key    end + +  def test_does_not_dump_view_as_table +    schema = dump_table_schema "paperbacks" +    assert_no_match %r{create_table "paperbacks"}, schema +  end +end + +# sqlite dose not support CREATE, INSERT, and DELETE for VIEW +if current_adapter?(:MysqlAdapter, :Mysql2Adapter, :PostgreSQLAdapter) +class UpdateableViewTest < ActiveRecord::TestCase +  self.use_transactional_tests = false +  fixtures :books + +  class PrintedBook < ActiveRecord::Base +    self.primary_key = "id" +  end + +  setup do +    @connection = ActiveRecord::Base.connection +    @connection.execute <<-SQL +      CREATE VIEW printed_books +        AS SELECT id, name, status, format FROM books WHERE format = 'paperback' +    SQL +  end + +  teardown do +    @connection.execute "DROP VIEW printed_books" if @connection.table_exists? "printed_books" +  end + +  def test_update_record +    book = PrintedBook.first +    book.name = "AWDwR" +    book.save! +    book.reload +    assert_equal "AWDwR", book.name +  end + +  def test_insert_record +    PrintedBook.create! name: "Rails in Action", status: 0, format: "paperback" + +    new_book = PrintedBook.last +    assert_equal "Rails in Action", new_book.name +  end + +  def test_update_record_to_fail_view_conditions +    book = PrintedBook.first +    book.format = "ebook" +    book.save! + +    assert_raises ActiveRecord::RecordNotFound do +      book.reload +    end +  end +end +end # end fo `if current_adapter?(:MysqlAdapter, :Mysql2Adapter, :PostgreSQLAdapter)` +end # end fo `if ActiveRecord::Base.connection.supports_views?` + +if ActiveRecord::Base.connection.respond_to?(:supports_materialized_views?) && +    ActiveRecord::Base.connection.supports_materialized_views? +class MaterializedViewTest < ActiveRecord::PostgreSQLTestCase +  include ViewBehavior + +  private +  def create_view(name, query) +    @connection.execute "CREATE MATERIALIZED VIEW #{name} AS #{query}" +  end + +  def drop_view(name) +    @connection.execute "DROP MATERIALIZED VIEW #{name}" if @connection.table_exists? name + +  end  end  end diff --git a/activerecord/test/cases/xml_serialization_test.rb b/activerecord/test/cases/xml_serialization_test.rb deleted file mode 100644 index b30b50f597..0000000000 --- a/activerecord/test/cases/xml_serialization_test.rb +++ /dev/null @@ -1,447 +0,0 @@ -require "cases/helper" -require "rexml/document" -require 'models/contact' -require 'models/post' -require 'models/author' -require 'models/comment' -require 'models/company_in_module' -require 'models/toy' -require 'models/topic' -require 'models/reply' -require 'models/company' - -class XmlSerializationTest < ActiveRecord::TestCase -  def test_should_serialize_default_root -    @xml = Contact.new.to_xml -    assert_match %r{^<contact>},  @xml -    assert_match %r{</contact>$}, @xml -  end - -  def test_should_serialize_default_root_with_namespace -    @xml = Contact.new.to_xml :namespace=>"http://xml.rubyonrails.org/contact" -    assert_match %r{^<contact xmlns="http://xml\.rubyonrails\.org/contact">},  @xml -    assert_match %r{</contact>$}, @xml -  end - -  def test_should_serialize_custom_root -    @xml = Contact.new.to_xml :root => 'xml_contact' -    assert_match %r{^<xml-contact>},  @xml -    assert_match %r{</xml-contact>$}, @xml -  end - -  def test_should_allow_undasherized_tags -    @xml = Contact.new.to_xml :root => 'xml_contact', :dasherize => false -    assert_match %r{^<xml_contact>},  @xml -    assert_match %r{</xml_contact>$}, @xml -    assert_match %r{<created_at},     @xml -  end - -  def test_should_allow_camelized_tags -    @xml = Contact.new.to_xml :root => 'xml_contact', :camelize => true -    assert_match %r{^<XmlContact>},  @xml -    assert_match %r{</XmlContact>$}, @xml -    assert_match %r{<CreatedAt},    @xml -  end - -  def test_should_allow_skipped_types -    @xml = Contact.new(:age => 25).to_xml :skip_types => true -    assert %r{<age>25</age>}.match(@xml) -  end - -  def test_should_include_yielded_additions -    @xml = Contact.new.to_xml do |xml| -      xml.creator "David" -    end -    assert_match %r{<creator>David</creator>}, @xml -  end - -  def test_to_xml_with_block -    value = "Rockin' the block" -    xml = Contact.new.to_xml(:skip_instruct => true) do |_xml| -      _xml.tag! "arbitrary-element", value -    end -    assert_equal "<contact>", xml.first(9) -    assert xml.include?(%(<arbitrary-element>#{value}</arbitrary-element>)) -  end - -  def test_should_skip_instruct_for_included_records -    @contact = Contact.new -    @contact.alternative = Contact.new(:name => 'Copa Cabana') -    @xml = @contact.to_xml(:include => [ :alternative ]) -    assert_equal @xml.index('<?xml '), 0 -    assert_nil @xml.index('<?xml ', 1) -  end -end - -class DefaultXmlSerializationTest < ActiveRecord::TestCase -  def setup -    @contact = Contact.new( -      :name        => 'aaron stack', -      :age         => 25, -      :avatar      => 'binarydata', -      :created_at  => Time.utc(2006, 8, 1), -      :awesome     => false, -      :preferences => { :gem => 'ruby' } -    ) -  end - -  def test_should_serialize_string -    assert_match %r{<name>aaron stack</name>}, @contact.to_xml -  end - -  def test_should_serialize_integer -    assert_match %r{<age type="integer">25</age>}, @contact.to_xml -  end - -  def test_should_serialize_binary -    xml = @contact.to_xml -    assert_match %r{YmluYXJ5ZGF0YQ==\n</avatar>},    xml -    assert_match %r{<avatar(.*)(type="binary")},     xml -    assert_match %r{<avatar(.*)(encoding="base64")}, xml -  end - -  def test_should_serialize_datetime -    assert_match %r{<created-at type=\"dateTime\">2006-08-01T00:00:00Z</created-at>}, @contact.to_xml -  end - -  def test_should_serialize_boolean -    assert_match %r{<awesome type=\"boolean\">false</awesome>}, @contact.to_xml -  end - -  def test_should_serialize_hash -    assert_match %r{<preferences>\s*<gem>ruby</gem>\s*</preferences>}m, @contact.to_xml -  end - -  def test_uses_serializable_hash_with_only_option -    def @contact.serializable_hash(options=nil) -      super(only: %w(name)) -    end - -    xml = @contact.to_xml -    assert_match %r{<name>aaron stack</name>}, xml -    assert_no_match %r{age}, xml -    assert_no_match %r{awesome}, xml -  end - -  def test_uses_serializable_hash_with_except_option -    def @contact.serializable_hash(options=nil) -      super(except: %w(age)) -    end - -    xml = @contact.to_xml -    assert_match %r{<name>aaron stack</name>}, xml -    assert_match %r{<awesome type=\"boolean\">false</awesome>}, xml -    assert_no_match %r{age}, xml -  end - -  def test_does_not_include_inheritance_column_from_sti -    @contact = ContactSti.new(@contact.attributes) -    assert_equal 'ContactSti', @contact.type - -    xml = @contact.to_xml -    assert_match %r{<name>aaron stack</name>}, xml -    assert_no_match %r{<type}, xml -    assert_no_match %r{ContactSti}, xml -  end - -  def test_serializable_hash_with_default_except_option_and_excluding_inheritance_column_from_sti -    @contact = ContactSti.new(@contact.attributes) -    assert_equal 'ContactSti', @contact.type - -    def @contact.serializable_hash(options={}) -      super({ except: %w(age) }.merge!(options)) -    end - -    xml = @contact.to_xml -    assert_match %r{<name>aaron stack</name>}, xml -    assert_no_match %r{age}, xml -    assert_no_match %r{<type}, xml -    assert_no_match %r{ContactSti}, xml -  end -end - -class DefaultXmlSerializationTimezoneTest < ActiveRecord::TestCase -  def test_should_serialize_datetime_with_timezone -    with_timezone_config zone: "Pacific Time (US & Canada)" do -      toy = Toy.create(:name => 'Mickey', :updated_at => Time.utc(2006, 8, 1)) -      assert_match %r{<updated-at type=\"dateTime\">2006-07-31T17:00:00-07:00</updated-at>}, toy.to_xml -    end -  end - -  def test_should_serialize_datetime_with_timezone_reloaded -    with_timezone_config zone: "Pacific Time (US & Canada)" do -      toy = Toy.create(:name => 'Minnie', :updated_at => Time.utc(2006, 8, 1)).reload -      assert_match %r{<updated-at type=\"dateTime\">2006-07-31T17:00:00-07:00</updated-at>}, toy.to_xml -    end -  end -end - -class NilXmlSerializationTest < ActiveRecord::TestCase -  def setup -    @xml = Contact.new.to_xml(:root => 'xml_contact') -  end - -  def test_should_serialize_string -    assert_match %r{<name nil="true"/>}, @xml -  end - -  def test_should_serialize_integer -    assert %r{<age (.*)/>}.match(@xml) -    attributes = $1 -    assert_match %r{nil="true"}, attributes -    assert_match %r{type="integer"}, attributes -  end - -  def test_should_serialize_binary -    assert %r{<avatar (.*)/>}.match(@xml) -    attributes = $1 -    assert_match %r{type="binary"}, attributes -    assert_match %r{encoding="base64"}, attributes -    assert_match %r{nil="true"}, attributes -  end - -  def test_should_serialize_datetime -    assert %r{<created-at (.*)/>}.match(@xml) -    attributes = $1 -    assert_match %r{nil="true"}, attributes -    assert_match %r{type="dateTime"}, attributes -  end - -  def test_should_serialize_boolean -    assert %r{<awesome (.*)/>}.match(@xml) -    attributes = $1 -    assert_match %r{type="boolean"}, attributes -    assert_match %r{nil="true"}, attributes -  end - -  def test_should_serialize_yaml -    assert_match %r{<preferences nil=\"true\"/>}, @xml -  end -end - -class DatabaseConnectedXmlSerializationTest < ActiveRecord::TestCase -  fixtures :topics, :companies, :accounts, :authors, :posts, :projects - -  def test_to_xml -    xml = REXML::Document.new(topics(:first).to_xml(:indent => 0)) -    bonus_time_in_current_timezone = topics(:first).bonus_time.xmlschema -    written_on_in_current_timezone = topics(:first).written_on.xmlschema - -    assert_equal "topic", xml.root.name -    assert_equal "The First Topic" , xml.elements["//title"].text -    assert_equal "David" , xml.elements["//author-name"].text -    assert_match "Have a nice day", xml.elements["//content"].text - -    assert_equal "1", xml.elements["//id"].text -    assert_equal "integer" , xml.elements["//id"].attributes['type'] - -    assert_equal "1", xml.elements["//replies-count"].text -    assert_equal "integer" , xml.elements["//replies-count"].attributes['type'] - -    assert_equal written_on_in_current_timezone, xml.elements["//written-on"].text -    assert_equal "dateTime" , xml.elements["//written-on"].attributes['type'] - -    assert_equal "david@loudthinking.com", xml.elements["//author-email-address"].text - -    assert_equal nil, xml.elements["//parent-id"].text -    assert_equal "integer", xml.elements["//parent-id"].attributes['type'] -    assert_equal "true", xml.elements["//parent-id"].attributes['nil'] - -    # Oracle enhanced adapter allows to define Date attributes in model class (see topic.rb) -    assert_equal "2004-04-15", xml.elements["//last-read"].text -    assert_equal "date" , xml.elements["//last-read"].attributes['type'] - -    # Oracle and DB2 don't have true boolean or time-only fields -    unless current_adapter?(:OracleAdapter, :DB2Adapter) -      assert_equal "false", xml.elements["//approved"].text -      assert_equal "boolean" , xml.elements["//approved"].attributes['type'] - -      assert_equal bonus_time_in_current_timezone, xml.elements["//bonus-time"].text -      assert_equal "dateTime" , xml.elements["//bonus-time"].attributes['type'] -    end -  end - -  def test_except_option -    xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :except => [:title, :replies_count]) -    assert_equal "<topic>", xml.first(7) -    assert !xml.include?(%(<title>The First Topic</title>)) -    assert xml.include?(%(<author-name>David</author-name>)) - -    xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :except => [:title, :author_name, :replies_count]) -    assert !xml.include?(%(<title>The First Topic</title>)) -    assert !xml.include?(%(<author-name>David</author-name>)) -  end - -  # to_xml used to mess with the hash the user provided which -  # caused the builder to be reused.  This meant the document kept -  # getting appended to. - -  def test_modules -    projects = MyApplication::Business::Project.all -    xml = projects.to_xml -    root = projects.first.class.to_s.underscore.pluralize.tr('/','_').dasherize -    assert_match "<#{root} type=\"array\">", xml -    assert_match "</#{root}>", xml -  end - -  def test_passing_hash_shouldnt_reuse_builder -    options = {:include=>:posts} -    david = authors(:david) -    first_xml_size = david.to_xml(options).size -    second_xml_size = david.to_xml(options).size -    assert_equal first_xml_size, second_xml_size -  end - -  def test_include_uses_association_name -    xml = authors(:david).to_xml :include=>:hello_posts, :indent => 0 -    assert_match %r{<hello-posts type="array">}, xml -    assert_match %r{<hello-post type="Post">}, xml -    assert_match %r{<hello-post type="StiPost">}, xml -  end - -  def test_included_associations_should_skip_types -    xml = authors(:david).to_xml :include=>:hello_posts, :indent => 0, :skip_types => true -    assert_match %r{<hello-posts>}, xml -    assert_match %r{<hello-post>}, xml -    assert_match %r{<hello-post>}, xml -  end - -  def test_including_has_many_association -    xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :include => :replies, :except => :replies_count) -    assert_equal "<topic>", xml.first(7) -    assert xml.include?(%(<replies type="array"><reply>)) -    assert xml.include?(%(<title>The Second Topic of the day</title>)) -  end - -  def test_including_belongs_to_association -    xml = companies(:first_client).to_xml(:indent => 0, :skip_instruct => true, :include => :firm) -    assert !xml.include?("<firm>") - -    xml = companies(:second_client).to_xml(:indent => 0, :skip_instruct => true, :include => :firm) -    assert xml.include?("<firm>") -  end - -  def test_including_multiple_associations -    xml = companies(:first_firm).to_xml(:indent => 0, :skip_instruct => true, :include => [ :clients, :account ]) -    assert_equal "<firm>", xml.first(6) -    assert xml.include?(%(<account>)) -    assert xml.include?(%(<clients type="array"><client>)) -  end - -  def test_including_association_with_options -    xml = companies(:first_firm).to_xml( -      :indent  => 0, :skip_instruct => true, -      :include => { :clients => { :only => :name } } -    ) - -    assert_equal "<firm>", xml.first(6) -    assert xml.include?(%(<client><name>Summit</name></client>)) -    assert xml.include?(%(<clients type="array"><client>)) -  end - -  def test_methods_are_called_on_object -    xml = authors(:david).to_xml :methods => :label, :indent => 0 -    assert_match %r{<label>.*</label>}, xml -  end - -  def test_should_not_call_methods_on_associations_that_dont_respond -    xml = authors(:david).to_xml :include=>:hello_posts, :methods => :label, :indent => 2 -    assert !authors(:david).hello_posts.first.respond_to?(:label) -    assert_match %r{^  <label>.*</label>}, xml -    assert_no_match %r{^      <label>}, xml -  end - -  def test_procs_are_called_on_object -    proc = Proc.new { |options| options[:builder].tag!('nationality', 'Danish') } -    xml = authors(:david).to_xml(:procs => [ proc ]) -    assert_match %r{<nationality>Danish</nationality>}, xml -  end - -  def test_dual_arity_procs_are_called_on_object -    proc = Proc.new { |options, record| options[:builder].tag!('name-reverse', record.name.reverse) } -    xml = authors(:david).to_xml(:procs => [ proc ]) -    assert_match %r{<name-reverse>divaD</name-reverse>}, xml -  end - -  def test_top_level_procs_arent_applied_to_associations -    author_proc = Proc.new { |options| options[:builder].tag!('nationality', 'Danish') } -    xml = authors(:david).to_xml(:procs => [ author_proc ], :include => :posts, :indent => 2) - -    assert_match %r{^  <nationality>Danish</nationality>}, xml -    assert_no_match %r{^ {6}<nationality>Danish</nationality>}, xml -  end - -  def test_procs_on_included_associations_are_called -    posts_proc = Proc.new { |options| options[:builder].tag!('copyright', 'DHH') } -    xml = authors(:david).to_xml( -      :indent => 2, -      :include => { -        :posts => { :procs => [ posts_proc ] } -      } -    ) - -    assert_no_match %r{^  <copyright>DHH</copyright>}, xml -    assert_match %r{^ {6}<copyright>DHH</copyright>}, xml -  end - -  def test_should_include_empty_has_many_as_empty_array -    authors(:david).posts.delete_all -    xml = authors(:david).to_xml :include=>:posts, :indent => 2 - -    assert_equal [], Hash.from_xml(xml)['author']['posts'] -    assert_match %r{^  <posts type="array"/>}, xml -  end - -  def test_should_has_many_array_elements_should_include_type_when_different_from_guessed_value -    xml = authors(:david).to_xml :include=>:posts_with_comments, :indent => 2 - -    assert Hash.from_xml(xml) -    assert_match %r{^  <posts-with-comments type="array">}, xml -    assert_match %r{^    <posts-with-comment type="Post">}, xml -    assert_match %r{^    <posts-with-comment type="StiPost">}, xml - -    types = Hash.from_xml(xml)['author']['posts_with_comments'].collect {|t| t['type'] } -    assert types.include?('SpecialPost') -    assert types.include?('Post') -    assert types.include?('StiPost') -  end - -  def test_should_produce_xml_for_methods_returning_array -    xml = authors(:david).to_xml(:methods => :social) -    array = Hash.from_xml(xml)['author']['social'] -    assert_equal 2, array.size -    assert array.include? 'twitter' -    assert array.include? 'github' -  end - -  def test_should_support_aliased_attributes -    xml = Author.select("name as firstname").to_xml -    Author.all.each do |author| -      assert xml.include?(%(<firstname>#{author.name}</firstname>)), xml -    end -  end - -  def test_array_to_xml_including_has_many_association -    xml = [ topics(:first), topics(:second) ].to_xml(:indent => 0, :skip_instruct => true, :include => :replies) -    assert xml.include?(%(<replies type="array"><reply>)) -  end - -  def test_array_to_xml_including_methods -    xml = [ topics(:first), topics(:second) ].to_xml(:indent => 0, :skip_instruct => true, :methods => [ :topic_id ]) -    assert xml.include?(%(<topic-id type="integer">#{topics(:first).topic_id}</topic-id>)), xml -    assert xml.include?(%(<topic-id type="integer">#{topics(:second).topic_id}</topic-id>)), xml -  end - -  def test_array_to_xml_including_has_one_association -    xml = [ companies(:first_firm), companies(:rails_core) ].to_xml(:indent => 0, :skip_instruct => true, :include => :account) -    assert xml.include?(companies(:first_firm).account.to_xml(:indent => 0, :skip_instruct => true)) -    assert xml.include?(companies(:rails_core).account.to_xml(:indent => 0, :skip_instruct => true)) -  end - -  def test_array_to_xml_including_belongs_to_association -    xml = [ companies(:first_client), companies(:second_client), companies(:another_client) ].to_xml(:indent => 0, :skip_instruct => true, :include => :firm) -    assert xml.include?(companies(:first_client).to_xml(:indent => 0, :skip_instruct => true)) -    assert xml.include?(companies(:second_client).firm.to_xml(:indent => 0, :skip_instruct => true)) -    assert xml.include?(companies(:another_client).firm.to_xml(:indent => 0, :skip_instruct => true)) -  end -end diff --git a/activerecord/test/fixtures/bad_posts.yml b/activerecord/test/fixtures/bad_posts.yml new file mode 100644 index 0000000000..addee8e3bf --- /dev/null +++ b/activerecord/test/fixtures/bad_posts.yml @@ -0,0 +1,9 @@ +# Please do not use this fixture without `set_fixture_class` as Post + +_fixture: +  model_class: BadPostModel + +bad_welcome: +  author_id: 1 +  title: Welcome to the another weblog +  body: It's really nice today diff --git a/activerecord/test/fixtures/naked/yml/parrots.yml b/activerecord/test/fixtures/naked/yml/parrots.yml new file mode 100644 index 0000000000..3e10331105 --- /dev/null +++ b/activerecord/test/fixtures/naked/yml/parrots.yml @@ -0,0 +1,2 @@ +george: +  arrr: "Curious George" diff --git a/activerecord/test/fixtures/other_comments.yml b/activerecord/test/fixtures/other_comments.yml new file mode 100644 index 0000000000..55e8216ec7 --- /dev/null +++ b/activerecord/test/fixtures/other_comments.yml @@ -0,0 +1,6 @@ +_fixture: +  model_class: Comment + +second_greetings: +  post: second_welcome +  body: Thank you for the second welcome diff --git a/activerecord/test/fixtures/other_posts.yml b/activerecord/test/fixtures/other_posts.yml new file mode 100644 index 0000000000..39ff763547 --- /dev/null +++ b/activerecord/test/fixtures/other_posts.yml @@ -0,0 +1,7 @@ +_fixture: +  model_class: Post + +second_welcome: +  author_id: 1 +  title: Welcome to the another weblog +  body: It's really nice today diff --git a/activerecord/test/models/aircraft.rb b/activerecord/test/models/aircraft.rb index 1f35ef45da..c4404a8094 100644 --- a/activerecord/test/models/aircraft.rb +++ b/activerecord/test/models/aircraft.rb @@ -1,4 +1,5 @@  class Aircraft < ActiveRecord::Base    self.pluralize_table_names = false    has_many :engines, :foreign_key => "car_id" +  has_many :wheels, as: :wheelable  end diff --git a/activerecord/test/models/author.rb b/activerecord/test/models/author.rb index 8c1f14bd36..0d90cbb110 100644 --- a/activerecord/test/models/author.rb +++ b/activerecord/test/models/author.rb @@ -144,9 +144,6 @@ class Author < ActiveRecord::Base    has_many :posts_with_signature, ->(record) { where("posts.title LIKE ?", "%by #{record.name.downcase}%") }, class_name: "Post" -  scope :relation_include_posts, -> { includes(:posts) } -  scope :relation_include_tags,  -> { includes(:tags) } -    attr_accessor :post_log    after_initialize :set_post_log diff --git a/activerecord/test/models/book.rb b/activerecord/test/models/book.rb index 24bfe47bbf..1927191393 100644 --- a/activerecord/test/models/book.rb +++ b/activerecord/test/models/book.rb @@ -10,10 +10,10 @@ class Book < ActiveRecord::Base    enum status: [:proposed, :written, :published]    enum read_status: {unread: 0, reading: 2, read: 3}    enum nullable_status: [:single, :married] -  enum language: [:english, :spanish, :french], enum_prefix: :in -  enum author_visibility: [:visible, :invisible], enum_prefix: true -  enum illustrator_visibility: [:visible, :invisible], enum_prefix: true -  enum font_size: [:small, :medium, :large], enum_prefix: :with, enum_suffix: true +  enum language: [:english, :spanish, :french], _prefix: :in +  enum author_visibility: [:visible, :invisible], _prefix: true +  enum illustrator_visibility: [:visible, :invisible], _prefix: true +  enum font_size: [:small, :medium, :large], _prefix: :with, _suffix: true    def published!      super diff --git a/activerecord/test/models/carrier.rb b/activerecord/test/models/carrier.rb new file mode 100644 index 0000000000..230be118c3 --- /dev/null +++ b/activerecord/test/models/carrier.rb @@ -0,0 +1,2 @@ +class Carrier < ActiveRecord::Base +end diff --git a/activerecord/test/models/categorization.rb b/activerecord/test/models/categorization.rb index 6588531de6..4cd67c970a 100644 --- a/activerecord/test/models/categorization.rb +++ b/activerecord/test/models/categorization.rb @@ -1,6 +1,6 @@  class Categorization < ActiveRecord::Base    belongs_to :post -  belongs_to :category +  belongs_to :category, counter_cache: true    belongs_to :named_category, :class_name => 'Category', :foreign_key => :named_category_name, :primary_key => :name    belongs_to :author diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb index 67936e8e5d..a96b8ef0f2 100644 --- a/activerecord/test/models/company.rb +++ b/activerecord/test/models/company.rb @@ -86,6 +86,9 @@ class Firm < Company    has_many :association_with_references, -> { references(:foo) }, :class_name => 'Client' +  has_one :lead_developer, class_name: "Developer" +  has_many :projects +    def log      @log ||= []    end diff --git a/activerecord/test/models/contact.rb b/activerecord/test/models/contact.rb index 3ea17c3abf..9f2f69e1ee 100644 --- a/activerecord/test/models/contact.rb +++ b/activerecord/test/models/contact.rb @@ -3,7 +3,7 @@ module ContactFakeColumns      base.class_eval do        establish_connection(:adapter => 'fake') -      connection.tables = [table_name] +      connection.data_sources = [table_name]        connection.primary_keys = {          table_name => 'id'        } diff --git a/activerecord/test/models/customer_carrier.rb b/activerecord/test/models/customer_carrier.rb new file mode 100644 index 0000000000..37186903ff --- /dev/null +++ b/activerecord/test/models/customer_carrier.rb @@ -0,0 +1,14 @@ +class CustomerCarrier < ActiveRecord::Base +  cattr_accessor :current_customer + +  belongs_to :customer +  belongs_to :carrier + +  default_scope -> { +    if current_customer +      where(customer: current_customer) +    else +      all +    end +  } +end diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb index d2a5a7fc49..7c5941b1af 100644 --- a/activerecord/test/models/developer.rb +++ b/activerecord/test/models/developer.rb @@ -7,6 +7,8 @@ module DeveloperProjectsAssociationExtension2  end  class Developer < ActiveRecord::Base +  self.ignored_columns = %w(first_name last_name) +    has_and_belongs_to_many :projects do      def find_most_recent        order("id DESC").first @@ -50,6 +52,10 @@ class Developer < ActiveRecord::Base    has_many :firms, :through => :contracts, :source => :firm    has_many :comments, ->(developer) { where(body: "I'm #{developer.name}") }    has_many :ratings, through: :comments +  has_one :ship, dependent: :nullify + +  belongs_to :firm +  has_many :contracted_projects, class_name: "Project"    scope :jamises, -> { where(:name => 'Jamis') } @@ -60,6 +66,9 @@ class Developer < ActiveRecord::Base      developer.audit_logs.build :message => "Computer created"    end +  attr_accessor :last_name +  define_attribute_method 'last_name' +    def log=(message)      audit_logs.build :message => message    end diff --git a/activerecord/test/models/face.rb b/activerecord/test/models/face.rb index 91e46f83e5..af76fea52c 100644 --- a/activerecord/test/models/face.rb +++ b/activerecord/test/models/face.rb @@ -1,7 +1,7 @@  class Face < ActiveRecord::Base    belongs_to :man, :inverse_of => :face    belongs_to :polymorphic_man, :polymorphic => true, :inverse_of => :polymorphic_face -  # Oracle identifier lengh is limited to 30 bytes or less, `polymorphic` renamed `poly` +  # Oracle identifier length is limited to 30 bytes or less, `polymorphic` renamed `poly`    belongs_to :poly_man_without_inverse, :polymorphic => true    # These is a "broken" inverse_of for the purposes of testing    belongs_to :horrible_man, :class_name => 'Man', :inverse_of => :horrible_face diff --git a/activerecord/test/models/member.rb b/activerecord/test/models/member.rb index dc0566d8a7..7693c6e515 100644 --- a/activerecord/test/models/member.rb +++ b/activerecord/test/models/member.rb @@ -26,6 +26,9 @@ class Member < ActiveRecord::Base    has_many :current_memberships, -> { where :favourite => true }    has_many :clubs, :through => :current_memberships +  has_many :tenant_memberships +  has_many :tenant_clubs, through: :tenant_memberships, class_name: 'Club', source: :club +    has_one :club_through_many, :through => :current_memberships, :source => :club    belongs_to :admittable, polymorphic: true diff --git a/activerecord/test/models/member_detail.rb b/activerecord/test/models/member_detail.rb index 9d253aa126..157130986c 100644 --- a/activerecord/test/models/member_detail.rb +++ b/activerecord/test/models/member_detail.rb @@ -1,7 +1,8 @@  class MemberDetail < ActiveRecord::Base -  belongs_to :member, :inverse_of => false +  belongs_to :member, inverse_of: false    belongs_to :organization -  has_one :member_type, :through => :member +  has_one :member_type, through: :member +  has_one :membership, through: :member -  has_many :organization_member_details, :through => :organization, :source => :member_details +  has_many :organization_member_details, through: :organization, source: :member_details  end diff --git a/activerecord/test/models/membership.rb b/activerecord/test/models/membership.rb index df7167ee93..e181ba1f11 100644 --- a/activerecord/test/models/membership.rb +++ b/activerecord/test/models/membership.rb @@ -18,3 +18,18 @@ class SelectedMembership < Membership      select("'1' as foo")    end  end + +class TenantMembership < Membership +  cattr_accessor :current_member + +  belongs_to :member +  belongs_to :club + +  default_scope -> { +    if current_member +      where(member: current_member) +    else +      all +    end +  } +end diff --git a/activerecord/test/models/parrot.rb b/activerecord/test/models/parrot.rb index b26035d944..ddc9dcaf29 100644 --- a/activerecord/test/models/parrot.rb +++ b/activerecord/test/models/parrot.rb @@ -21,9 +21,3 @@ end  class DeadParrot < Parrot    belongs_to :killer, :class_name => 'Pirate', foreign_key: :killer_id  end - -class FunkyParrot < Parrot -  before_destroy do -    raise "before_destroy was called" -  end -end diff --git a/activerecord/test/models/person.rb b/activerecord/test/models/person.rb index ad12f00d42..a4a9c6b0d4 100644 --- a/activerecord/test/models/person.rb +++ b/activerecord/test/models/person.rb @@ -37,7 +37,6 @@ class Person < ActiveRecord::Base    has_many :essays, primary_key: "first_name", foreign_key: "writer_id"    scope :males,   -> { where(:gender => 'M') } -  scope :females, -> { where(:gender => 'F') }  end  class PersonWithDependentDestroyJobs < ActiveRecord::Base diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb index 052b1c9690..81a18188d4 100644 --- a/activerecord/test/models/post.rb +++ b/activerecord/test/models/post.rb @@ -98,11 +98,11 @@ class Post < ActiveRecord::Base      end    end -  has_many :taggings_with_delete_all, :class_name => 'Tagging', :as => :taggable, :dependent => :delete_all -  has_many :taggings_with_destroy, :class_name => 'Tagging', :as => :taggable, :dependent => :destroy +  has_many :taggings_with_delete_all, :class_name => 'Tagging', :as => :taggable, :dependent => :delete_all, counter_cache: :taggings_with_delete_all_count +  has_many :taggings_with_destroy, :class_name => 'Tagging', :as => :taggable, :dependent => :destroy, counter_cache: :taggings_with_destroy_count -  has_many :tags_with_destroy, :through => :taggings, :source => :tag, :dependent => :destroy -  has_many :tags_with_nullify, :through => :taggings, :source => :tag, :dependent => :nullify +  has_many :tags_with_destroy, :through => :taggings, :source => :tag, :dependent => :destroy, counter_cache: :tags_with_destroy_count +  has_many :tags_with_nullify, :through => :taggings, :source => :tag, :dependent => :nullify, counter_cache: :tags_with_nullify_count    has_many :misc_tags, -> { where :tags => { :name => 'Misc' } }, :through => :taggings, :source => :tag    has_many :funky_tags, :through => :taggings, :source => :tag @@ -208,6 +208,22 @@ class PostWithDefaultScope < ActiveRecord::Base    default_scope { order(:title) }  end +class PostWithPreloadDefaultScope < ActiveRecord::Base +  self.table_name = 'posts' + +  has_many :readers, foreign_key: 'post_id' + +  default_scope { preload(:readers) } +end + +class PostWithIncludesDefaultScope < ActiveRecord::Base +  self.table_name = 'posts' + +  has_many :readers, foreign_key: 'post_id' + +  default_scope { includes(:readers) } +end +  class SpecialPostWithDefaultScope < ActiveRecord::Base    self.table_name = 'posts'    default_scope { where(:id => [1, 5,6]) } diff --git a/activerecord/test/models/professor.rb b/activerecord/test/models/professor.rb new file mode 100644 index 0000000000..7654eda0ef --- /dev/null +++ b/activerecord/test/models/professor.rb @@ -0,0 +1,5 @@ +require_dependency 'models/arunit2_model' + +class Professor < ARUnit2Model +  has_and_belongs_to_many :courses +end diff --git a/activerecord/test/models/project.rb b/activerecord/test/models/project.rb index 7f42a4b1f8..5328330653 100644 --- a/activerecord/test/models/project.rb +++ b/activerecord/test/models/project.rb @@ -11,6 +11,8 @@ class Project < ActiveRecord::Base                              :before_remove => Proc.new {|o, r| o.developers_log << "before_removing#{r.id}"},                              :after_remove => Proc.new {|o, r| o.developers_log << "after_removing#{r.id}"}    has_and_belongs_to_many :well_payed_salary_groups, -> { group("developers.salary").having("SUM(salary) > 10000").select("SUM(salary) as salary") }, :class_name => "Developer" +  belongs_to :firm +  has_one :lead_developer, through: :firm, inverse_of: :contracted_projects    attr_accessor :developers_log    after_initialize :set_developers_log diff --git a/activerecord/test/models/ship.rb b/activerecord/test/models/ship.rb index 312caef604..e333b964ab 100644 --- a/activerecord/test/models/ship.rb +++ b/activerecord/test/models/ship.rb @@ -3,6 +3,7 @@ class Ship < ActiveRecord::Base    belongs_to :pirate    belongs_to :update_only_pirate, :class_name => 'Pirate' +  belongs_to :developer, dependent: :destroy    has_many :parts, :class_name => 'ShipPart'    has_many :treasures @@ -19,6 +20,18 @@ class Ship < ActiveRecord::Base    end  end +class ShipWithoutNestedAttributes < ActiveRecord::Base +  self.table_name = "ships" +  has_many :prisoners, inverse_of: :ship, foreign_key: :ship_id +  has_many :parts, class_name: "ShipPart", foreign_key: :ship_id + +  validates :name, presence: true +end + +class Prisoner < ActiveRecord::Base +  belongs_to :ship, autosave: true, class_name: "ShipWithoutNestedAttributes", inverse_of: :prisoners +end +  class FamousShip < ActiveRecord::Base    self.table_name = 'ships'    belongs_to :famous_pirate diff --git a/activerecord/test/models/shop_account.rb b/activerecord/test/models/shop_account.rb new file mode 100644 index 0000000000..1580e8b20c --- /dev/null +++ b/activerecord/test/models/shop_account.rb @@ -0,0 +1,6 @@ +class ShopAccount < ActiveRecord::Base +  belongs_to :customer +  belongs_to :customer_carrier + +  has_one :carrier, through: :customer_carrier +end diff --git a/activerecord/test/models/topic.rb b/activerecord/test/models/topic.rb index f81ffe1d90..176bc79dc7 100644 --- a/activerecord/test/models/topic.rb +++ b/activerecord/test/models/topic.rb @@ -32,7 +32,7 @@ class Topic < ActiveRecord::Base      end    end -  has_many :replies, :dependent => :destroy, :foreign_key => "parent_id" +  has_many :replies, dependent: :destroy, foreign_key: "parent_id", autosave: true    has_many :approved_replies, -> { approved }, class_name: 'Reply', foreign_key: "parent_id", counter_cache: 'replies_count'    has_many :unique_replies, :dependent => :destroy, :foreign_key => "parent_id" @@ -86,7 +86,7 @@ class Topic < ActiveRecord::Base      end      def destroy_children -      self.class.delete_all "parent_id = #{id}" +      self.class.where("parent_id = #{id}").delete_all      end      def set_email_address diff --git a/activerecord/test/models/treasure.rb b/activerecord/test/models/treasure.rb index ffc65466d5..63ff0c23ec 100644 --- a/activerecord/test/models/treasure.rb +++ b/activerecord/test/models/treasure.rb @@ -1,6 +1,7 @@  class Treasure < ActiveRecord::Base    has_and_belongs_to_many :parrots    belongs_to :looter, :polymorphic => true +  # No counter_cache option given    belongs_to :ship    has_many :price_estimates, :as => :estimate_of diff --git a/activerecord/test/models/vehicle.rb b/activerecord/test/models/vehicle.rb new file mode 100644 index 0000000000..ef26170f1f --- /dev/null +++ b/activerecord/test/models/vehicle.rb @@ -0,0 +1,7 @@ +class Vehicle < ActiveRecord::Base +  self.abstract_class = true +  default_scope -> { where("tires_count IS NOT NULL") } +end + +class Bus < Vehicle +end
\ No newline at end of file diff --git a/activerecord/test/schema/mysql2_specific_schema.rb b/activerecord/test/schema/mysql2_specific_schema.rb index 52d3290c84..92e0b197a7 100644 --- a/activerecord/test/schema/mysql2_specific_schema.rb +++ b/activerecord/test/schema/mysql2_specific_schema.rb @@ -2,7 +2,7 @@ ActiveRecord::Schema.define do    create_table :binary_fields, force: true do |t|      t.binary :var_binary, limit: 255      t.binary :var_binary_large, limit: 4095 -    t.column :tiny_blob, 'tinyblob', limit: 255 +    t.blob   :tiny_blob, limit: 255      t.binary :normal_blob, limit: 65535      t.binary :medium_blob, limit: 16777215      t.binary :long_blob, limit: 2147483647 @@ -40,6 +40,17 @@ BEGIN  END  SQL +  ActiveRecord::Base.connection.execute <<-SQL +DROP PROCEDURE IF EXISTS topics; +SQL + +  ActiveRecord::Base.connection.execute <<-SQL +CREATE PROCEDURE topics(IN num INT) SQL SECURITY INVOKER +BEGIN +  select * from topics limit num; +END +SQL +    ActiveRecord::Base.connection.drop_table "enum_tests", if_exists: true    ActiveRecord::Base.connection.execute <<-SQL diff --git a/activerecord/test/schema/mysql_specific_schema.rb b/activerecord/test/schema/mysql_specific_schema.rb index 90f5a60d7b..553cb56103 100644 --- a/activerecord/test/schema/mysql_specific_schema.rb +++ b/activerecord/test/schema/mysql_specific_schema.rb @@ -2,7 +2,7 @@ ActiveRecord::Schema.define do    create_table :binary_fields, force: true do |t|      t.binary :var_binary, limit: 255      t.binary :var_binary_large, limit: 4095 -    t.column :tiny_blob, 'tinyblob', limit: 255 +    t.blob   :tiny_blob, limit: 255      t.binary :normal_blob, limit: 65535      t.binary :medium_blob, limit: 16777215      t.binary :long_blob, limit: 2147483647 @@ -45,9 +45,9 @@ DROP PROCEDURE IF EXISTS topics;  SQL    ActiveRecord::Base.connection.execute <<-SQL -CREATE PROCEDURE topics() SQL SECURITY INVOKER +CREATE PROCEDURE topics(IN num INT) SQL SECURITY INVOKER  BEGIN -	select * from topics limit 1; +  select * from topics limit num;  END  SQL diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index 6872b49ad9..d334a2740e 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -1,4 +1,3 @@ -  ActiveRecord::Schema.define do    def except(adapter_names_to_exclude)      unless [adapter_names_to_exclude].flatten.include?(adapter_name) @@ -37,6 +36,7 @@ ActiveRecord::Schema.define do    create_table :aircraft, force: true do |t|      t.string :name +    t.integer :wheels_count, default: 0, null: false    end    create_table :articles, force: true do |t| @@ -130,6 +130,8 @@ ActiveRecord::Schema.define do      t.timestamps null: false    end +  create_table :carriers, force: true +    create_table :categories, force: true do |t|      t.string :name, null: false      t.string :type @@ -236,6 +238,11 @@ ActiveRecord::Schema.define do      t.string  :gps_location    end +  create_table :customer_carriers, force: true do |t| +    t.references :customer +    t.references :carrier +  end +    create_table :dashboards, force: true, id: false do |t|      t.string :dashboard_id      t.string :name @@ -243,11 +250,20 @@ ActiveRecord::Schema.define do    create_table :developers, force: true do |t|      t.string   :name +    t.string   :first_name      t.integer  :salary, default: 70000 -    t.datetime :created_at -    t.datetime :updated_at -    t.datetime :created_on -    t.datetime :updated_on +    t.integer :firm_id +    if subsecond_precision_supported? +      t.datetime :created_at, precision: 6 +      t.datetime :updated_at, precision: 6 +      t.datetime :created_on, precision: 6 +      t.datetime :updated_on, precision: 6 +    else +      t.datetime :created_at +      t.datetime :updated_at +      t.datetime :created_on +      t.datetime :updated_on +    end    end    create_table :developers_projects, force: true, id: false do |t| @@ -346,7 +362,11 @@ ActiveRecord::Schema.define do    create_table :invoices, force: true do |t|      t.integer :balance -    t.datetime :updated_at +    if subsecond_precision_supported? +      t.datetime :updated_at, precision: 6 +    else +      t.datetime :updated_at +    end    end    create_table :iris, force: true do |t| @@ -496,7 +516,11 @@ ActiveRecord::Schema.define do    create_table :owners, primary_key: :owner_id, force: true do |t|      t.string :name -    t.column :updated_at, :datetime +    if subsecond_precision_supported? +      t.column :updated_at, :datetime, precision: 6 +    else +      t.column :updated_at, :datetime +    end      t.column :happy_at,   :datetime      t.string :essay_id    end @@ -514,10 +538,17 @@ ActiveRecord::Schema.define do      t.column :color, :string      t.column :parrot_sti_class, :string      t.column :killer_id, :integer -    t.column :created_at, :datetime -    t.column :created_on, :datetime -    t.column :updated_at, :datetime -    t.column :updated_on, :datetime +    if subsecond_precision_supported? +      t.column :created_at, :datetime, precision: 0 +      t.column :created_on, :datetime, precision: 0 +      t.column :updated_at, :datetime, precision: 0 +      t.column :updated_on, :datetime, precision: 0 +    else +      t.column :created_at, :datetime +      t.column :created_on, :datetime +      t.column :updated_at, :datetime +      t.column :updated_on, :datetime +    end    end    create_table :parrots_pirates, id: false, force: true do |t| @@ -560,15 +591,24 @@ ActiveRecord::Schema.define do    create_table :pets, primary_key: :pet_id, force: true do |t|      t.string :name      t.integer :owner_id, :integer -    t.timestamps null: false +    if subsecond_precision_supported? +      t.timestamps null: false, precision: 6 +    else +      t.timestamps null: false +    end    end    create_table :pirates, force: true do |t|      t.column :catchphrase, :string      t.column :parrot_id, :integer      t.integer :non_validated_parrot_id -    t.column :created_on, :datetime -    t.column :updated_on, :datetime +    if subsecond_precision_supported? +      t.column :created_on, :datetime, precision: 6 +      t.column :updated_on, :datetime, precision: 6 +    else +      t.column :created_on, :datetime +      t.column :updated_on, :datetime +    end    end    create_table :posts, force: true do |t| @@ -619,6 +659,7 @@ ActiveRecord::Schema.define do    create_table :projects, force: true do |t|      t.string :name      t.string :type +    t.integer :firm_id    end    create_table :randomly_named_table1, force: true do |t| @@ -665,7 +706,10 @@ ActiveRecord::Schema.define do    create_table :ships, force: true do |t|      t.string :name      t.integer :pirate_id +    t.belongs_to :developer      t.integer :update_only_pirate_id +    # Conventionally named column for counter_cache +    t.integer :treasures_count, default: 0      t.datetime :created_at      t.datetime :created_on      t.datetime :updated_at @@ -675,7 +719,20 @@ ActiveRecord::Schema.define do    create_table :ship_parts, force: true do |t|      t.string :name      t.integer :ship_id -    t.datetime :updated_at +    if subsecond_precision_supported? +      t.datetime :updated_at, precision: 6 +    else +      t.datetime :updated_at +    end +  end + +  create_table :prisoners, force: true do |t| +    t.belongs_to :ship +  end + +  create_table :shop_accounts, force: true do |t| +    t.references :customer +    t.references :customer_carrier    end    create_table :speedometers, force: true, id: false do |t| @@ -736,7 +793,7 @@ ActiveRecord::Schema.define do      t.string   :title, limit: 250      t.string   :author_name      t.string   :author_email_address -    if mysql_56? +    if subsecond_precision_supported?        t.datetime :written_on, precision: 6      else        t.datetime :written_on @@ -759,7 +816,11 @@ ActiveRecord::Schema.define do      t.string   :parent_title      t.string   :type      t.string   :group -    t.timestamps null: true +    if subsecond_precision_supported? +      t.timestamps null: true, precision: 6 +    else +      t.timestamps null: true +    end    end    create_table :toys, primary_key: :toy_id, force: true do |t| @@ -924,6 +985,10 @@ ActiveRecord::Schema.define do      t.string :token      t.string :auth_token    end + +  create_table :test_with_keyword_column_name, force: true do |t| +    t.string :desc +  end  end  Course.connection.create_table :courses, force: true do |t| @@ -934,3 +999,12 @@ end  College.connection.create_table :colleges, force: true do |t|    t.column :name, :string, null: false  end + +Professor.connection.create_table :professors, force: true do |t| +  t.column :name, :string, null: false +end + +Professor.connection.create_table :courses_professors, id: false, force: true do |t| +  t.references :course +  t.references :professor +end diff --git a/activerecord/test/support/connection.rb b/activerecord/test/support/connection.rb index d11fd9cfc1..c5334e8596 100644 --- a/activerecord/test/support/connection.rb +++ b/activerecord/test/support/connection.rb @@ -1,6 +1,7 @@  require 'active_support/logger'  require 'models/college'  require 'models/course' +require 'models/professor'  module ARTest    def self.connection_name  | 
