diff options
Diffstat (limited to 'activerecord/test')
116 files changed, 3127 insertions, 1226 deletions
diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb index 595edc6263..0eb1231c79 100644 --- a/activerecord/test/cases/adapter_test.rb +++ b/activerecord/test/cases/adapter_test.rb @@ -1,5 +1,7 @@ require "cases/helper" require "models/book" +require "models/post" +require "models/author" module ActiveRecord class AdapterTest < ActiveRecord::TestCase @@ -92,7 +94,7 @@ module ActiveRecord ) end ensure - ActiveRecord::Base.establish_connection 'arunit' + ActiveRecord::Base.establish_connection :arunit end end end @@ -178,6 +180,31 @@ module ActiveRecord result = @connection.select_all "SELECT * FROM posts" assert result.is_a?(ActiveRecord::Result) end + + def test_select_methods_passing_a_association_relation + author = Author.create!(name: 'john') + Post.create!(author: author, title: 'foo', body: 'bar') + query = author.posts.select(:title) + assert_equal({"title" => "foo"}, @connection.select_one(query.arel, nil, query.bind_values)) + assert_equal({"title" => "foo"}, @connection.select_one(query)) + assert @connection.select_all(query).is_a?(ActiveRecord::Result) + assert_equal "foo", @connection.select_value(query) + assert_equal ["foo"], @connection.select_values(query) + end + + def test_select_methods_passing_a_relation + Post.create!(title: 'foo', body: 'bar') + query = Post.where(title: 'foo').select(:title) + assert_equal({"title" => "foo"}, @connection.select_one(query.arel, nil, query.bind_values)) + assert_equal({"title" => "foo"}, @connection.select_one(query)) + assert @connection.select_all(query).is_a?(ActiveRecord::Result) + assert_equal "foo", @connection.select_value(query) + assert_equal ["foo"], @connection.select_values(query) + end + + test "type_to_sql returns a String for unmapped types" do + assert_equal "special_db_type", @connection.type_to_sql(:special_db_type) + end end class AdapterTestWithoutTransaction < ActiveRecord::TestCase @@ -187,7 +214,7 @@ module ActiveRecord end def setup - Klass.establish_connection 'arunit' + Klass.establish_connection :arunit @connection = Klass.connection end diff --git a/activerecord/test/cases/adapters/firebird/connection_test.rb b/activerecord/test/cases/adapters/firebird/connection_test.rb deleted file mode 100644 index f57ea686a5..0000000000 --- a/activerecord/test/cases/adapters/firebird/connection_test.rb +++ /dev/null @@ -1,8 +0,0 @@ -require "cases/helper" - -class FirebirdConnectionTest < ActiveRecord::TestCase - def test_charset_properly_set - fb_conn = ActiveRecord::Base.connection.instance_variable_get(:@connection) - assert_equal 'UTF8', fb_conn.database.character_set - end -end diff --git a/activerecord/test/cases/adapters/firebird/default_test.rb b/activerecord/test/cases/adapters/firebird/default_test.rb deleted file mode 100644 index 713c7e11bf..0000000000 --- a/activerecord/test/cases/adapters/firebird/default_test.rb +++ /dev/null @@ -1,16 +0,0 @@ -require "cases/helper" -require 'models/default' - -class DefaultTest < ActiveRecord::TestCase - def test_default_timestamp - default = Default.new - assert_instance_of(Time, default.default_timestamp) - assert_equal(:datetime, default.column_for_attribute(:default_timestamp).type) - - # Variance should be small; increase if required -- e.g., if test db is on - # remote host and clocks aren't synchronized. - t1 = Time.new - accepted_variance = 1.0 - assert_in_delta(t1.to_f, default.default_timestamp.to_f, accepted_variance) - end -end diff --git a/activerecord/test/cases/adapters/firebird/migration_test.rb b/activerecord/test/cases/adapters/firebird/migration_test.rb deleted file mode 100644 index 5c94593765..0000000000 --- a/activerecord/test/cases/adapters/firebird/migration_test.rb +++ /dev/null @@ -1,124 +0,0 @@ -require "cases/helper" -require 'models/course' - -class FirebirdMigrationTest < ActiveRecord::TestCase - self.use_transactional_fixtures = false - - def setup - # using Course connection for tests -- need a db that doesn't already have a BOOLEAN domain - @connection = Course.connection - @fireruby_connection = @connection.instance_variable_get(:@connection) - end - - def teardown - @connection.drop_table :foo rescue nil - @connection.execute("DROP DOMAIN D_BOOLEAN") rescue nil - end - - def test_create_table_with_custom_sequence_name - assert_nothing_raised do - @connection.create_table(:foo, :sequence => 'foo_custom_seq') do |f| - f.column :bar, :string - end - end - assert !sequence_exists?('foo_seq') - assert sequence_exists?('foo_custom_seq') - - assert_nothing_raised { @connection.drop_table(:foo) } - assert !sequence_exists?('foo_custom_seq') - ensure - FireRuby::Generator.new('foo_custom_seq', @fireruby_connection).drop rescue nil - end - - def test_create_table_without_sequence - assert_nothing_raised do - @connection.create_table(:foo, :sequence => false) do |f| - f.column :bar, :string - end - end - assert !sequence_exists?('foo_seq') - assert_nothing_raised { @connection.drop_table :foo } - - assert_nothing_raised do - @connection.create_table(:foo, :id => false) do |f| - f.column :bar, :string - end - end - assert !sequence_exists?('foo_seq') - assert_nothing_raised { @connection.drop_table :foo } - end - - def test_create_table_with_boolean_column - assert !boolean_domain_exists? - assert_nothing_raised do - @connection.create_table :foo do |f| - f.column :bar, :string - f.column :baz, :boolean - end - end - assert boolean_domain_exists? - end - - def test_add_boolean_column - assert !boolean_domain_exists? - @connection.create_table :foo do |f| - f.column :bar, :string - end - - assert_nothing_raised { @connection.add_column :foo, :baz, :boolean } - assert boolean_domain_exists? - assert_equal :boolean, @connection.columns(:foo).find { |c| c.name == "baz" }.type - end - - def test_change_column_to_boolean - assert !boolean_domain_exists? - # Manually create table with a SMALLINT column, which can be changed to a BOOLEAN - @connection.execute "CREATE TABLE foo (bar SMALLINT)" - assert_equal :integer, @connection.columns(:foo).find { |c| c.name == "bar" }.type - - assert_nothing_raised { @connection.change_column :foo, :bar, :boolean } - assert boolean_domain_exists? - assert_equal :boolean, @connection.columns(:foo).find { |c| c.name == "bar" }.type - end - - def test_rename_table_with_data_and_index - @connection.create_table :foo do |f| - f.column :baz, :string, :limit => 50 - end - 100.times { |i| @connection.execute "INSERT INTO foo VALUES (GEN_ID(foo_seq, 1), 'record #{i+1}')" } - @connection.add_index :foo, :baz - - assert_nothing_raised { @connection.rename_table :foo, :bar } - assert !@connection.tables.include?("foo") - assert @connection.tables.include?("bar") - assert_equal "index_bar_on_baz", @connection.indexes("bar").first.name - assert_equal 100, FireRuby::Generator.new("bar_seq", @fireruby_connection).last - assert_equal 100, @connection.select_one("SELECT COUNT(*) FROM bar")["count"] - ensure - @connection.drop_table :bar rescue nil - end - - def test_renaming_table_with_fk_constraint_raises_error - @connection.create_table :parent do |p| - p.column :name, :string - end - @connection.create_table :child do |c| - c.column :parent_id, :integer - end - @connection.execute "ALTER TABLE child ADD CONSTRAINT fk_child_parent FOREIGN KEY(parent_id) REFERENCES parent(id)" - assert_raise(ActiveRecord::ActiveRecordError) { @connection.rename_table :child, :descendant } - ensure - @connection.drop_table :child rescue nil - @connection.drop_table :descendant rescue nil - @connection.drop_table :parent rescue nil - end - - private - def boolean_domain_exists? - !@connection.select_one("SELECT 1 FROM rdb$fields WHERE rdb$field_name = 'D_BOOLEAN'").nil? - end - - def sequence_exists?(sequence_name) - FireRuby::Generator.exists?(sequence_name, @fireruby_connection) - end -end diff --git a/activerecord/test/cases/adapters/mysql/connection_test.rb b/activerecord/test/cases/adapters/mysql/connection_test.rb index a1b41b6991..5cd5d8ac5f 100644 --- a/activerecord/test/cases/adapters/mysql/connection_test.rb +++ b/activerecord/test/cases/adapters/mysql/connection_test.rb @@ -71,7 +71,7 @@ class MysqlConnectionTest < ActiveRecord::TestCase def test_exec_no_binds @connection.exec_query('drop table if exists ex') @connection.exec_query(<<-eosql) - CREATE TABLE `ex` (`id` int(11) DEFAULT NULL auto_increment PRIMARY KEY, + CREATE TABLE `ex` (`id` int(11) auto_increment PRIMARY KEY, `data` varchar(255)) eosql result = @connection.exec_query('SELECT id, data FROM ex') @@ -93,7 +93,7 @@ class MysqlConnectionTest < ActiveRecord::TestCase def test_exec_with_binds @connection.exec_query('drop table if exists ex') @connection.exec_query(<<-eosql) - CREATE TABLE `ex` (`id` int(11) DEFAULT NULL auto_increment PRIMARY KEY, + CREATE TABLE `ex` (`id` int(11) auto_increment PRIMARY KEY, `data` varchar(255)) eosql @connection.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")') @@ -109,7 +109,7 @@ class MysqlConnectionTest < ActiveRecord::TestCase def test_exec_typecasts_bind_vals @connection.exec_query('drop table if exists ex') @connection.exec_query(<<-eosql) - CREATE TABLE `ex` (`id` int(11) DEFAULT NULL auto_increment PRIMARY KEY, + CREATE TABLE `ex` (`id` int(11) auto_increment PRIMARY KEY, `data` varchar(255)) eosql @connection.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")') diff --git a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb index 9ad0744aee..578f6301bd 100644 --- a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb +++ b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb @@ -10,12 +10,20 @@ module ActiveRecord @conn.exec_query('drop table if exists ex') @conn.exec_query(<<-eosql) CREATE TABLE `ex` ( - `id` int(11) DEFAULT NULL auto_increment PRIMARY KEY, + `id` int(11) auto_increment PRIMARY KEY, `number` integer, `data` varchar(255)) eosql end + def test_bad_connection_mysql + assert_raise ActiveRecord::NoDatabaseError do + configuration = ActiveRecord::Base.configurations['arunit'].merge(database: 'inexistent_activerecord_unittest') + connection = ActiveRecord::Base.mysql_connection(configuration) + connection.exec_query('drop table if exists ex') + end + end + def test_valid_column column = @conn.columns('ex').find { |col| col.name == 'id' } assert @conn.valid_type?(column.type) @@ -75,7 +83,7 @@ module ActiveRecord @conn.exec_query('drop table if exists ex_with_non_standard_pk') @conn.exec_query(<<-eosql) CREATE TABLE `ex_with_non_standard_pk` ( - `code` INT(11) DEFAULT NULL auto_increment, + `code` INT(11) auto_increment, PRIMARY KEY (`code`)) eosql pk, seq = @conn.pk_and_sequence_for('ex_with_non_standard_pk') @@ -87,7 +95,7 @@ module ActiveRecord @conn.exec_query('drop table if exists ex_with_custom_index_type_pk') @conn.exec_query(<<-eosql) CREATE TABLE `ex_with_custom_index_type_pk` ( - `id` INT(11) DEFAULT NULL auto_increment, + `id` INT(11) auto_increment, PRIMARY KEY USING BTREE (`id`)) eosql pk, seq = @conn.pk_and_sequence_for('ex_with_custom_index_type_pk') diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb index 8dc1df1851..9b7202c915 100644 --- a/activerecord/test/cases/adapters/mysql2/connection_test.rb +++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb @@ -13,6 +13,14 @@ class MysqlConnectionTest < ActiveRecord::TestCase super end + def test_bad_connection + assert_raise ActiveRecord::NoDatabaseError do + configuration = ActiveRecord::Base.configurations['arunit'].merge(database: 'inexistent_activerecord_unittest') + connection = ActiveRecord::Base.mysql2_connection(configuration) + connection.exec_query('drop table if exists ex') + end + end + def test_no_automatic_reconnection_after_timeout assert @connection.active? @connection.update('set @@wait_timeout=1') diff --git a/activerecord/test/cases/adapters/mysql2/explain_test.rb b/activerecord/test/cases/adapters/mysql2/explain_test.rb index 68ed361aeb..1cd356e868 100644 --- a/activerecord/test/cases/adapters/mysql2/explain_test.rb +++ b/activerecord/test/cases/adapters/mysql2/explain_test.rb @@ -10,15 +10,15 @@ module ActiveRecord 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 %(developers | const), 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 %(developers | const), explain + assert_match %r(developers |.* const), explain assert_match %(EXPLAIN for: SELECT `audit_logs`.* FROM `audit_logs` WHERE `audit_logs`.`developer_id` IN (1)), explain - assert_match %(audit_logs | ALL), explain + assert_match %r(audit_logs |.* ALL), explain end end end diff --git a/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb b/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb index 9ecd901eac..ec73ec35aa 100644 --- a/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb +++ b/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb @@ -4,22 +4,35 @@ module ActiveRecord module ConnectionAdapters class Mysql2Adapter class SchemaMigrationsTest < ActiveRecord::TestCase - def test_initializes_schema_migrations_for_encoding_utf8mb4 - conn = ActiveRecord::Base.connection + def test_renaming_index_on_foreign_key + connection.add_index "engines", "car_id" + connection.execute "ALTER TABLE engines ADD CONSTRAINT fk_engines_cars FOREIGN KEY (car_id) REFERENCES cars(id)" + + connection.rename_index("engines", "index_engines_on_car_id", "idx_renamed") + assert_equal ["idx_renamed"], connection.indexes("engines").map(&:name) + ensure + connection.execute "ALTER TABLE engines DROP FOREIGN KEY fk_engines_cars" + end + def test_initializes_schema_migrations_for_encoding_utf8mb4 smtn = ActiveRecord::Migrator.schema_migrations_table_name - conn.drop_table(smtn) if conn.table_exists?(smtn) + connection.drop_table(smtn) if connection.table_exists?(smtn) - config = conn.instance_variable_get(:@config) + config = connection.instance_variable_get(:@config) original_encoding = config[:encoding] config[:encoding] = 'utf8mb4' - conn.initialize_schema_migrations_table + connection.initialize_schema_migrations_table - assert conn.column_exists?(smtn, :version, :string, limit: Mysql2Adapter::MAX_INDEX_LENGTH_FOR_UTF8MB4) + assert connection.column_exists?(smtn, :version, :string, limit: Mysql2Adapter::MAX_INDEX_LENGTH_FOR_UTF8MB4) ensure config[:encoding] = original_encoding end + + private + def connection + @connection ||= ActiveRecord::Base.connection + end end end end diff --git a/activerecord/test/cases/adapters/mysql2/schema_test.rb b/activerecord/test/cases/adapters/mysql2/schema_test.rb index 5db60ff8a0..43c9116b5a 100644 --- a/activerecord/test/cases/adapters/mysql2/schema_test.rb +++ b/activerecord/test/cases/adapters/mysql2/schema_test.rb @@ -65,6 +65,15 @@ module ActiveRecord assert_nil index_c.using assert_equal :fulltext, index_c.type end + + def test_drop_temporary_table + @connection.transaction do + @connection.create_table(:temp_table, temporary: true) + # if it doesn't properly say DROP TEMPORARY TABLE, the transaction commit + # will complain that no transaction is active + @connection.drop_table(:temp_table, temporary: true) + end + end end end end diff --git a/activerecord/test/cases/adapters/oracle/synonym_test.rb b/activerecord/test/cases/adapters/oracle/synonym_test.rb deleted file mode 100644 index b9a422a6ca..0000000000 --- a/activerecord/test/cases/adapters/oracle/synonym_test.rb +++ /dev/null @@ -1,17 +0,0 @@ -require "cases/helper" -require 'models/topic' -require 'models/subject' - -# confirm that synonyms work just like tables; in this case -# the "subjects" table in Oracle (defined in oci.sql) is just -# a synonym to the "topics" table - -class TestOracleSynonym < ActiveRecord::TestCase - - def test_oracle_synonym - topic = Topic.new - subject = Subject.new - assert_equal(topic.attributes, subject.attributes) - end - -end diff --git a/activerecord/test/cases/adapters/postgresql/array_test.rb b/activerecord/test/cases/adapters/postgresql/array_test.rb index 9536cceb1d..3090f4478f 100644 --- a/activerecord/test/cases/adapters/postgresql/array_test.rb +++ b/activerecord/test/cases/adapters/postgresql/array_test.rb @@ -10,12 +10,12 @@ class PostgresqlArrayTest < ActiveRecord::TestCase def setup @connection = ActiveRecord::Base.connection - @connection.transaction do - @connection.create_table('pg_arrays') do |t| - t.string 'tags', array: true - t.integer 'ratings', array: true - end + @connection.transaction do + @connection.create_table('pg_arrays') do |t| + t.string 'tags', array: true + t.integer 'ratings', array: true end + end @column = PgArray.columns.find { |c| c.name == 'tags' } end @@ -26,6 +26,12 @@ class PostgresqlArrayTest < ActiveRecord::TestCase def test_column assert_equal :string, @column.type assert @column.array + assert_not @column.text? + + ratings_column = PgArray.columns_hash['ratings'] + assert_equal :integer, ratings_column.type + assert ratings_column.array + assert_not ratings_column.number? end def test_change_column_with_array @@ -50,8 +56,6 @@ class PostgresqlArrayTest < ActiveRecord::TestCase end def test_type_cast_array - assert @column - data = '{1,2,3}' oid_type = @column.instance_variable_get('@oid_type').subtype # we are getting the instance variable in this test, but in the @@ -66,6 +70,12 @@ class PostgresqlArrayTest < ActiveRecord::TestCase assert_equal([nil], @column.type_cast('{NULL}')) end + def test_type_cast_integers + x = PgArray.new(ratings: ['1', '2']) + assert x.save! + assert_equal(['1', '2'], x.ratings) + end + def test_rewrite @connection.execute "insert into pg_arrays (tags) VALUES ('{1,2,3}')" x = PgArray.first @@ -83,6 +93,18 @@ class PostgresqlArrayTest < ActiveRecord::TestCase assert_cycle(:tags, [[['1'], ['2']], [['2'], ['3']]]) end + def test_with_empty_strings + assert_cycle(:tags, [ '1', '2', '', '4', '', '5' ]) + end + + def test_with_multi_dimensional_empty_strings + assert_cycle(:tags, [[['1', '2'], ['', '4'], ['', '5']]]) + end + + def test_with_arbitrary_whitespace + assert_cycle(:tags, [[['1', '2'], [' ', '4'], [' ', '5']]]) + end + def test_multi_dimensional_with_integers assert_cycle(:ratings, [[[1], [7]], [[8], [10]]]) end @@ -118,6 +140,16 @@ class PostgresqlArrayTest < ActiveRecord::TestCase assert_equal("[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ...]", record.attribute_for_inspect(:ratings)) end + def test_update_all + pg_array = PgArray.create! tags: ["one", "two", "three"] + + PgArray.update_all tags: ["four", "five"] + assert_equal ["four", "five"], pg_array.reload.tags + + PgArray.update_all tags: [] + assert_equal [], pg_array.reload.tags + end + private def assert_cycle field, array # test creation diff --git a/activerecord/test/cases/adapters/postgresql/citext_test.rb b/activerecord/test/cases/adapters/postgresql/citext_test.rb new file mode 100644 index 0000000000..ebb9ff98b1 --- /dev/null +++ b/activerecord/test/cases/adapters/postgresql/citext_test.rb @@ -0,0 +1,77 @@ +# encoding: utf-8 + +require 'cases/helper' +require 'active_record/base' +require 'active_record/connection_adapters/postgresql_adapter' + +class PostgresqlCitextTest < ActiveRecord::TestCase + class Citext < ActiveRecord::Base + self.table_name = 'citexts' + end + + def setup + @connection = ActiveRecord::Base.connection + + unless @connection.extension_enabled?('citext') + @connection.enable_extension 'citext' + @connection.commit_db_transaction + end + + @connection.reconnect! + + @connection.create_table('citexts') do |t| + t.citext 'cival' + end + @column = Citext.columns_hash['cival'] + end + + def teardown + @connection.execute 'DROP TABLE IF EXISTS citexts;' + @connection.execute 'DROP EXTENSION IF EXISTS citext CASCADE;' + end + + def test_citext_enabled + assert @connection.extension_enabled?('citext') + end + + def test_column_type + assert_equal :citext, @column.type + end + + def test_column_sql_type + assert_equal 'citext', @column.sql_type + end + + def test_change_table_supports_json + @connection.transaction do + @connection.change_table('citexts') do |t| + t.citext 'username' + end + Citext.reset_column_information + column = Citext.columns.find { |c| c.name == 'username' } + assert_equal :citext, column.type + + raise ActiveRecord::Rollback # reset the schema change + end + ensure + Citext.reset_column_information + end + + def test_write + x = Citext.new(cival: 'Some CI Text') + x.save! + citext = Citext.first + assert_equal "Some CI Text", citext.cival + + citext.cival = "Some NEW CI Text" + citext.save! + + assert_equal "Some NEW CI Text", citext.reload.cival + end + + def test_select_case_insensitive + @connection.execute "insert into citexts (cival) values('Cased Text')" + x = Citext.where(cival: 'cased text').first + assert_equal 'Cased Text', x.cival + end +end diff --git a/activerecord/test/cases/adapters/postgresql/composite_test.rb b/activerecord/test/cases/adapters/postgresql/composite_test.rb new file mode 100644 index 0000000000..7202cce390 --- /dev/null +++ b/activerecord/test/cases/adapters/postgresql/composite_test.rb @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +require "cases/helper" +require 'active_record/base' +require 'active_record/connection_adapters/postgresql_adapter' + +class PostgresqlCompositeTest < ActiveRecord::TestCase + class PostgresqlComposite < ActiveRecord::Base + self.table_name = "postgresql_composites" + end + + def teardown + @connection.execute 'DROP TABLE IF EXISTS postgresql_composites' + @connection.execute 'DROP TYPE IF EXISTS full_address' + end + + def setup + @connection = ActiveRecord::Base.connection + @connection.transaction do + @connection.execute <<-SQL + CREATE TYPE full_address AS + ( + city VARCHAR(90), + street VARCHAR(90) + ); + SQL + @connection.create_table('postgresql_composites') do |t| + t.column :address, :full_address + end + end + end + + def test_composite_mapping + @connection.execute "INSERT INTO postgresql_composites VALUES (1, ROW('Paris', 'Champs-Élysées'));" + composite = PostgresqlComposite.first + assert_equal "(Paris,Champs-Élysées)", composite.address + + composite.address = "(Paris,Rue Basse)" + composite.save! + + assert_equal '(Paris,"Rue Basse")', composite.reload.address + end +end diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb index 90cca7d3e6..4715fa002d 100644 --- a/activerecord/test/cases/adapters/postgresql/connection_test.rb +++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb @@ -91,40 +91,50 @@ module ActiveRecord assert_operator plan.length, :>, 0 end - # Must have with_manual_interventions set to true for this - # test to run. + # Must have PostgreSQL >= 9.2, or with_manual_interventions set to + # true for this test to run. + # # When prompted, restart the PostgreSQL server with the # "-m fast" option or kill the individual connection assuming # you know the incantation to do that. # To restart PostgreSQL 9.1 on OS X, installed via MacPorts, ... # sudo su postgres -c "pg_ctl restart -D /opt/local/var/db/postgresql91/defaultdb/ -m fast" - if ARTest.config['with_manual_interventions'] - def test_reconnection_after_actual_disconnection_with_verify - original_connection_pid = @connection.query('select pg_backend_pid()') + def test_reconnection_after_actual_disconnection_with_verify + original_connection_pid = @connection.query('select pg_backend_pid()') - # Sanity check. - assert @connection.active? + # Sanity check. + assert @connection.active? + if @connection.send(:postgresql_version) >= 90200 + secondary_connection = ActiveRecord::Base.connection_pool.checkout + secondary_connection.query("select pg_terminate_backend(#{original_connection_pid.first.first})") + ActiveRecord::Base.connection_pool.checkin(secondary_connection) + elsif ARTest.config['with_manual_interventions'] puts 'Kill the connection now (e.g. by restarting the PostgreSQL ' + 'server with the "-m fast" option) and then press enter.' $stdin.gets + else + # We're not capable of terminating the backend ourselves, and + # we're not allowed to seek assistance; bail out without + # actually testing anything. + return + end - @connection.verify! + @connection.verify! - assert @connection.active? + assert @connection.active? - # If we get no exception here, then either we re-connected successfully, or - # we never actually got disconnected. - new_connection_pid = @connection.query('select pg_backend_pid()') + # If we get no exception here, then either we re-connected successfully, or + # we never actually got disconnected. + new_connection_pid = @connection.query('select pg_backend_pid()') - assert_not_equal original_connection_pid, new_connection_pid, - "umm -- looks like you didn't break the connection, because we're still " + - "successfully querying with the same connection pid." + assert_not_equal original_connection_pid, new_connection_pid, + "umm -- looks like you didn't break the connection, because we're still " + + "successfully querying with the same connection pid." - # Repair all fixture connections so other tests won't break. - @fixture_connections.each do |c| - c.verify! - end + # Repair all fixture connections so other tests won't break. + @fixture_connections.each do |c| + c.verify! end end diff --git a/activerecord/test/cases/adapters/postgresql/datatype_test.rb b/activerecord/test/cases/adapters/postgresql/datatype_test.rb index 01de202d11..158bc6ee89 100644 --- a/activerecord/test/cases/adapters/postgresql/datatype_test.rb +++ b/activerecord/test/cases/adapters/postgresql/datatype_test.rb @@ -3,9 +3,6 @@ require "cases/helper" class PostgresqlArray < ActiveRecord::Base end -class PostgresqlRange < ActiveRecord::Base -end - class PostgresqlTsvector < ActiveRecord::Base end @@ -30,9 +27,6 @@ end class PostgresqlTimestampWithZone < ActiveRecord::Base end -class PostgresqlUUID < ActiveRecord::Base -end - class PostgresqlLtree < ActiveRecord::Base end @@ -46,104 +40,6 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase @connection.execute("INSERT INTO postgresql_arrays (id, commission_by_quarter, nicknames) VALUES (1, '{35000,21000,18000,17000}', '{foo,bar,baz}')") @first_array = PostgresqlArray.find(1) - @connection.execute <<_SQL if @connection.supports_ranges? - INSERT INTO postgresql_ranges ( - date_range, - num_range, - ts_range, - tstz_range, - int4_range, - int8_range - ) VALUES ( - '[''2012-01-02'', ''2012-01-04'']', - '[0.1, 0.2]', - '[''2010-01-01 14:30'', ''2011-01-01 14:30'']', - '[''2010-01-01 14:30:00+05'', ''2011-01-01 14:30:00-03'']', - '[1, 10]', - '[10, 100]' - ) -_SQL - - @connection.execute <<_SQL if @connection.supports_ranges? - INSERT INTO postgresql_ranges ( - date_range, - num_range, - ts_range, - tstz_range, - int4_range, - int8_range - ) VALUES ( - '(''2012-01-02'', ''2012-01-04'')', - '[0.1, 0.2)', - '[''2010-01-01 14:30'', ''2011-01-01 14:30'')', - '[''2010-01-01 14:30:00+05'', ''2011-01-01 14:30:00-03'')', - '(1, 10)', - '(10, 100)' - ) -_SQL - - @connection.execute <<_SQL if @connection.supports_ranges? - INSERT INTO postgresql_ranges ( - date_range, - num_range, - ts_range, - tstz_range, - int4_range, - int8_range - ) VALUES ( - '(''2012-01-02'',]', - '[0.1,]', - '[''2010-01-01 14:30'',]', - '[''2010-01-01 14:30:00+05'',]', - '(1,]', - '(10,]' - ) -_SQL - - @connection.execute <<_SQL if @connection.supports_ranges? - INSERT INTO postgresql_ranges ( - date_range, - num_range, - ts_range, - tstz_range, - int4_range, - int8_range - ) VALUES ( - '[,]', - '[,]', - '[,]', - '[,]', - '[,]', - '[,]' - ) -_SQL - - @connection.execute <<_SQL if @connection.supports_ranges? - INSERT INTO postgresql_ranges ( - date_range, - num_range, - ts_range, - tstz_range, - int4_range, - int8_range - ) VALUES ( - '(''2012-01-02'', ''2012-01-02'')', - '(0.1, 0.1)', - '(''2010-01-01 14:30'', ''2010-01-01 14:30'')', - '(''2010-01-01 14:30:00+05'', ''2010-01-01 06:30:00-03'')', - '(1, 1)', - '(10, 10)' - ) -_SQL - - if @connection.supports_ranges? - @first_range = PostgresqlRange.find(1) - @second_range = PostgresqlRange.find(2) - @third_range = PostgresqlRange.find(3) - @fourth_range = PostgresqlRange.find(4) - @empty_range = PostgresqlRange.find(5) - end - @connection.execute("INSERT INTO postgresql_tsvectors (id, text_vector) VALUES (1, ' ''text'' ''vector'' ')") @first_tsvector = PostgresqlTsvector.find(1) @@ -169,14 +65,19 @@ _SQL @first_oid = PostgresqlOid.find(1) @connection.execute("INSERT INTO postgresql_timestamp_with_zones (id, time) VALUES (1, '2010-01-01 10:00:00-1')") - - @connection.execute("INSERT INTO postgresql_uuids (id, guid, compact_guid) VALUES(1, 'd96c3da0-96c1-012f-1316-64ce8f32c6d8', 'f06c715096c1012f131764ce8f32c6d8')") - @first_uuid = PostgresqlUUID.find(1) end def teardown [PostgresqlArray, PostgresqlTsvector, PostgresqlMoney, PostgresqlNumber, PostgresqlTime, PostgresqlNetworkAddress, - PostgresqlBitString, PostgresqlOid, PostgresqlTimestampWithZone, PostgresqlUUID].each(&:delete_all) + PostgresqlBitString, PostgresqlOid, PostgresqlTimestampWithZone].each(&:delete_all) + end + + def test_array_escaping + unknown = %(foo\\",bar,baz,\\) + nicknames = ["hello_#{unknown}"] + ar = PostgresqlArray.create!(nicknames: nicknames, id: 100) + ar.reload + assert_equal nicknames, ar.nicknames end def test_data_type_of_array_types @@ -217,10 +118,6 @@ _SQL assert_equal :integer, @first_oid.column_for_attribute(:obj_id).type end - def test_data_type_of_uuid_types - assert_equal :uuid, @first_uuid.column_for_attribute(:guid).type - end - def test_array_values assert_equal [35000,21000,18000,17000], @first_array.commission_by_quarter assert_equal ['foo','bar','baz'], @first_array.nicknames @@ -230,187 +127,6 @@ _SQL assert_equal "'text' 'vector'", @first_tsvector.text_vector end - if ActiveRecord::Base.connection.supports_ranges? - def test_data_type_of_range_types - assert_equal :daterange, @first_range.column_for_attribute(:date_range).type - assert_equal :numrange, @first_range.column_for_attribute(:num_range).type - assert_equal :tsrange, @first_range.column_for_attribute(:ts_range).type - assert_equal :tstzrange, @first_range.column_for_attribute(:tstz_range).type - assert_equal :int4range, @first_range.column_for_attribute(:int4_range).type - assert_equal :int8range, @first_range.column_for_attribute(:int8_range).type - end - - def test_int4range_values - assert_equal 1...11, @first_range.int4_range - assert_equal 2...10, @second_range.int4_range - assert_equal 2...Float::INFINITY, @third_range.int4_range - assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.int4_range) - assert_nil @empty_range.int4_range - end - - def test_int8range_values - assert_equal 10...101, @first_range.int8_range - assert_equal 11...100, @second_range.int8_range - assert_equal 11...Float::INFINITY, @third_range.int8_range - assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.int8_range) - assert_nil @empty_range.int8_range - end - - def test_daterange_values - assert_equal Date.new(2012, 1, 2)...Date.new(2012, 1, 5), @first_range.date_range - assert_equal Date.new(2012, 1, 3)...Date.new(2012, 1, 4), @second_range.date_range - assert_equal Date.new(2012, 1, 3)...Float::INFINITY, @third_range.date_range - assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.date_range) - assert_nil @empty_range.date_range - end - - def test_numrange_values - assert_equal BigDecimal.new('0.1')..BigDecimal.new('0.2'), @first_range.num_range - assert_equal BigDecimal.new('0.1')...BigDecimal.new('0.2'), @second_range.num_range - assert_equal BigDecimal.new('0.1')...BigDecimal.new('Infinity'), @third_range.num_range - assert_equal BigDecimal.new('-Infinity')...BigDecimal.new('Infinity'), @fourth_range.num_range - assert_nil @empty_range.num_range - end - - def test_tsrange_values - tz = ::ActiveRecord::Base.default_timezone - assert_equal Time.send(tz, 2010, 1, 1, 14, 30, 0)..Time.send(tz, 2011, 1, 1, 14, 30, 0), @first_range.ts_range - assert_equal Time.send(tz, 2010, 1, 1, 14, 30, 0)...Time.send(tz, 2011, 1, 1, 14, 30, 0), @second_range.ts_range - assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.ts_range) - assert_nil @empty_range.ts_range - end - - def test_tstzrange_values - assert_equal Time.parse('2010-01-01 09:30:00 UTC')..Time.parse('2011-01-01 17:30:00 UTC'), @first_range.tstz_range - assert_equal Time.parse('2010-01-01 09:30:00 UTC')...Time.parse('2011-01-01 17:30:00 UTC'), @second_range.tstz_range - assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.tstz_range) - assert_nil @empty_range.tstz_range - end - - def test_create_tstzrange - tstzrange = Time.parse('2010-01-01 14:30:00 +0100')...Time.parse('2011-02-02 14:30:00 CDT') - range = PostgresqlRange.new(:tstz_range => tstzrange) - assert range.save - assert range.reload - assert_equal range.tstz_range, tstzrange - assert_equal range.tstz_range, Time.parse('2010-01-01 13:30:00 UTC')...Time.parse('2011-02-02 19:30:00 UTC') - end - - def test_update_tstzrange - new_tstzrange = Time.parse('2010-01-01 14:30:00 CDT')...Time.parse('2011-02-02 14:30:00 CET') - @first_range.tstz_range = new_tstzrange - assert @first_range.save - assert @first_range.reload - assert_equal new_tstzrange, @first_range.tstz_range - @first_range.tstz_range = Time.parse('2010-01-01 14:30:00 +0100')...Time.parse('2010-01-01 13:30:00 +0000') - assert @first_range.save - assert @first_range.reload - assert_nil @first_range.tstz_range - end - - def test_create_tsrange - tz = ::ActiveRecord::Base.default_timezone - tsrange = Time.send(tz, 2010, 1, 1, 14, 30, 0)...Time.send(tz, 2011, 2, 2, 14, 30, 0) - range = PostgresqlRange.new(:ts_range => tsrange) - assert range.save - assert range.reload - assert_equal range.ts_range, tsrange - end - - def test_update_tsrange - tz = ::ActiveRecord::Base.default_timezone - new_tsrange = Time.send(tz, 2010, 1, 1, 14, 30, 0)...Time.send(tz, 2011, 2, 2, 14, 30, 0) - @first_range.ts_range = new_tsrange - assert @first_range.save - assert @first_range.reload - assert_equal new_tsrange, @first_range.ts_range - @first_range.ts_range = Time.send(tz, 2010, 1, 1, 14, 30, 0)...Time.send(tz, 2010, 1, 1, 14, 30, 0) - assert @first_range.save - assert @first_range.reload - assert_nil @first_range.ts_range - end - - def test_create_numrange - numrange = BigDecimal.new('0.5')...BigDecimal.new('1') - range = PostgresqlRange.new(:num_range => numrange) - assert range.save - assert range.reload - assert_equal range.num_range, numrange - end - - def test_update_numrange - new_numrange = BigDecimal.new('0.5')...BigDecimal.new('1') - @first_range.num_range = new_numrange - assert @first_range.save - assert @first_range.reload - assert_equal new_numrange, @first_range.num_range - @first_range.num_range = BigDecimal.new('0.5')...BigDecimal.new('0.5') - assert @first_range.save - assert @first_range.reload - assert_nil @first_range.num_range - end - - def test_create_daterange - daterange = Range.new(Date.new(2012, 1, 1), Date.new(2013, 1, 1), true) - range = PostgresqlRange.new(:date_range => daterange) - assert range.save - assert range.reload - assert_equal range.date_range, daterange - end - - def test_update_daterange - new_daterange = Date.new(2012, 2, 3)...Date.new(2012, 2, 10) - @first_range.date_range = new_daterange - assert @first_range.save - assert @first_range.reload - assert_equal new_daterange, @first_range.date_range - @first_range.date_range = Date.new(2012, 2, 3)...Date.new(2012, 2, 3) - assert @first_range.save - assert @first_range.reload - assert_nil @first_range.date_range - end - - def test_create_int4range - int4range = Range.new(3, 50, true) - range = PostgresqlRange.new(:int4_range => int4range) - assert range.save - assert range.reload - assert_equal range.int4_range, int4range - end - - def test_update_int4range - new_int4range = 6...10 - @first_range.int4_range = new_int4range - assert @first_range.save - assert @first_range.reload - assert_equal new_int4range, @first_range.int4_range - @first_range.int4_range = 3...3 - assert @first_range.save - assert @first_range.reload - assert_nil @first_range.int4_range - end - - def test_create_int8range - int8range = Range.new(30, 50, true) - range = PostgresqlRange.new(:int8_range => int8range) - assert range.save - assert range.reload - assert_equal range.int8_range, int8range - end - - def test_update_int8range - new_int8range = 60000...10000000 - @first_range.int8_range = new_int8range - assert @first_range.save - assert @first_range.reload - assert_equal new_int8range, @first_range.int8_range - @first_range.int8_range = 39999...39999 - assert @first_range.save - assert @first_range.reload - assert_nil @first_range.int8_range - end - end - def test_money_values assert_equal 567.89, @first_money.wealth assert_equal(-567.89, @second_money.wealth) @@ -454,11 +170,6 @@ _SQL assert_equal '01:23:45:67:89:0a', @first_network_address.mac_address end - def test_uuid_values - assert_equal 'd96c3da0-96c1-012f-1316-64ce8f32c6d8', @first_uuid.guid - assert_equal 'f06c7150-96c1-012f-1317-64ce8f32c6d8', @first_uuid.compact_guid - end - def test_bit_string_values assert_equal '00010101', @first_bit_string.bit_string assert_equal '00010101', @first_bit_string.bit_string_varying diff --git a/activerecord/test/cases/adapters/postgresql/enum_test.rb b/activerecord/test/cases/adapters/postgresql/enum_test.rb new file mode 100644 index 0000000000..62f84caf91 --- /dev/null +++ b/activerecord/test/cases/adapters/postgresql/enum_test.rb @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- +require "cases/helper" +require 'active_record/base' +require 'active_record/connection_adapters/postgresql_adapter' + +class PostgresqlEnumTest < ActiveRecord::TestCase + class PostgresqlEnum < ActiveRecord::Base + self.table_name = "postgresql_enums" + end + + def teardown + @connection.execute 'DROP TABLE IF EXISTS postgresql_enums' + @connection.execute 'DROP TYPE IF EXISTS mood' + end + + def setup + @connection = ActiveRecord::Base.connection + @connection.transaction do + @connection.execute <<-SQL + CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy'); + SQL + @connection.create_table('postgresql_enums') do |t| + t.column :current_mood, :mood + end + end + # reload type map after creating the enum type + @connection.send(:reload_type_map) + end + + def test_enum_mapping + @connection.execute "INSERT INTO postgresql_enums VALUES (1, 'sad');" + enum = PostgresqlEnum.first + assert_equal "sad", enum.current_mood + + enum.current_mood = "happy" + enum.save! + + assert_equal "happy", enum.reload.current_mood + end + + def test_invalid_enum_update + @connection.execute "INSERT INTO postgresql_enums VALUES (1, 'sad');" + enum = PostgresqlEnum.first + enum.current_mood = "angry" + + assert_raise ActiveRecord::StatementInvalid do + enum.save + end + end + + def test_no_oid_warning + @connection.execute "INSERT INTO postgresql_enums VALUES (1, 'sad');" + stderr_output = capture(:stderr) { PostgresqlEnum.first } + + assert stderr_output.blank? + end + + def test_enum_type_cast + enum = PostgresqlEnum.new + enum.current_mood = :happy + + assert_equal "happy", enum.current_mood + end +end diff --git a/activerecord/test/cases/adapters/postgresql/hstore_test.rb b/activerecord/test/cases/adapters/postgresql/hstore_test.rb index 2845413575..f2502430de 100644 --- a/activerecord/test/cases/adapters/postgresql/hstore_test.rb +++ b/activerecord/test/cases/adapters/postgresql/hstore_test.rb @@ -24,6 +24,7 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase @connection.transaction do @connection.create_table('hstores') do |t| t.hstore 'tags', :default => '' + t.hstore 'payload', array: true t.hstore 'settings' end end @@ -70,6 +71,23 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase Hstore.reset_column_information end + def test_hstore_migration + hstore_migration = Class.new(ActiveRecord::Migration) do + def change + change_table("hstores") do |t| + t.hstore :keys + end + end + end + + hstore_migration.new.suppress_messages do + hstore_migration.migrate(:up) + assert_includes @connection.columns(:hstores).map(&:name), "keys" + hstore_migration.migrate(:down) + assert_not_includes @connection.columns(:hstores).map(&:name), "keys" + end + end + def test_cast_value_on_write x = Hstore.new tags: {"bool" => true, "number" => 5} assert_equal({"bool" => "true", "number" => "5"}, x.tags) @@ -165,6 +183,30 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase assert_equal({'1' => '2'}, x.tags) end + def test_array_cycle + assert_array_cycle([{"AA" => "BB", "CC" => "DD"}, {"AA" => nil}]) + end + + def test_array_strings_with_quotes + assert_array_cycle([{'this has' => 'some "s that need to be escaped"'}]) + end + + def test_array_strings_with_commas + assert_array_cycle([{'this,has' => 'many,values'}]) + end + + def test_array_strings_with_array_delimiters + assert_array_cycle(['{' => '}']) + end + + def test_array_strings_with_null_strings + assert_array_cycle([{'NULL' => 'NULL'}]) + end + + def test_contains_nils + assert_array_cycle([{'NULL' => nil}]) + end + def test_select_multikey @connection.execute "insert into hstores (tags) VALUES ('1=>2,2=>3')" x = Hstore.first @@ -206,10 +248,34 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase def test_multiline assert_cycle("a\nb" => "c\nd") end + + def test_update_all + hstore = Hstore.create! tags: { "one" => "two" } + + Hstore.update_all tags: { "three" => "four" } + assert_equal({ "three" => "four" }, hstore.reload.tags) + + Hstore.update_all tags: { } + assert_equal({ }, hstore.reload.tags) + end end private + def assert_array_cycle(array) + # test creation + x = Hstore.create!(payload: array) + x.reload + assert_equal(array, x.payload) + + # test updating + x = Hstore.create!(payload: []) + x.payload = array + x.save! + x.reload + assert_equal(array, x.payload) + end + def assert_cycle(hash) # test creation x = Hstore.create!(:tags => hash) diff --git a/activerecord/test/cases/adapters/postgresql/json_test.rb b/activerecord/test/cases/adapters/postgresql/json_test.rb index c33c7ef968..3daef399d8 100644 --- a/activerecord/test/cases/adapters/postgresql/json_test.rb +++ b/activerecord/test/cases/adapters/postgresql/json_test.rb @@ -21,7 +21,7 @@ class PostgresqlJSONTest < ActiveRecord::TestCase end end rescue ActiveRecord::StatementInvalid - return skip "do not test on PG without json" + skip "do not test on PG without json" end @column = JsonDataType.columns.find { |c| c.name == 'payload' } end @@ -121,4 +121,14 @@ class PostgresqlJSONTest < ActiveRecord::TestCase x = JsonDataType.first assert_equal "640×1136", x.resolution end + + def test_update_all + json = JsonDataType.create! payload: { "one" => "two" } + + JsonDataType.update_all payload: { "three" => "four" } + assert_equal({ "three" => "four" }, json.reload.payload) + + JsonDataType.update_all payload: { } + assert_equal({ }, json.reload.payload) + end end diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb index 8b017760b1..019406dd84 100644 --- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb +++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb @@ -6,13 +6,21 @@ module ActiveRecord class PostgreSQLAdapterTest < ActiveRecord::TestCase def setup @connection = ActiveRecord::Base.connection - @connection.exec_query('drop table if exists ex') - @connection.exec_query('create table ex(id serial primary key, number integer, data character varying(255))') + end + + def test_bad_connection + assert_raise ActiveRecord::NoDatabaseError do + configuration = ActiveRecord::Base.configurations['arunit'].merge(database: 'should_not_exist-cinco-dog-db') + connection = ActiveRecord::Base.postgresql_connection(configuration) + connection.exec_query('SELECT 1') + end end def test_valid_column - column = @connection.columns('ex').find { |col| col.name == 'id' } - assert @connection.valid_type?(column.type) + with_example_table do + column = @connection.columns('ex').find { |col| col.name == 'id' } + assert @connection.valid_type?(column.type) + end end def test_invalid_column @@ -20,7 +28,9 @@ module ActiveRecord end def test_primary_key - assert_equal 'id', @connection.primary_key('ex') + with_example_table do + assert_equal 'id', @connection.primary_key('ex') + end end def test_primary_key_works_tables_containing_capital_letters @@ -28,15 +38,15 @@ module ActiveRecord end def test_non_standard_primary_key - @connection.exec_query('drop table if exists ex') - @connection.exec_query('create table ex(data character varying(255) primary key)') - assert_equal 'data', @connection.primary_key('ex') + with_example_table 'data character varying(255) primary key' do + assert_equal 'data', @connection.primary_key('ex') + end end def test_primary_key_returns_nil_for_no_pk - @connection.exec_query('drop table if exists ex') - @connection.exec_query('create table ex(id integer)') - assert_nil @connection.primary_key('ex') + with_example_table 'id integer' do + assert_nil @connection.primary_key('ex') + end end def test_primary_key_raises_error_if_table_not_found @@ -46,20 +56,40 @@ module ActiveRecord end def test_insert_sql_with_proprietary_returning_clause - id = @connection.insert_sql("insert into ex (number) values(5150)", nil, "number") - assert_equal "5150", id + with_example_table do + id = @connection.insert_sql("insert into ex (number) values(5150)", nil, "number") + assert_equal "5150", id + end end def test_insert_sql_with_quoted_schema_and_table_name - id = @connection.insert_sql('insert into "public"."ex" (number) values(5150)') - expect = @connection.query('select max(id) from ex').first.first - assert_equal expect, id + with_example_table do + id = @connection.insert_sql('insert into "public"."ex" (number) values(5150)') + expect = @connection.query('select max(id) from ex').first.first + assert_equal expect, id + end end def test_insert_sql_with_no_space_after_table_name - id = @connection.insert_sql("insert into ex(number) values(5150)") - expect = @connection.query('select max(id) from ex').first.first - assert_equal expect, id + with_example_table do + id = @connection.insert_sql("insert into ex(number) values(5150)") + expect = @connection.query('select max(id) from ex').first.first + assert_equal expect, id + end + end + + def test_multiline_insert_sql + with_example_table do + id = @connection.insert_sql(<<-SQL) + insert into ex( + number) + values( + 5152 + ) + SQL + expect = @connection.query('select max(id) from ex').first.first + assert_equal expect, id + end end def test_insert_sql_with_returning_disabled @@ -115,29 +145,31 @@ module ActiveRecord end def test_pk_and_sequence_for - pk, seq = @connection.pk_and_sequence_for('ex') - assert_equal 'id', pk - assert_equal @connection.default_sequence_name('ex', 'id'), seq + with_example_table do + pk, seq = @connection.pk_and_sequence_for('ex') + assert_equal 'id', pk + assert_equal @connection.default_sequence_name('ex', 'id'), seq + end end def test_pk_and_sequence_for_with_non_standard_primary_key - @connection.exec_query('drop table if exists ex') - @connection.exec_query('create table ex(code serial primary key)') - pk, seq = @connection.pk_and_sequence_for('ex') - assert_equal 'code', pk - assert_equal @connection.default_sequence_name('ex', 'code'), seq + with_example_table 'code serial primary key' do + pk, seq = @connection.pk_and_sequence_for('ex') + assert_equal 'code', pk + assert_equal @connection.default_sequence_name('ex', 'code'), seq + end end def test_pk_and_sequence_for_returns_nil_if_no_seq - @connection.exec_query('drop table if exists ex') - @connection.exec_query('create table ex(id integer primary key)') - assert_nil @connection.pk_and_sequence_for('ex') + with_example_table 'id integer primary key' do + assert_nil @connection.pk_and_sequence_for('ex') + end end def test_pk_and_sequence_for_returns_nil_if_no_pk - @connection.exec_query('drop table if exists ex') - @connection.exec_query('create table ex(id integer)') - assert_nil @connection.pk_and_sequence_for('ex') + with_example_table 'id integer' do + assert_nil @connection.pk_and_sequence_for('ex') + end end def test_pk_and_sequence_for_returns_nil_if_table_not_found @@ -145,23 +177,27 @@ module ActiveRecord end def test_exec_insert_number - insert(@connection, 'number' => 10) + with_example_table do + insert(@connection, 'number' => 10) - result = @connection.exec_query('SELECT number FROM ex WHERE number = 10') + result = @connection.exec_query('SELECT number FROM ex WHERE number = 10') - assert_equal 1, result.rows.length - assert_equal "10", result.rows.last.last + assert_equal 1, result.rows.length + assert_equal "10", result.rows.last.last + end end def test_exec_insert_string - str = 'いただきます!' - insert(@connection, 'number' => 10, 'data' => str) + with_example_table do + str = 'いただきます!' + insert(@connection, 'number' => 10, 'data' => str) - result = @connection.exec_query('SELECT number, data FROM ex WHERE number = 10') + result = @connection.exec_query('SELECT number, data FROM ex WHERE number = 10') - value = result.rows.last.last + value = result.rows.last.last - assert_equal str, value + assert_equal str, value + end end def test_table_alias_length @@ -171,44 +207,50 @@ module ActiveRecord end def test_exec_no_binds - result = @connection.exec_query('SELECT id, data FROM ex') - assert_equal 0, result.rows.length - assert_equal 2, result.columns.length - assert_equal %w{ id data }, result.columns - - string = @connection.quote('foo') - @connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})") - result = @connection.exec_query('SELECT id, data FROM ex') - assert_equal 1, result.rows.length - assert_equal 2, result.columns.length - - assert_equal [['1', 'foo']], result.rows + with_example_table do + result = @connection.exec_query('SELECT id, data FROM ex') + assert_equal 0, result.rows.length + assert_equal 2, result.columns.length + assert_equal %w{ id data }, result.columns + + string = @connection.quote('foo') + @connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})") + result = @connection.exec_query('SELECT id, data FROM ex') + assert_equal 1, result.rows.length + assert_equal 2, result.columns.length + + assert_equal [['1', 'foo']], result.rows + end end def test_exec_with_binds - string = @connection.quote('foo') - @connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})") - result = @connection.exec_query( - 'SELECT id, data FROM ex WHERE id = $1', nil, [[nil, 1]]) + with_example_table do + string = @connection.quote('foo') + @connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})") + result = @connection.exec_query( + 'SELECT id, data FROM ex WHERE id = $1', nil, [[nil, 1]]) - assert_equal 1, result.rows.length - assert_equal 2, result.columns.length + assert_equal 1, result.rows.length + assert_equal 2, result.columns.length - assert_equal [['1', 'foo']], result.rows + assert_equal [['1', 'foo']], result.rows + end end def test_exec_typecasts_bind_vals - string = @connection.quote('foo') - @connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})") + with_example_table do + string = @connection.quote('foo') + @connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})") - column = @connection.columns('ex').find { |col| col.name == 'id' } - result = @connection.exec_query( - 'SELECT id, data FROM ex WHERE id = $1', nil, [[column, '1-fuu']]) + column = @connection.columns('ex').find { |col| col.name == 'id' } + result = @connection.exec_query( + 'SELECT id, data FROM ex WHERE id = $1', nil, [[column, '1-fuu']]) - assert_equal 1, result.rows.length - assert_equal 2, result.columns.length + assert_equal 1, result.rows.length + assert_equal 2, result.columns.length - assert_equal [['1', 'foo']], result.rows + assert_equal [['1', 'foo']], result.rows + end end def test_substitute_at @@ -220,9 +262,11 @@ module ActiveRecord end def test_partial_index - @connection.add_index 'ex', %w{ id number }, :name => 'partial', :where => "number > 100" - index = @connection.indexes('ex').find { |idx| idx.name == 'partial' } - assert_equal "(number > 100)", index.where + with_example_table do + @connection.add_index 'ex', %w{ id number }, :name => 'partial', :where => "number > 100" + index = @connection.indexes('ex').find { |idx| idx.name == 'partial' } + assert_equal "(number > 100)", index.where + end end def test_columns_for_distinct_zero_orders @@ -280,6 +324,14 @@ module ActiveRecord ctx.exec_insert(sql, 'SQL', binds) end + def with_example_table(definition = nil) + definition ||= 'id serial primary key, number integer, data character varying(255)' + @connection.exec_query("create table ex(#{definition})") + yield + ensure + @connection.exec_query('drop table if exists ex') + end + def connection_without_insert_returning ActiveRecord::Base.postgresql_connection(ActiveRecord::Base.configurations['arunit'].merge(:insert_returning => false)) end diff --git a/activerecord/test/cases/adapters/postgresql/range_test.rb b/activerecord/test/cases/adapters/postgresql/range_test.rb new file mode 100644 index 0000000000..4e84335e9b --- /dev/null +++ b/activerecord/test/cases/adapters/postgresql/range_test.rb @@ -0,0 +1,306 @@ +require "cases/helper" +require 'active_record/base' +require 'active_record/connection_adapters/postgresql_adapter' + +if ActiveRecord::Base.connection.supports_ranges? + class PostgresqlRange < ActiveRecord::Base + self.table_name = "postgresql_ranges" + end + + class PostgresqlRangeTest < ActiveRecord::TestCase + def teardown + @connection.execute 'DROP TABLE IF EXISTS postgresql_ranges' + @connection.execute 'DROP TYPE IF EXISTS floatrange' + end + + def setup + @connection = PostgresqlRange.connection + begin + @connection.transaction do + @connection.execute 'DROP TABLE IF EXISTS postgresql_ranges' + @connection.execute 'DROP TYPE IF EXISTS floatrange' + @connection.execute <<_SQL + CREATE TYPE floatrange AS RANGE ( + subtype = float8, + subtype_diff = float8mi + ); +_SQL + + @connection.create_table('postgresql_ranges') do |t| + t.daterange :date_range + t.numrange :num_range + t.tsrange :ts_range + t.tstzrange :tstz_range + t.int4range :int4_range + t.int8range :int8_range + end + + @connection.add_column 'postgresql_ranges', 'float_range', 'floatrange' + end + @connection.send :reload_type_map + PostgresqlRange.reset_column_information + rescue ActiveRecord::StatementInvalid + skip "do not test on PG without range" + end + + insert_range(id: 101, + date_range: "[''2012-01-02'', ''2012-01-04'']", + num_range: "[0.1, 0.2]", + ts_range: "[''2010-01-01 14:30'', ''2011-01-01 14:30'']", + tstz_range: "[''2010-01-01 14:30:00+05'', ''2011-01-01 14:30:00-03'']", + int4_range: "[1, 10]", + int8_range: "[10, 100]", + float_range: "[0.5, 0.7]") + + insert_range(id: 102, + date_range: "[''2012-01-02'', ''2012-01-04'')", + num_range: "[0.1, 0.2)", + ts_range: "[''2010-01-01 14:30'', ''2011-01-01 14:30'')", + tstz_range: "[''2010-01-01 14:30:00+05'', ''2011-01-01 14:30:00-03'')", + int4_range: "[1, 10)", + int8_range: "[10, 100)", + float_range: "[0.5, 0.7)") + + insert_range(id: 103, + date_range: "[''2012-01-02'',]", + num_range: "[0.1,]", + ts_range: "[''2010-01-01 14:30'',]", + tstz_range: "[''2010-01-01 14:30:00+05'',]", + int4_range: "[1,]", + int8_range: "[10,]", + float_range: "[0.5,]") + + insert_range(id: 104, + date_range: "[,]", + num_range: "[,]", + ts_range: "[,]", + tstz_range: "[,]", + int4_range: "[,]", + int8_range: "[,]", + float_range: "[,]") + + insert_range(id: 105, + date_range: "[''2012-01-02'', ''2012-01-02'')", + num_range: "[0.1, 0.1)", + ts_range: "[''2010-01-01 14:30'', ''2010-01-01 14:30'')", + tstz_range: "[''2010-01-01 14:30:00+05'', ''2010-01-01 06:30:00-03'')", + int4_range: "[1, 1)", + int8_range: "[10, 10)", + float_range: "[0.5, 0.5)") + + @new_range = PostgresqlRange.new + @first_range = PostgresqlRange.find(101) + @second_range = PostgresqlRange.find(102) + @third_range = PostgresqlRange.find(103) + @fourth_range = PostgresqlRange.find(104) + @empty_range = PostgresqlRange.find(105) + end + + def test_data_type_of_range_types + assert_equal :daterange, @first_range.column_for_attribute(:date_range).type + assert_equal :numrange, @first_range.column_for_attribute(:num_range).type + assert_equal :tsrange, @first_range.column_for_attribute(:ts_range).type + assert_equal :tstzrange, @first_range.column_for_attribute(:tstz_range).type + assert_equal :int4range, @first_range.column_for_attribute(:int4_range).type + assert_equal :int8range, @first_range.column_for_attribute(:int8_range).type + end + + def test_int4range_values + assert_equal 1...11, @first_range.int4_range + assert_equal 1...10, @second_range.int4_range + assert_equal 1...Float::INFINITY, @third_range.int4_range + assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.int4_range) + assert_nil @empty_range.int4_range + end + + def test_int8range_values + assert_equal 10...101, @first_range.int8_range + assert_equal 10...100, @second_range.int8_range + assert_equal 10...Float::INFINITY, @third_range.int8_range + assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.int8_range) + assert_nil @empty_range.int8_range + end + + def test_daterange_values + assert_equal Date.new(2012, 1, 2)...Date.new(2012, 1, 5), @first_range.date_range + assert_equal Date.new(2012, 1, 2)...Date.new(2012, 1, 4), @second_range.date_range + assert_equal Date.new(2012, 1, 2)...Float::INFINITY, @third_range.date_range + assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.date_range) + assert_nil @empty_range.date_range + end + + def test_numrange_values + assert_equal BigDecimal.new('0.1')..BigDecimal.new('0.2'), @first_range.num_range + assert_equal BigDecimal.new('0.1')...BigDecimal.new('0.2'), @second_range.num_range + assert_equal BigDecimal.new('0.1')...BigDecimal.new('Infinity'), @third_range.num_range + assert_equal BigDecimal.new('-Infinity')...BigDecimal.new('Infinity'), @fourth_range.num_range + assert_nil @empty_range.num_range + end + + def test_tsrange_values + tz = ::ActiveRecord::Base.default_timezone + assert_equal Time.send(tz, 2010, 1, 1, 14, 30, 0)..Time.send(tz, 2011, 1, 1, 14, 30, 0), @first_range.ts_range + assert_equal Time.send(tz, 2010, 1, 1, 14, 30, 0)...Time.send(tz, 2011, 1, 1, 14, 30, 0), @second_range.ts_range + assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.ts_range) + assert_nil @empty_range.ts_range + end + + def test_tstzrange_values + assert_equal Time.parse('2010-01-01 09:30:00 UTC')..Time.parse('2011-01-01 17:30:00 UTC'), @first_range.tstz_range + assert_equal Time.parse('2010-01-01 09:30:00 UTC')...Time.parse('2011-01-01 17:30:00 UTC'), @second_range.tstz_range + assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.tstz_range) + assert_nil @empty_range.tstz_range + end + + def test_custom_range_values + assert_equal 0.5..0.7, @first_range.float_range + assert_equal 0.5...0.7, @second_range.float_range + assert_equal 0.5...Float::INFINITY, @third_range.float_range + assert_equal (-Float::INFINITY...Float::INFINITY), @fourth_range.float_range + assert_nil @empty_range.float_range + end + + def test_create_tstzrange + tstzrange = Time.parse('2010-01-01 14:30:00 +0100')...Time.parse('2011-02-02 14:30:00 CDT') + round_trip(@new_range, :tstz_range, tstzrange) + assert_equal @new_range.tstz_range, tstzrange + assert_equal @new_range.tstz_range, Time.parse('2010-01-01 13:30:00 UTC')...Time.parse('2011-02-02 19:30:00 UTC') + end + + def test_update_tstzrange + assert_equal_round_trip(@first_range, :tstz_range, + Time.parse('2010-01-01 14:30:00 CDT')...Time.parse('2011-02-02 14:30:00 CET')) + assert_nil_round_trip(@first_range, :tstz_range, + Time.parse('2010-01-01 14:30:00 +0100')...Time.parse('2010-01-01 13:30:00 +0000')) + end + + def test_create_tsrange + tz = ::ActiveRecord::Base.default_timezone + assert_equal_round_trip(@new_range, :ts_range, + Time.send(tz, 2010, 1, 1, 14, 30, 0)...Time.send(tz, 2011, 2, 2, 14, 30, 0)) + end + + def test_update_tsrange + tz = ::ActiveRecord::Base.default_timezone + assert_equal_round_trip(@first_range, :ts_range, + Time.send(tz, 2010, 1, 1, 14, 30, 0)...Time.send(tz, 2011, 2, 2, 14, 30, 0)) + assert_nil_round_trip(@first_range, :ts_range, + Time.send(tz, 2010, 1, 1, 14, 30, 0)...Time.send(tz, 2010, 1, 1, 14, 30, 0)) + end + + def test_create_numrange + assert_equal_round_trip(@new_range, :num_range, + BigDecimal.new('0.5')...BigDecimal.new('1')) + end + + def test_update_numrange + assert_equal_round_trip(@first_range, :num_range, + BigDecimal.new('0.5')...BigDecimal.new('1')) + assert_nil_round_trip(@first_range, :num_range, + BigDecimal.new('0.5')...BigDecimal.new('0.5')) + end + + def test_create_daterange + assert_equal_round_trip(@new_range, :date_range, + Range.new(Date.new(2012, 1, 1), Date.new(2013, 1, 1), true)) + end + + def test_update_daterange + assert_equal_round_trip(@first_range, :date_range, + Date.new(2012, 2, 3)...Date.new(2012, 2, 10)) + assert_nil_round_trip(@first_range, :date_range, + Date.new(2012, 2, 3)...Date.new(2012, 2, 3)) + end + + def test_create_int4range + assert_equal_round_trip(@new_range, :int4_range, Range.new(3, 50, true)) + end + + def test_update_int4range + assert_equal_round_trip(@first_range, :int4_range, 6...10) + assert_nil_round_trip(@first_range, :int4_range, 3...3) + end + + def test_create_int8range + assert_equal_round_trip(@new_range, :int8_range, Range.new(30, 50, true)) + end + + def test_update_int8range + assert_equal_round_trip(@first_range, :int8_range, 60000...10000000) + assert_nil_round_trip(@first_range, :int8_range, 39999...39999) + end + + def test_exclude_beginning_for_subtypes_with_succ_method_is_deprecated + tz = ::ActiveRecord::Base.default_timezone + + silence_warnings { + assert_deprecated { + range = PostgresqlRange.create!(date_range: "(''2012-01-02'', ''2012-01-04'']") + assert_equal Date.new(2012, 1, 3)..Date.new(2012, 1, 4), range.date_range + } + assert_deprecated { + range = PostgresqlRange.create!(ts_range: "(''2010-01-01 14:30'', ''2011-01-01 14:30'']") + assert_equal Time.send(tz, 2010, 1, 1, 14, 30, 1)..Time.send(tz, 2011, 1, 1, 14, 30, 0), range.ts_range + } + assert_deprecated { + range = PostgresqlRange.create!(tstz_range: "(''2010-01-01 14:30:00+05'', ''2011-01-01 14:30:00-03'']") + assert_equal Time.parse('2010-01-01 09:30:01 UTC')..Time.parse('2011-01-01 17:30:00 UTC'), range.tstz_range + } + assert_deprecated { + range = PostgresqlRange.create!(int4_range: "(1, 10]") + assert_equal 2..10, range.int4_range + } + assert_deprecated { + range = PostgresqlRange.create!(int8_range: "(10, 100]") + assert_equal 11..100, range.int8_range + } + } + end + + def test_exclude_beginning_for_subtypes_without_succ_method_is_not_supported + assert_raises(ArgumentError) { PostgresqlRange.create!(num_range: "(0.1, 0.2]") } + assert_raises(ArgumentError) { PostgresqlRange.create!(float_range: "(0.5, 0.7]") } + end + + private + def assert_equal_round_trip(range, attribute, value) + round_trip(range, attribute, value) + assert_equal value, range.public_send(attribute) + end + + def assert_nil_round_trip(range, attribute, value) + round_trip(range, attribute, value) + assert_nil range.public_send(attribute) + end + + def round_trip(range, attribute, value) + range.public_send "#{attribute}=", value + assert range.save + assert range.reload + end + + def insert_range(values) + @connection.execute <<-SQL + INSERT INTO postgresql_ranges ( + id, + date_range, + num_range, + ts_range, + tstz_range, + int4_range, + int8_range, + float_range + ) VALUES ( + #{values[:id]}, + '#{values[:date_range]}', + '#{values[:num_range]}', + '#{values[:ts_range]}', + '#{values[:tstz_range]}', + '#{values[:int4_range]}', + '#{values[:int8_range]}', + '#{values[:float_range]}' + ) + SQL + end + end +end diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb index e8dd188ec8..3f7009c1d1 100644 --- a/activerecord/test/cases/adapters/postgresql/schema_test.rb +++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb @@ -115,6 +115,12 @@ class SchemaTest < ActiveRecord::TestCase 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]] + end + end + def test_schema_change_with_prepared_stmt altered = false @connection.exec_query "select * from developers where id = $1", 'sql', [[nil, 1]] @@ -240,6 +246,18 @@ class SchemaTest < ActiveRecord::TestCase assert_nothing_raised { with_schema_search_path nil } end + def test_index_name_exists + with_schema_search_path(SCHEMA_NAME) do + assert @connection.index_name_exists?(TABLE_NAME, INDEX_A_NAME, true) + assert @connection.index_name_exists?(TABLE_NAME, INDEX_B_NAME, true) + assert @connection.index_name_exists?(TABLE_NAME, INDEX_C_NAME, true) + assert @connection.index_name_exists?(TABLE_NAME, INDEX_D_NAME, true) + assert @connection.index_name_exists?(TABLE_NAME, INDEX_E_NAME, true) + assert @connection.index_name_exists?(TABLE_NAME, INDEX_E_NAME, true) + assert_not @connection.index_name_exists?(TABLE_NAME, 'missing_index', true) + end + end + def test_dump_indexes_for_schema_one do_dump_index_tests_for_schema(SCHEMA_NAME, INDEX_A_COLUMN, INDEX_B_COLUMN_S1, INDEX_D_COLUMN, INDEX_E_COLUMN) end diff --git a/activerecord/test/cases/adapters/postgresql/timestamp_test.rb b/activerecord/test/cases/adapters/postgresql/timestamp_test.rb index 89210866f0..4d29a20e66 100644 --- a/activerecord/test/cases/adapters/postgresql/timestamp_test.rb +++ b/activerecord/test/cases/adapters/postgresql/timestamp_test.rb @@ -77,7 +77,7 @@ class TimestampTest < ActiveRecord::TestCase end def test_bc_timestamp - date = Date.new(0) - 1.second + date = Date.new(0) - 1.week Developer.create!(:name => "aaron", :updated_at => date) assert_equal date, Developer.find_by_name("aaron").updated_at end diff --git a/activerecord/test/cases/adapters/postgresql/uuid_test.rb b/activerecord/test/cases/adapters/postgresql/uuid_test.rb index 3f5d981444..f581c4cf39 100644 --- a/activerecord/test/cases/adapters/postgresql/uuid_test.rb +++ b/activerecord/test/cases/adapters/postgresql/uuid_test.rb @@ -4,31 +4,77 @@ require "cases/helper" require 'active_record/base' require 'active_record/connection_adapters/postgresql_adapter' +module PostgresqlUUIDHelper + def connection + @connection ||= ActiveRecord::Base.connection + end + + def enable_uuid_ossp + unless connection.extension_enabled?('uuid-ossp') + connection.enable_extension 'uuid-ossp' + connection.commit_db_transaction + end + + connection.reconnect! + end + + def drop_table(name) + connection.execute "drop table if exists #{name}" + end +end + class PostgresqlUUIDTest < ActiveRecord::TestCase - class UUID < ActiveRecord::Base - self.table_name = 'pg_uuids' + include PostgresqlUUIDHelper + + class UUIDType < ActiveRecord::Base + self.table_name = "uuid_data_type" end - def setup - @connection = ActiveRecord::Base.connection + setup do + connection.create_table "uuid_data_type" do |t| + t.uuid 'guid' + end + end + + teardown do + drop_table "uuid_data_type" + end + + def test_data_type_of_uuid_types + assert_equal :uuid, UUIDType.columns_hash["guid"].type + end - unless @connection.extension_enabled?('uuid-ossp') - @connection.enable_extension 'uuid-ossp' - @connection.commit_db_transaction + def test_uuid_formats + ["A0EEBC99-9C0B-4EF8-BB6D-6BB9BD380A11", + "{a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11}", + "a0eebc999c0b4ef8bb6d6bb9bd380a11", + "a0ee-bc99-9c0b-4ef8-bb6d-6bb9-bd38-0a11", + "{a0eebc99-9c0b4ef8-bb6d6bb9-bd380a11}"].each do |valid_uuid| + UUIDType.create(guid: valid_uuid) + uuid = UUIDType.last + assert_equal "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", uuid.guid end + end +end - @connection.reconnect! +class PostgresqlUUIDGenerationTest < ActiveRecord::TestCase + include PostgresqlUUIDHelper - @connection.transaction do - @connection.create_table('pg_uuids', id: :uuid, default: 'uuid_generate_v1()') do |t| - t.string 'name' - t.uuid 'other_uuid', default: 'uuid_generate_v4()' - end + class UUID < ActiveRecord::Base + self.table_name = 'pg_uuids' + end + + setup do + enable_uuid_ossp + + connection.create_table('pg_uuids', id: :uuid, default: 'uuid_generate_v1()') do |t| + t.string 'name' + t.uuid 'other_uuid', default: 'uuid_generate_v4()' end end - def teardown - @connection.execute 'drop table if exists pg_uuids' + teardown do + drop_table "pg_uuids" end if ActiveRecord::Base.connection.supports_extensions? @@ -49,14 +95,14 @@ class PostgresqlUUIDTest < ActiveRecord::TestCase end def test_pk_and_sequence_for_uuid_primary_key - pk, seq = @connection.pk_and_sequence_for('pg_uuids') + pk, seq = connection.pk_and_sequence_for('pg_uuids') assert_equal 'id', pk assert_equal nil, seq end def test_schema_dumper_for_uuid_primary_key schema = StringIO.new - ActiveRecord::SchemaDumper.dump(@connection, schema) + ActiveRecord::SchemaDumper.dump(connection, schema) assert_match(/\bcreate_table "pg_uuids", id: :uuid, default: "uuid_generate_v1\(\)"/, schema.string) assert_match(/t\.uuid "other_uuid", default: "uuid_generate_v4\(\)"/, schema.string) end @@ -64,34 +110,24 @@ class PostgresqlUUIDTest < ActiveRecord::TestCase end class PostgresqlUUIDTestNilDefault < ActiveRecord::TestCase - class UUID < ActiveRecord::Base - self.table_name = 'pg_uuids' - end + include PostgresqlUUIDHelper - def setup - @connection = ActiveRecord::Base.connection - @connection.reconnect! - - unless @connection.extension_enabled?('uuid-ossp') - @connection.enable_extension 'uuid-ossp' - @connection.commit_db_transaction - end + setup do + enable_uuid_ossp - @connection.transaction do - @connection.create_table('pg_uuids', id: false) do |t| - t.primary_key :id, :uuid, default: nil - t.string 'name' - end + connection.create_table('pg_uuids', id: false) do |t| + t.primary_key :id, :uuid, default: nil + t.string 'name' end end - def teardown - @connection.execute 'drop table if exists pg_uuids' + teardown do + drop_table "pg_uuids" end if ActiveRecord::Base.connection.supports_extensions? def test_id_allows_default_override_via_nil - col_desc = @connection.execute("SELECT pg_get_expr(d.adbin, d.adrelid) as default + col_desc = connection.execute("SELECT pg_get_expr(d.adbin, d.adrelid) as default FROM pg_attribute a LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum WHERE a.attname='id' AND a.attrelid = 'pg_uuids'::regclass").first @@ -101,6 +137,8 @@ class PostgresqlUUIDTestNilDefault < ActiveRecord::TestCase end class PostgresqlUUIDTestInverseOf < ActiveRecord::TestCase + include PostgresqlUUIDHelper + class UuidPost < ActiveRecord::Base self.table_name = 'pg_uuid_posts' has_many :uuid_comments, inverse_of: :uuid_post @@ -111,30 +149,24 @@ class PostgresqlUUIDTestInverseOf < ActiveRecord::TestCase belongs_to :uuid_post end - def setup - @connection = ActiveRecord::Base.connection - @connection.reconnect! - - unless @connection.extension_enabled?('uuid-ossp') - @connection.enable_extension 'uuid-ossp' - @connection.commit_db_transaction - end + setup do + enable_uuid_ossp - @connection.transaction do - @connection.create_table('pg_uuid_posts', id: :uuid) do |t| + connection.transaction do + connection.create_table('pg_uuid_posts', id: :uuid) do |t| t.string 'title' end - @connection.create_table('pg_uuid_comments', id: :uuid) do |t| + connection.create_table('pg_uuid_comments', id: :uuid) do |t| t.uuid :uuid_post_id, default: 'uuid_generate_v4()' t.string 'content' end end end - def teardown - @connection.transaction do - @connection.execute 'drop table if exists pg_uuid_comments' - @connection.execute 'drop table if exists pg_uuid_posts' + teardown do + connection.transaction do + drop_table "pg_uuid_comments" + drop_table "pg_uuid_posts" end end diff --git a/activerecord/test/cases/adapters/postgresql/xml_test.rb b/activerecord/test/cases/adapters/postgresql/xml_test.rb index bf14b378d8..dd2a727afe 100644 --- a/activerecord/test/cases/adapters/postgresql/xml_test.rb +++ b/activerecord/test/cases/adapters/postgresql/xml_test.rb @@ -18,7 +18,7 @@ class PostgresqlXMLTest < ActiveRecord::TestCase end end rescue ActiveRecord::StatementInvalid - return skip "do not test on PG without xml" + skip "do not test on PG without xml" end @column = XmlDataType.columns.find { |c| c.name == 'payload' } end diff --git a/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb b/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb index e78cb88562..b478db749d 100644 --- a/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb @@ -1,7 +1,7 @@ require "cases/helper" class CopyTableTest < ActiveRecord::TestCase - fixtures :customers, :companies, :comments, :binaries + fixtures :customers def setup @connection = ActiveRecord::Base.connection diff --git a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb index a7b2764fc1..ba89487838 100644 --- a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb @@ -95,6 +95,13 @@ module ActiveRecord end }.new assert_equal 10, @conn.type_cast(quoted_id_obj, nil) + + quoted_id_obj = Class.new { + def quoted_id + "'zomg'" + end + } + assert_raise(TypeError) { @conn.type_cast(quoted_id_obj, nil) } 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 ce7c869eec..73cb739b2b 100644 --- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb @@ -1,6 +1,7 @@ # encoding: utf-8 require "cases/helper" require 'models/owner' +require 'tempfile' module ActiveRecord module ConnectionAdapters @@ -25,6 +26,36 @@ module ActiveRecord ActiveSupport::Notifications.subscribe('sql.active_record', @subscriber) end + def test_bad_connection + assert_raise ActiveRecord::NoDatabaseError do + connection = ActiveRecord::Base.sqlite3_connection(adapter: "sqlite3", database: "/tmp/should/_not/_exist/-cinco-dog.db") + connection.exec_query('drop table if exists ex') + end + end + + unless in_memory_db? + def test_connect_with_url + original_connection = ActiveRecord::Base.remove_connection + tf = Tempfile.open 'whatever' + url = "sqlite3://#{tf.path}" + ActiveRecord::Base.establish_connection(url) + assert ActiveRecord::Base.connection + ensure + tf.close + tf.unlink + ActiveRecord::Base.establish_connection(original_connection) + end + + def test_connect_memory_with_url + original_connection = ActiveRecord::Base.remove_connection + url = "sqlite3:///:memory:" + ActiveRecord::Base.establish_connection(url) + assert ActiveRecord::Base.connection + ensure + ActiveRecord::Base.establish_connection(original_connection) + end + end + def test_valid_column column = @conn.columns('items').find { |col| col.name == 'id' } assert @conn.valid_type?(column.type) @@ -154,7 +185,7 @@ module ActiveRecord DualEncoding.connection.execute(<<-eosql) CREATE TABLE dual_encodings ( id integer PRIMARY KEY AUTOINCREMENT, - name string, + name varchar(255), data binary ) eosql diff --git a/activerecord/test/cases/associations/association_scope_test.rb b/activerecord/test/cases/associations/association_scope_test.rb index d38648202e..c78b036f53 100644 --- a/activerecord/test/cases/associations/association_scope_test.rb +++ b/activerecord/test/cases/associations/association_scope_test.rb @@ -6,8 +6,9 @@ module ActiveRecord module Associations class AssociationScopeTest < ActiveRecord::TestCase test 'does not duplicate conditions' do - association_scope = AssociationScope.new(Author.new.association(:welcome_posts)) - wheres = association_scope.scope.where_values.map(&:right) + scope = AssociationScope.scope(Author.new.association(:welcome_posts), + Author.connection) + wheres = scope.where_values.map(&:right) assert_equal wheres.uniq, wheres end end diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb index a79f145e31..9340bc0a83 100644 --- a/activerecord/test/cases/associations/belongs_to_associations_test.rb +++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb @@ -28,6 +28,13 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase assert_equal companies(:first_firm).name, firm.name end + def test_belongs_to_does_not_use_order_by + ActiveRecord::SQLCounter.clear_log + Client.find(3).firm + ensure + assert ActiveRecord::SQLCounter.log_all.all? { |sql| /order by/i !~ sql }, 'ORDER BY was used in the query' + end + def test_belongs_to_with_primary_key client = Client.create(:name => "Primary key client", :firm_name => companies(:first_firm).name) assert_equal companies(:first_firm).name, client.firm_with_primary_key.name @@ -356,6 +363,14 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase assert_queries(2) { line_item.destroy } end + def test_belongs_to_with_touch_option_on_destroy_with_destroyed_parent + line_item = LineItem.create! + invoice = Invoice.create!(line_items: [line_item]) + invoice.destroy + + assert_queries(1) { line_item.destroy } + end + def test_belongs_to_with_touch_option_on_touch_and_reassigned_parent line_item = LineItem.create! Invoice.create!(line_items: [line_item]) @@ -578,6 +593,19 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase assert_nil essay.writer_id end + def test_polymorphic_assignment_with_nil + essay = Essay.new + assert_nil essay.writer_id + assert_nil essay.writer_type + + essay.writer_id = 1 + essay.writer_type = 'Author' + + essay.writer = nil + assert_nil essay.writer_id + assert_nil essay.writer_type + end + def test_belongs_to_proxy_should_not_respond_to_private_methods assert_raise(NoMethodError) { companies(:first_firm).private_method } assert_raise(NoMethodError) { companies(:second_client).firm.private_method } @@ -619,16 +647,11 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase assert_equal [author_address.id], AuthorAddress.destroyed_author_address_ids end - def test_invalid_belongs_to_dependent_option_nullify_raises_exception - assert_raise ArgumentError do + def test_belongs_to_invalid_dependent_option_raises_exception + error = assert_raise ArgumentError do Class.new(Author).belongs_to :special_author_address, :dependent => :nullify end - end - - def test_invalid_belongs_to_dependent_option_restrict_raises_exception - assert_raise ArgumentError do - Class.new(Author).belongs_to :special_author_address, :dependent => :restrict - end + assert_equal error.message, 'The :dependent option must be one of [:destroy, :delete], but is :nullify' end def test_attributes_are_being_set_when_initialized_from_belongs_to_association_with_where_clause @@ -830,4 +853,14 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase assert post.save assert_equal post.author_id, author2.id end + + test 'dangerous association name raises ArgumentError' do + [:errors, 'errors', :save, 'save'].each do |name| + assert_raises(ArgumentError, "Association #{name} should not be allowed") do + Class.new(ActiveRecord::Base) do + belongs_to name + end + end + end + end end diff --git a/activerecord/test/cases/associations/callbacks_test.rb b/activerecord/test/cases/associations/callbacks_test.rb index 2d0d4541b4..cf71bc1597 100644 --- a/activerecord/test/cases/associations/callbacks_test.rb +++ b/activerecord/test/cases/associations/callbacks_test.rb @@ -101,6 +101,27 @@ class AssociationCallbacksTest < ActiveRecord::TestCase "after_adding#{david.id}"], ar.developers_log end + def test_has_and_belongs_to_many_before_add_called_before_save + dev = nil + new_dev = nil + klass = Class.new(Project) do + def self.name; Project.name; end + has_and_belongs_to_many :developers_with_callbacks, + :class_name => "Developer", + :before_add => lambda { |o,r| + dev = r + new_dev = r.new_record? + } + end + rec = klass.create! + alice = Developer.new(:name => 'alice') + rec.developers_with_callbacks << alice + assert_equal alice, dev + assert_not_nil new_dev + assert new_dev, "record should not have been saved" + assert_not alice.new_record? + end + def test_has_and_belongs_to_many_after_add_called_after_save ar = projects(:active_record) assert ar.developers_log.empty? @@ -138,7 +159,7 @@ class AssociationCallbacksTest < ActiveRecord::TestCase activerecord.reload assert activerecord.developers_with_callbacks.size == 2 end - log_array = activerecord.developers_with_callbacks.collect {|d| ["before_removing#{d.id}","after_removing#{d.id}"]}.flatten.sort + log_array = activerecord.developers_with_callbacks.flat_map {|d| ["before_removing#{d.id}","after_removing#{d.id}"]}.sort assert activerecord.developers_with_callbacks.clear assert_equal log_array, activerecord.developers_log.sort end diff --git a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb index 811d91f849..71c0609df5 100644 --- a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb +++ b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb @@ -174,4 +174,15 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase sink = Vertex.all.merge!(:includes=>{:sources=>{:sources=>{:sources=>:sources}}}, :order => 'vertices.id DESC').first assert_equal vertices(:vertex_1), assert_no_queries { sink.sources.first.sources.first.sources.first.sources.first } end + + def test_eager_association_loading_with_cascaded_interdependent_one_level_and_two_levels + authors_relation = Author.all.merge!(includes: [:comments, { posts: :categorizations }], order: "authors.id") + authors = authors_relation.to_a + assert_equal 3, authors.size + assert_equal 10, authors[0].comments.size + assert_equal 1, authors[1].comments.size + assert_equal 5, authors[0].posts.size + assert_equal 3, authors[1].posts.size + assert_equal 3, authors[0].posts.collect { |post| post.categorizations.size }.inject(0) { |sum, i| sum+i } + end end diff --git a/activerecord/test/cases/associations/eager_singularization_test.rb b/activerecord/test/cases/associations/eager_singularization_test.rb index 634f6b63ba..b12bc355e8 100644 --- a/activerecord/test/cases/associations/eager_singularization_test.rb +++ b/activerecord/test/cases/associations/eager_singularization_test.rb @@ -1,128 +1,132 @@ require "cases/helper" -class Virus < ActiveRecord::Base - belongs_to :octopus -end -class Octopus < ActiveRecord::Base - has_one :virus -end -class Pass < ActiveRecord::Base - belongs_to :bus -end -class Bus < ActiveRecord::Base - has_many :passes -end -class Mess < ActiveRecord::Base - has_and_belongs_to_many :crises -end -class Crisis < ActiveRecord::Base - has_and_belongs_to_many :messes - has_many :analyses, :dependent => :destroy - has_many :successes, :through => :analyses - has_many :dresses, :dependent => :destroy - has_many :compresses, :through => :dresses -end -class Analysis < ActiveRecord::Base - belongs_to :crisis - belongs_to :success -end -class Success < ActiveRecord::Base - has_many :analyses, :dependent => :destroy - has_many :crises, :through => :analyses -end -class Dress < ActiveRecord::Base - belongs_to :crisis - has_many :compresses -end -class Compress < ActiveRecord::Base - belongs_to :dress -end - +if ActiveRecord::Base.connection.supports_migrations? class EagerSingularizationTest < ActiveRecord::TestCase + class Virus < ActiveRecord::Base + belongs_to :octopus + end + + class Octopus < ActiveRecord::Base + has_one :virus + end + + class Pass < ActiveRecord::Base + belongs_to :bus + end + + class Bus < ActiveRecord::Base + has_many :passes + end + + class Mess < ActiveRecord::Base + has_and_belongs_to_many :crises + end + + class Crisis < ActiveRecord::Base + has_and_belongs_to_many :messes + has_many :analyses, :dependent => :destroy + has_many :successes, :through => :analyses + has_many :dresses, :dependent => :destroy + has_many :compresses, :through => :dresses + end + + class Analysis < ActiveRecord::Base + belongs_to :crisis + belongs_to :success + end + + class Success < ActiveRecord::Base + has_many :analyses, :dependent => :destroy + has_many :crises, :through => :analyses + end + + class Dress < ActiveRecord::Base + belongs_to :crisis + has_many :compresses + end + + class Compress < ActiveRecord::Base + belongs_to :dress + end def setup - if ActiveRecord::Base.connection.supports_migrations? - ActiveRecord::Base.connection.create_table :viri do |t| - t.column :octopus_id, :integer - t.column :species, :string - end - ActiveRecord::Base.connection.create_table :octopi do |t| - t.column :species, :string - end - ActiveRecord::Base.connection.create_table :passes do |t| - t.column :bus_id, :integer - t.column :rides, :integer - end - ActiveRecord::Base.connection.create_table :buses do |t| - t.column :name, :string - end - ActiveRecord::Base.connection.create_table :crises_messes, :id => false do |t| - t.column :crisis_id, :integer - t.column :mess_id, :integer - end - ActiveRecord::Base.connection.create_table :messes do |t| - t.column :name, :string - end - ActiveRecord::Base.connection.create_table :crises do |t| - t.column :name, :string - end - ActiveRecord::Base.connection.create_table :successes do |t| - t.column :name, :string - end - ActiveRecord::Base.connection.create_table :analyses do |t| - t.column :crisis_id, :integer - t.column :success_id, :integer - end - ActiveRecord::Base.connection.create_table :dresses do |t| - t.column :crisis_id, :integer - end - ActiveRecord::Base.connection.create_table :compresses do |t| - t.column :dress_id, :integer - end - @have_tables = true - else - @have_tables = false + connection.create_table :viri do |t| + t.column :octopus_id, :integer + t.column :species, :string + end + connection.create_table :octopi do |t| + t.column :species, :string + end + connection.create_table :passes do |t| + t.column :bus_id, :integer + t.column :rides, :integer + end + connection.create_table :buses do |t| + t.column :name, :string + end + connection.create_table :crises_messes, :id => false do |t| + t.column :crisis_id, :integer + t.column :mess_id, :integer + end + connection.create_table :messes do |t| + t.column :name, :string + end + connection.create_table :crises do |t| + t.column :name, :string + end + connection.create_table :successes do |t| + t.column :name, :string + end + connection.create_table :analyses do |t| + t.column :crisis_id, :integer + t.column :success_id, :integer + end + connection.create_table :dresses do |t| + t.column :crisis_id, :integer + end + connection.create_table :compresses do |t| + t.column :dress_id, :integer end end def teardown - ActiveRecord::Base.connection.drop_table :viri - ActiveRecord::Base.connection.drop_table :octopi - ActiveRecord::Base.connection.drop_table :passes - ActiveRecord::Base.connection.drop_table :buses - ActiveRecord::Base.connection.drop_table :crises_messes - ActiveRecord::Base.connection.drop_table :messes - ActiveRecord::Base.connection.drop_table :crises - ActiveRecord::Base.connection.drop_table :successes - ActiveRecord::Base.connection.drop_table :analyses - ActiveRecord::Base.connection.drop_table :dresses - ActiveRecord::Base.connection.drop_table :compresses + connection.drop_table :viri + connection.drop_table :octopi + connection.drop_table :passes + connection.drop_table :buses + connection.drop_table :crises_messes + connection.drop_table :messes + connection.drop_table :crises + connection.drop_table :successes + connection.drop_table :analyses + connection.drop_table :dresses + connection.drop_table :compresses + end + + def connection + ActiveRecord::Base.connection end def test_eager_no_extra_singularization_belongs_to - return unless @have_tables assert_nothing_raised do Virus.all.merge!(:includes => :octopus).to_a end end def test_eager_no_extra_singularization_has_one - return unless @have_tables assert_nothing_raised do Octopus.all.merge!(:includes => :virus).to_a end end def test_eager_no_extra_singularization_has_many - return unless @have_tables assert_nothing_raised do Bus.all.merge!(:includes => :passes).to_a end end def test_eager_no_extra_singularization_has_and_belongs_to_many - return unless @have_tables assert_nothing_raised do Crisis.all.merge!(:includes => :messes).to_a Mess.all.merge!(:includes => :crises).to_a @@ -130,16 +134,15 @@ class EagerSingularizationTest < ActiveRecord::TestCase end def test_eager_no_extra_singularization_has_many_through_belongs_to - return unless @have_tables assert_nothing_raised do Crisis.all.merge!(:includes => :successes).to_a end end def test_eager_no_extra_singularization_has_many_through_has_many - return unless @have_tables assert_nothing_raised do Crisis.all.merge!(:includes => :compresses).to_a end end end +end diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb index 498a4e8144..5522a33b79 100644 --- a/activerecord/test/cases/associations/eager_test.rb +++ b/activerecord/test/cases/associations/eager_test.rb @@ -1194,4 +1194,13 @@ class EagerAssociationTest < ActiveRecord::TestCase authors(:david).essays.includes(:writer).any? end end + + test "preloading associations with string joins and order references" do + author = assert_queries(2) { + Author.includes(:posts).joins("LEFT JOIN posts ON posts.author_id = authors.id").order("posts.title DESC").first + } + assert_no_queries { + assert_equal 5, author.posts.size + } + end end diff --git a/activerecord/test/cases/associations/extension_test.rb b/activerecord/test/cases/associations/extension_test.rb index f8f2832ab1..4c1fdfdd9a 100644 --- a/activerecord/test/cases/associations/extension_test.rb +++ b/activerecord/test/cases/associations/extension_test.rb @@ -75,6 +75,7 @@ class AssociationsExtensionsTest < ActiveRecord::TestCase private def extend!(model) - ActiveRecord::Associations::Builder::HasMany.define_extensions(model, :association_name) { } + builder = ActiveRecord::Associations::Builder::HasMany.new(model, :association_name, nil, {}) { } + builder.define_extensions(model) end end 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 be928ec8ee..bac1cb8e2d 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 @@ -570,6 +570,13 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase assert !developer.special_projects.include?(other_project) end + def test_symbol_join_table + developer = Developer.first + sp = developer.sym_special_projects.create("name" => "omg") + developer.reload + assert_includes developer.sym_special_projects, sp + end + def test_update_attributes_after_push_without_duplicate_join_table_rows developer = Developer.new("name" => "Kano") project = SpecialProject.create("name" => "Special Project") @@ -768,6 +775,16 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase assert project.developers.include?(developer) end + def test_destruction_does_not_error_without_primary_key + redbeard = pirates(:redbeard) + george = parrots(:george) + redbeard.parrots << george + assert_equal 2, george.pirates.count + Pirate.includes(:parrots).where(parrot: redbeard.parrot).find(redbeard.id).destroy + assert_equal 1, george.pirates.count + assert_equal [], Pirate.where(id: redbeard.id) + end + test "has and belongs to many associations on new records use null relations" do projects = Developer.new.projects assert_no_queries do diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index a025d49fa3..49d3301044 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -216,6 +216,31 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end assert_no_queries do + bulbs.second() + bulbs.second({}) + end + + assert_no_queries do + bulbs.third() + bulbs.third({}) + end + + assert_no_queries do + bulbs.fourth() + bulbs.fourth({}) + end + + assert_no_queries do + bulbs.fifth() + bulbs.fifth({}) + end + + assert_no_queries do + bulbs.forty_two() + bulbs.forty_two({}) + end + + assert_no_queries do bulbs.last() bulbs.last({}) end @@ -242,11 +267,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase # sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first def test_counting_with_counter_sql - assert_equal 2, Firm.all.merge!(:order => "id").first.clients.count + assert_equal 3, Firm.all.merge!(:order => "id").first.clients.count end def test_counting - assert_equal 2, Firm.all.merge!(:order => "id").first.plain_clients.count + assert_equal 3, Firm.all.merge!(:order => "id").first.plain_clients.count end def test_counting_with_single_hash @@ -254,7 +279,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_counting_with_column_name_and_hash - assert_equal 2, Firm.all.merge!(:order => "id").first.plain_clients.count(:name) + assert_equal 3, Firm.all.merge!(:order => "id").first.plain_clients.count(:name) end def test_counting_with_association_limit @@ -264,17 +289,17 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_finding - assert_equal 2, Firm.all.merge!(:order => "id").first.clients.length + assert_equal 3, Firm.all.merge!(:order => "id").first.clients.length end def test_finding_array_compatibility - assert_equal 2, Firm.order(:id).find{|f| f.id > 0}.clients.length + assert_equal 3, Firm.order(:id).find{|f| f.id > 0}.clients.length end def test_find_many_with_merged_options assert_equal 1, companies(:first_firm).limited_clients.size assert_equal 1, companies(:first_firm).limited_clients.to_a.size - assert_equal 2, companies(:first_firm).limited_clients.limit(nil).to_a.size + assert_equal 3, companies(:first_firm).limited_clients.limit(nil).to_a.size end def test_find_should_append_to_association_order @@ -283,8 +308,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_dynamic_find_should_respect_association_order - assert_equal companies(:second_client), companies(:first_firm).clients_sorted_desc.where("type = 'Client'").first - assert_equal companies(:second_client), companies(:first_firm).clients_sorted_desc.find_by_type('Client') + assert_equal companies(:another_first_firm_client), companies(:first_firm).clients_sorted_desc.where("type = 'Client'").first + assert_equal companies(:another_first_firm_client), companies(:first_firm).clients_sorted_desc.find_by_type('Client') end def test_cant_save_has_many_readonly_association @@ -297,7 +322,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_finding_with_different_class_name_and_order - assert_equal "Microsoft", Firm.all.merge!(:order => "id").first.clients_sorted_desc.first.name + assert_equal "Apex", Firm.all.merge!(:order => "id").first.clients_sorted_desc.first.name end def test_finding_with_foreign_key @@ -318,9 +343,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_belongs_to_sanity c = Client.new - assert_nil c.firm - - flunk "belongs_to failed if check" if c.firm + assert_nil c.firm, "belongs_to failed sanity check on new object" end def test_find_ids @@ -357,7 +380,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_find_all firm = Firm.all.merge!(:order => "id").first - assert_equal 2, firm.clients.where("#{QUOTED_TYPE} = 'Client'").to_a.length + assert_equal 3, firm.clients.where("#{QUOTED_TYPE} = 'Client'").to_a.length assert_equal 1, firm.clients.where("name = 'Summit'").to_a.length end @@ -366,7 +389,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert ! firm.clients.loaded? - assert_queries(3) do + assert_queries(4) do firm.clients.find_each(:batch_size => 1) {|c| assert_equal firm.id, c.firm_id } end @@ -436,15 +459,15 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_find_grouped all_clients_of_firm1 = Client.all.merge!(:where => "firm_id = 1").to_a grouped_clients_of_firm1 = Client.all.merge!(:where => "firm_id = 1", :group => "firm_id", :select => 'firm_id, count(id) as clients_count').to_a - assert_equal 2, all_clients_of_firm1.size + assert_equal 3, all_clients_of_firm1.size assert_equal 1, grouped_clients_of_firm1.size end def test_find_scoped_grouped assert_equal 1, companies(:first_firm).clients_grouped_by_firm_id.size assert_equal 1, companies(:first_firm).clients_grouped_by_firm_id.length - assert_equal 2, companies(:first_firm).clients_grouped_by_name.size - assert_equal 2, companies(:first_firm).clients_grouped_by_name.length + assert_equal 3, companies(:first_firm).clients_grouped_by_name.size + assert_equal 3, companies(:first_firm).clients_grouped_by_name.length end def test_find_scoped_grouped_having @@ -457,28 +480,32 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_select_query_method - assert_equal ['id'], posts(:welcome).comments.select(:id).first.attributes.keys + assert_equal ['id', 'body'], posts(:welcome).comments.select(:id, :body).first.attributes.keys + end + + def test_select_with_block + assert_equal [1], posts(:welcome).comments.select { |c| c.id == 1 }.map(&:id) end - def test_select_without_foreign_key + def test_select_without_foreign_key assert_equal companies(:first_firm).accounts.first.credit_limit, companies(:first_firm).accounts.select(:credit_limit).first.credit_limit - end + end def test_adding force_signal37_to_load_all_clients_of_firm natural = Client.new("name" => "Natural Company") companies(:first_firm).clients_of_firm << natural - assert_equal 2, companies(:first_firm).clients_of_firm.size # checking via the collection - assert_equal 2, companies(:first_firm).clients_of_firm(true).size # checking using the db + 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 natural, companies(:first_firm).clients_of_firm.last end def test_adding_using_create first_firm = companies(:first_firm) - assert_equal 2, first_firm.plain_clients.size - first_firm.plain_clients.create(:name => "Natural Company") - assert_equal 3, first_firm.plain_clients.length assert_equal 3, first_firm.plain_clients.size + first_firm.plain_clients.create(:name => "Natural Company") + assert_equal 4, first_firm.plain_clients.length + assert_equal 4, first_firm.plain_clients.size end def test_create_with_bang_on_has_many_when_parent_is_new_raises @@ -517,8 +544,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_adding_a_collection 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 3, companies(:first_firm).clients_of_firm.size - assert_equal 3, companies(:first_firm).clients_of_firm(true).size + assert_equal 4, companies(:first_firm).clients_of_firm.size + assert_equal 4, companies(:first_firm).clients_of_firm(true).size end def test_transactions_when_adding_to_persisted @@ -571,7 +598,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase company = companies(:first_firm) # company already has one client company.clients_of_firm.build("name" => "Another Client") company.clients_of_firm.build("name" => "Yet Another Client") - assert_equal 3, company.clients_of_firm.size + assert_equal 4, company.clients_of_firm.size end def test_collection_not_empty_after_building @@ -647,14 +674,14 @@ class HasManyAssociationsTest < ActiveRecord::TestCase Firm.column_names Client.column_names - assert_equal 1, first_firm.clients_of_firm.size + assert_equal 2, first_firm.clients_of_firm.size first_firm.clients_of_firm.reset assert_queries(1) do first_firm.clients_of_firm.create(:name => "Superstars") end - assert_equal 2, first_firm.clients_of_firm.size + assert_equal 3, first_firm.clients_of_firm.size end def test_create @@ -667,7 +694,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_create_many companies(:first_firm).clients_of_firm.create([{"name" => "Another Client"}, {"name" => "Another Client II"}]) - assert_equal 3, companies(:first_firm).clients_of_firm(true).size + assert_equal 4, companies(:first_firm).clients_of_firm(true).size end def test_create_followed_by_save_does_not_load_target @@ -679,8 +706,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_deleting force_signal37_to_load_all_clients_of_firm companies(:first_firm).clients_of_firm.delete(companies(:first_firm).clients_of_firm.first) - assert_equal 0, companies(:first_firm).clients_of_firm.size - assert_equal 0, companies(:first_firm).clients_of_firm(true).size + assert_equal 1, companies(:first_firm).clients_of_firm.size + assert_equal 1, companies(:first_firm).clients_of_firm(true).size end def test_deleting_before_save @@ -777,8 +804,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_deleting_a_collection force_signal37_to_load_all_clients_of_firm companies(:first_firm).clients_of_firm.create("name" => "Another Client") - assert_equal 2, 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]]) + 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 end @@ -787,7 +814,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase force_signal37_to_load_all_clients_of_firm companies(:first_firm).dependent_clients_of_firm.create("name" => "Another Client") clients = companies(:first_firm).dependent_clients_of_firm.to_a - assert_equal 2, clients.count + assert_equal 3, clients.count assert_difference "Client.count", -(clients.count) do companies(:first_firm).dependent_clients_of_firm.delete_all @@ -797,7 +824,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_delete_all_with_not_yet_loaded_association_collection force_signal37_to_load_all_clients_of_firm companies(:first_firm).clients_of_firm.create("name" => "Another Client") - assert_equal 2, companies(:first_firm).clients_of_firm.size + assert_equal 3, companies(:first_firm).clients_of_firm.size 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 @@ -830,7 +857,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_clearing_an_association_collection firm = companies(:first_firm) client_id = firm.clients_of_firm.first.id - assert_equal 1, firm.clients_of_firm.size + assert_equal 2, firm.clients_of_firm.size firm.clients_of_firm.clear @@ -864,7 +891,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_clearing_a_dependent_association_collection firm = companies(:first_firm) client_id = firm.dependent_clients_of_firm.first.id - assert_equal 1, firm.dependent_clients_of_firm.size + assert_equal 2, firm.dependent_clients_of_firm.size assert_equal 1, Client.find_by_id(client_id).client_of # :delete_all is called on each client since the dependent options is :destroy @@ -895,7 +922,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_clearing_an_exclusively_dependent_association_collection firm = companies(:first_firm) client_id = firm.exclusively_dependent_clients_of_firm.first.id - assert_equal 1, firm.exclusively_dependent_clients_of_firm.size + assert_equal 2, firm.exclusively_dependent_clients_of_firm.size assert_equal [], Client.destroyed_client_ids[firm.id] @@ -951,10 +978,10 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_delete_all_association_with_primary_key_deletes_correct_records firm = Firm.first # break the vanilla firm_id foreign key - assert_equal 2, firm.clients.count + assert_equal 3, firm.clients.count firm.clients.first.update_columns(firm_id: nil) - assert_equal 1, firm.clients(true).count - assert_equal 1, firm.clients_using_primary_key_with_delete_all.count + assert_equal 2, firm.clients(true).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 firm.destroy @@ -986,8 +1013,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase force_signal37_to_load_all_clients_of_firm summit = Client.find_by_name('Summit') companies(:first_firm).clients_of_firm.delete(summit) - assert_equal 1, companies(:first_firm).clients_of_firm.size - assert_equal 1, companies(:first_firm).clients_of_firm(true).size + assert_equal 2, companies(:first_firm).clients_of_firm.size + assert_equal 2, companies(:first_firm).clients_of_firm(true).size assert_equal 2, summit.client_of end @@ -1024,8 +1051,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase companies(:first_firm).clients_of_firm.destroy(companies(:first_firm).clients_of_firm.first) end - assert_equal 0, companies(:first_firm).reload.clients_of_firm.size - assert_equal 0, companies(:first_firm).clients_of_firm(true).size + assert_equal 1, companies(:first_firm).reload.clients_of_firm.size + assert_equal 1, companies(:first_firm).clients_of_firm(true).size end def test_destroying_by_fixnum_id @@ -1035,8 +1062,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase companies(:first_firm).clients_of_firm.destroy(companies(:first_firm).clients_of_firm.first.id) end - assert_equal 0, companies(:first_firm).reload.clients_of_firm.size - assert_equal 0, companies(:first_firm).clients_of_firm(true).size + assert_equal 1, companies(:first_firm).reload.clients_of_firm.size + assert_equal 1, companies(:first_firm).clients_of_firm(true).size end def test_destroying_by_string_id @@ -1046,21 +1073,21 @@ class HasManyAssociationsTest < ActiveRecord::TestCase companies(:first_firm).clients_of_firm.destroy(companies(:first_firm).clients_of_firm.first.id.to_s) end - assert_equal 0, companies(:first_firm).reload.clients_of_firm.size - assert_equal 0, companies(:first_firm).clients_of_firm(true).size + assert_equal 1, companies(:first_firm).reload.clients_of_firm.size + assert_equal 1, companies(:first_firm).clients_of_firm(true).size end def test_destroying_a_collection force_signal37_to_load_all_clients_of_firm companies(:first_firm).clients_of_firm.create("name" => "Another Client") - assert_equal 2, companies(:first_firm).clients_of_firm.size + assert_equal 3, companies(:first_firm).clients_of_firm.size assert_difference "Client.count", -2 do companies(:first_firm).clients_of_firm.destroy([companies(:first_firm).clients_of_firm[0], companies(:first_firm).clients_of_firm[1]]) end - assert_equal 0, companies(:first_firm).reload.clients_of_firm.size - assert_equal 0, companies(:first_firm).clients_of_firm(true).size + assert_equal 1, companies(:first_firm).reload.clients_of_firm.size + assert_equal 1, companies(:first_firm).clients_of_firm(true).size end def test_destroy_all @@ -1076,7 +1103,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_dependence firm = companies(:first_firm) - assert_equal 2, firm.clients.size + assert_equal 3, firm.clients.size firm.destroy assert Client.all.merge!(:where => "firm_id=#{firm.id}").to_a.empty? end @@ -1089,14 +1116,14 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_destroy_dependent_when_deleted_from_association # sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first firm = Firm.all.merge!(:order => "id").first - assert_equal 2, firm.clients.size + assert_equal 3, firm.clients.size client = firm.clients.first firm.clients.delete(client) assert_raise(ActiveRecord::RecordNotFound) { Client.find(client.id) } assert_raise(ActiveRecord::RecordNotFound) { firm.clients.find(client.id) } - assert_equal 1, firm.clients.size + assert_equal 2, firm.clients.size end def test_three_levels_of_dependence @@ -1111,12 +1138,12 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_dependence_with_transaction_support_on_failure firm = companies(:first_firm) clients = firm.clients - assert_equal 2, clients.length + assert_equal 3, clients.length clients.last.instance_eval { def overwrite_to_raise() raise "Trigger rollback" end } firm.destroy rescue "do nothing" - assert_equal 2, Client.all.merge!(:where => "firm_id=#{firm.id}").to_a.size + assert_equal 3, Client.all.merge!(:where => "firm_id=#{firm.id}").to_a.size end def test_dependence_on_account @@ -1215,6 +1242,16 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal orig_accounts, firm.accounts end + def test_replace_with_same_content + firm = Firm.first + firm.clients = [] + firm.save + + assert_queries(0, ignore_none: true) do + firm.clients = [] + end + end + def test_transactions_when_replacing_on_persisted good = Client.new(:name => "Good") bad = Client.new(:name => "Bad", :raise_on_save => true) @@ -1237,7 +1274,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_get_ids - assert_equal [companies(:first_client).id, companies(:second_client).id], companies(:first_firm).client_ids + assert_equal [companies(:first_client).id, companies(:second_client).id, companies(:another_first_firm_client).id], companies(:first_firm).client_ids end def test_get_ids_for_loaded_associations @@ -1252,7 +1289,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_get_ids_for_unloaded_associations_does_not_load_them company = companies(:first_firm) assert !company.clients.loaded? - assert_equal [companies(:first_client).id, companies(:second_client).id], company.client_ids + assert_equal [companies(:first_client).id, companies(:second_client).id, companies(:another_first_firm_client).id], company.client_ids assert !company.clients.loaded? end @@ -1261,7 +1298,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_get_ids_for_ordered_association - assert_equal [companies(:second_client).id, companies(:first_client).id], companies(:first_firm).clients_ordered_by_name_ids + assert_equal [companies(:another_first_firm_client).id, companies(:second_client).id, companies(:first_client).id], companies(:first_firm).clients_ordered_by_name_ids end def test_get_ids_for_association_on_new_record_does_not_try_to_find_records @@ -1355,9 +1392,10 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal false, firm.clients.include?(client) end - def test_calling_first_or_last_on_association_should_not_load_association + def test_calling_first_nth_or_last_on_association_should_not_load_association firm = companies(:first_firm) firm.clients.first + firm.clients.second firm.clients.last assert !firm.clients.loaded? end @@ -1382,30 +1420,33 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_queries 1 do firm.clients.first + firm.clients.second firm.clients.last end assert firm.clients.loaded? end - def test_calling_first_or_last_on_existing_record_with_create_should_not_load_association + def test_calling_first_nth_or_last_on_existing_record_with_create_should_not_load_association firm = companies(:first_firm) firm.clients.create(:name => 'Foo') assert !firm.clients.loaded? - assert_queries 2 do + assert_queries 3 do firm.clients.first + firm.clients.second firm.clients.last end assert !firm.clients.loaded? end - def test_calling_first_or_last_on_new_record_should_not_run_queries + def test_calling_first_nth_or_last_on_new_record_should_not_run_queries firm = Firm.new assert_no_queries do firm.clients.first + firm.clients.second firm.clients.last end end @@ -1492,7 +1533,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_calling_many_should_return_true_if_more_than_one firm = companies(:first_firm) assert firm.clients.many? - assert_equal 2, firm.clients.size + assert_equal 3, firm.clients.size end def test_joins_with_namespaced_model_should_use_correct_type @@ -1770,4 +1811,33 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal [bulb1], car.bulbs assert_equal [bulb1, bulb2], car.all_bulbs.sort_by(&:id) end + + test "raises RecordNotDestroyed when replaced child can't be destroyed" do + car = Car.create! + original_child = FailedBulb.create!(car: car) + + assert_raise(ActiveRecord::RecordNotDestroyed) do + car.failed_bulbs = [FailedBulb.create!] + end + + assert_equal [original_child], car.reload.failed_bulbs + end + + test 'updates counter cache when default scope is given' do + topic = DefaultRejectedTopic.create approved: true + + assert_difference "topic.reload.replies_count", 1 do + topic.approved_replies.create! + end + end + + test 'dangerous association name raises ArgumentError' do + [:errors, 'errors', :save, 'save'].each do |name| + assert_raises(ArgumentError, "Association #{name} should not be allowed") do + Class.new(ActiveRecord::Base) do + has_many name + end + end + end + 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 47592f312e..026a7fe635 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -817,6 +817,13 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase assert author.named_categories(true).include?(category) end + def test_collection_exists + author = authors(:mary) + category = Category.create!(author_ids: [author.id], name: "Primary") + assert category.authors.exists?(id: author.id) + assert category.reload.authors.exists?(id: author.id) + end + def test_collection_delete_with_nonstandard_primary_key_on_belongs_to author = authors(:mary) category = author.named_categories.create(:name => "Primary") diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb index 1f78c73f71..a4650ccdf2 100644 --- a/activerecord/test/cases/associations/has_one_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_associations_test.rb @@ -22,6 +22,13 @@ class HasOneAssociationsTest < ActiveRecord::TestCase assert_equal Account.find(1).credit_limit, companies(:first_firm).account.credit_limit end + def test_has_one_does_not_use_order_by + ActiveRecord::SQLCounter.clear_log + companies(:first_firm).account + ensure + assert ActiveRecord::SQLCounter.log_all.all? { |sql| /order by/i !~ sql }, 'ORDER BY was used in the query' + end + def test_has_one_cache_nils firm = companies(:another_firm) assert_queries(1) { assert_nil firm.account } @@ -549,4 +556,22 @@ class HasOneAssociationsTest < ActiveRecord::TestCase assert_not_nil author.post assert_equal author.post, post end + + def test_has_one_relationship_cannot_have_a_counter_cache + assert_raise(ArgumentError) do + Class.new(ActiveRecord::Base) do + has_one :thing, counter_cache: true + end + end + end + + test 'dangerous association name raises ArgumentError' do + [:errors, 'errors', :save, 'save'].each do |name| + assert_raises(ArgumentError, "Association #{name} should not be allowed") do + Class.new(ActiveRecord::Base) do + has_one name + end + end + end + 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 f2723f2e18..a2725441b3 100644 --- a/activerecord/test/cases/associations/has_one_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_through_associations_test.rb @@ -315,4 +315,12 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase def test_has_one_through_with_custom_select_on_join_model_default_scope assert_equal clubs(:boring_club), members(:groucho).selected_club end + + def test_has_one_through_relationship_cannot_have_a_counter_cache + assert_raise(ArgumentError) do + Class.new(ActiveRecord::Base) do + has_one :thing, through: :other_thing, counter_cache: true + end + end + end end diff --git a/activerecord/test/cases/associations/inner_join_association_test.rb b/activerecord/test/cases/associations/inner_join_association_test.rb index dffee42e7d..a9efa6d86a 100644 --- a/activerecord/test/cases/associations/inner_join_association_test.rb +++ b/activerecord/test/cases/associations/inner_join_association_test.rb @@ -42,7 +42,7 @@ class InnerJoinAssociationTest < ActiveRecord::TestCase end def test_join_association_conditions_support_string_and_arel_expressions - assert_equal 0, Author.joins(:welcome_posts_with_comment).count + assert_equal 0, Author.joins(:welcome_posts_with_one_comment).count assert_equal 1, Author.joins(:welcome_posts_with_comments).count end diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb index 48e6fc5cd4..f663b5490c 100644 --- a/activerecord/test/cases/associations_test.rb +++ b/activerecord/test/cases/associations_test.rb @@ -255,6 +255,15 @@ class AssociationProxyTest < ActiveRecord::TestCase assert_equal man, man.interests.where("1=1").first.man end end + + def test_reset_unloads_target + david = authors(:david) + david.posts.reload + + assert david.posts.loaded? + david.posts.reset + assert !david.posts.loaded? + end end class OverridingAssociationsTest < ActiveRecord::TestCase diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index 9c66ed354e..173081cb28 100644 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -555,6 +555,24 @@ class AttributeMethodsTest < ActiveRecord::TestCase end end + def test_converted_values_are_returned_after_assignment + developer = Developer.new(name: 1337, salary: "50000") + + 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 + + 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 + def test_write_nil_to_time_attributes in_time_zone "Pacific Time (US & Canada)" do record = @target.new @@ -728,19 +746,40 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert "unknown attribute: hello", error.message end - def test_read_attribute_overwrites_private_method_not_considered_implemented - # simulate a model with a db column that shares its name an inherited - # private method (e.g. Object#system) - # - Object.class_eval do - private - def title; "private!"; end + def test_methods_override_in_multi_level_subclass + klass = Class.new(Developer) do + def name + "dev:#{read_attribute(:name)}" + end end - assert !@target.instance_method_already_implemented?(:title) - topic = @target.new - assert_nil topic.title - Object.send(:undef_method, :title) # remove test method from object + 2.times { klass = Class.new klass } + dev = klass.new(name: 'arthurnn') + dev.save! + assert_equal 'dev:arthurnn', dev.reload.name + end + + def test_global_methods_are_overwritten + klass = Class.new(ActiveRecord::Base) do + self.table_name = 'computers' + end + + assert !klass.instance_method_already_implemented?(:system) + computer = klass.new + assert_nil computer.system + end + + def test_global_methods_are_overwritte_when_subclassing + klass = Class.new(ActiveRecord::Base) { self.abstract_class = true } + + subklass = Class.new(klass) do + self.table_name = 'computers' + end + + assert !klass.instance_method_already_implemented?(:system) + assert !subklass.instance_method_already_implemented?(:system) + computer = subklass.new + assert_nil computer.system end def test_instance_method_should_be_defined_on_the_base_class @@ -767,8 +806,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase # that by defining a 'foo' method in the generated methods module for B. # (That module will be inserted between the two, e.g. [B, <GeneratedAttributes>, A].) def test_inherited_custom_accessors - klass = Class.new(ActiveRecord::Base) do - self.table_name = "topics" + klass = new_topic_like_ar_class do self.abstract_class = true def title; "omg"; end def title=(val); self.author_name = val; end @@ -783,8 +821,40 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert_equal "lol", topic.author_name end + def test_on_the_fly_super_invokable_generated_attribute_methods_via_method_missing + klass = new_topic_like_ar_class do + def title + super + '!' + end + end + + real_topic = topics(:first) + assert_equal real_topic.title + '!', klass.find(real_topic.id).title + end + + def test_on_the_fly_super_invokable_generated_predicate_attribute_methods_via_method_missing + klass = new_topic_like_ar_class do + def title? + !super + end + end + + real_topic = topics(:first) + assert_equal !real_topic.title?, klass.find(real_topic.id).title? + end + private + def new_topic_like_ar_class(&block) + klass = Class.new(ActiveRecord::Base) do + self.table_name = 'topics' + class_eval(&block) + end + + assert_empty klass.generated_attribute_methods.instance_methods(false) + klass + end + def cached_columns Topic.columns.map(&:name) - Topic.serialized_attributes.keys end diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb index 517d2674a7..09892d50ba 100644 --- a/activerecord/test/cases/autosave_association_test.rb +++ b/activerecord/test/cases/autosave_association_test.rb @@ -17,8 +17,35 @@ require 'models/tag' require 'models/tagging' require 'models/treasure' require 'models/eye' +require 'models/electron' +require 'models/molecule' class TestAutosaveAssociationsInGeneral < ActiveRecord::TestCase + def test_autosave_validation + person = Class.new(ActiveRecord::Base) { + self.table_name = 'people' + validate :should_be_cool, :on => :create + def self.name; 'Person'; end + + private + + def should_be_cool + unless self.first_name == 'cool' + errors.add :first_name, "not cool" + end + end + } + reference = Class.new(ActiveRecord::Base) { + self.table_name = "references" + def self.name; 'Reference'; end + belongs_to :person, autosave: true, class: person + } + + u = person.create!(first_name: 'cool') + u.update_attributes!(first_name: 'nah') # still valid because validation only applies on 'create' + assert reference.create!(person: u).persisted? + end + def test_should_not_add_the_same_callbacks_multiple_times_for_has_one assert_no_difference_when_adding_callbacks_twice_for Pirate, :ship end @@ -37,10 +64,6 @@ class TestAutosaveAssociationsInGeneral < ActiveRecord::TestCase private - def base - ActiveRecord::Base - end - def assert_no_difference_when_adding_callbacks_twice_for(model, association_name) reflection = model.reflect_on_association(association_name) assert_no_difference "callbacks_for_model(#{model.name}).length" do @@ -49,9 +72,9 @@ class TestAutosaveAssociationsInGeneral < ActiveRecord::TestCase end def callbacks_for_model(model) - model.instance_variables.grep(/_callbacks$/).map do |ivar| + model.instance_variables.grep(/_callbacks$/).flat_map do |ivar| model.instance_variable_get(ivar) - end.flatten + end end end @@ -343,6 +366,33 @@ class TestDefaultAutosaveAssociationOnABelongsToAssociation < ActiveRecord::Test end end +class TestDefaultAutosaveAssociationOnAHasManyAssociationWithAcceptsNestedAttributes < ActiveRecord::TestCase + def test_invalid_adding_with_nested_attributes + molecule = Molecule.new + valid_electron = Electron.new(name: 'electron') + invalid_electron = Electron.new + + molecule.electrons = [valid_electron, invalid_electron] + molecule.save + + assert_not invalid_electron.valid? + assert valid_electron.valid? + assert_not molecule.persisted?, 'Molecule should not be persisted when its electrons are invalid' + end + + def test_valid_adding_with_nested_attributes + molecule = Molecule.new + valid_electron = Electron.new(name: 'electron') + + molecule.electrons = [valid_electron] + molecule.save + + assert valid_electron.valid? + assert molecule.persisted? + assert_equal 1, molecule.electrons.count + end +end + class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCase fixtures :companies, :people @@ -401,7 +451,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 1, companies(:first_firm).clients_of_firm(true).size + assert_equal 2, companies(:first_firm).clients_of_firm(true).size end def test_adding_before_save @@ -455,7 +505,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa company.name += '-changed' assert_queries(2) { assert company.save } assert new_client.persisted? - assert_equal 2, company.clients_of_firm(true).size + assert_equal 3, company.clients_of_firm(true).size end def test_build_many_before_save @@ -464,7 +514,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa company.name += '-changed' assert_queries(3) { assert company.save } - assert_equal 3, company.clients_of_firm(true).size + assert_equal 4, company.clients_of_firm(true).size end def test_build_via_block_before_save @@ -475,7 +525,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa company.name += '-changed' assert_queries(2) { assert company.save } assert new_client.persisted? - assert_equal 2, company.clients_of_firm(true).size + assert_equal 3, company.clients_of_firm(true).size end def test_build_many_via_block_before_save @@ -488,7 +538,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa company.name += '-changed' assert_queries(3) { assert company.save } - assert_equal 3, company.clients_of_firm(true).size + assert_equal 4, company.clients_of_firm(true).size end def test_replace_on_new_object @@ -568,12 +618,19 @@ end class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase self.use_transactional_fixtures = false - def setup - super + setup do @pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?") @ship = @pirate.create_ship(:name => 'Nights Dirty Lightning') end + teardown do + # We are running without transactional fixtures and need to cleanup. + Bird.delete_all + Parrot.delete_all + @ship.delete + @pirate.delete + end + # reload def test_a_marked_for_destruction_record_should_not_be_be_marked_after_reload @pirate.mark_for_destruction @@ -626,10 +683,23 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase end end + @ship.pirate.catchphrase = "Changed Catchphrase" + assert_raise(RuntimeError) { assert !@pirate.save } assert_not_nil @pirate.reload.ship end + def test_should_save_changed_has_one_changed_object_if_child_is_saved + @pirate.ship.name = "NewName" + assert @pirate.save + assert_equal "NewName", @pirate.ship.reload.name + end + + def test_should_not_save_changed_has_one_unchanged_object_if_child_is_saved + @pirate.ship.expects(:save).never + assert @pirate.save + end + # belongs_to def test_should_destroy_a_parent_association_as_part_of_the_save_transaction_if_it_was_marked_for_destroyal assert !@ship.pirate.marked_for_destruction? @@ -1176,15 +1246,15 @@ module AutosaveAssociationOnACollectionAssociationTests end def test_should_default_invalid_error_from_i18n - I18n.backend.store_translations(:en, :activerecord => {:errors => { :models => - { @association_name.to_s.singularize.to_sym => { :blank => "cannot be blank" } } + I18n.backend.store_translations(:en, activerecord: {errors: { models: + { @associated_model_name.to_s.to_sym => { blank: "cannot be blank" } } }}) - @pirate.send(@association_name).build(:name => '') + @pirate.send(@association_name).build(name: '') assert !@pirate.valid? assert_equal ["cannot be blank"], @pirate.errors["#{@association_name}.name"] - assert_equal ["#{@association_name.to_s.titleize} name cannot be blank"], @pirate.errors.full_messages + assert_equal ["#{@association_name.to_s.humanize} name cannot be blank"], @pirate.errors.full_messages assert @pirate.errors[@association_name].empty? ensure I18n.backend = I18n::Backend::Simple.new @@ -1300,6 +1370,7 @@ class TestAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCase def setup super @association_name = :birds + @associated_model_name = :bird @pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?") @child_1 = @pirate.birds.create(:name => 'Posideons Killer') @@ -1314,12 +1385,30 @@ class TestAutosaveAssociationOnAHasAndBelongsToManyAssociation < ActiveRecord::T def setup super + @association_name = :autosaved_parrots + @associated_model_name = :parrot + @habtm = true + + @pirate = Pirate.create(catchphrase: "Don' botharrr talkin' like one, savvy?") + @child_1 = @pirate.parrots.create(name: 'Posideons Killer') + @child_2 = @pirate.parrots.create(name: 'Killer bandita Dionne') + end + + include AutosaveAssociationOnACollectionAssociationTests +end + +class TestAutosaveAssociationOnAHasAndBelongsToManyAssociationWithAcceptsNestedAttributes < ActiveRecord::TestCase + self.use_transactional_fixtures = false unless supports_savepoints? + + def setup + super @association_name = :parrots + @associated_model_name = :parrot @habtm = true - @pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?") - @child_1 = @pirate.parrots.create(:name => 'Posideons Killer') - @child_2 = @pirate.parrots.create(:name => 'Killer bandita Dionne') + @pirate = Pirate.create(catchphrase: "Don' botharrr talkin' like one, savvy?") + @child_1 = @pirate.parrots.create(name: 'Posideons Killer') + @child_2 = @pirate.parrots.create(name: 'Killer bandita Dionne') end include AutosaveAssociationOnACollectionAssociationTests diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index cde188f6c3..6acb342d0b 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -121,6 +121,10 @@ class BasicsTest < ActiveRecord::TestCase assert_equal 1, Topic.limit(1).to_a.length end + def test_limit_should_take_value_from_latest_limit + assert_equal 1, Topic.limit(2).limit(1).to_a.length + end + def test_invalid_limit assert_raises(ArgumentError) do Topic.limit("asdfadf").to_a @@ -208,7 +212,7 @@ class BasicsTest < ActiveRecord::TestCase ) # For adapters which support microsecond resolution. - if current_adapter?(:PostgreSQLAdapter, :SQLite3Adapter) + if current_adapter?(:PostgreSQLAdapter, :SQLite3Adapter) || mysql_56? 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 @@ -317,7 +321,7 @@ class BasicsTest < ActiveRecord::TestCase def test_load topics = Topic.all.merge!(:order => 'id').to_a - assert_equal(4, topics.size) + assert_equal(5, topics.size) assert_equal(topics(:first).title, topics.first.title) end @@ -622,6 +626,7 @@ class BasicsTest < ActiveRecord::TestCase assert_equal ["EUC-JP"], Weird.columns.map {|c| c.name.encoding.name }.uniq ensure silence_warnings { Encoding.default_internal = old_default_internal } + Weird.reset_column_information end end @@ -1125,7 +1130,7 @@ class BasicsTest < ActiveRecord::TestCase k = Class.new(ak) k.table_name = "projects" orig_name = k.sequence_name - return skip "sequences not supported by db" unless orig_name + skip "sequences not supported by db" unless orig_name assert_equal k.reset_sequence_name, orig_name end @@ -1297,9 +1302,11 @@ class BasicsTest < ActiveRecord::TestCase end def test_compute_type_nonexistent_constant - assert_raises NameError do + e = assert_raises NameError do ActiveRecord::Base.send :compute_type, 'NonexistentModel' end + assert_equal 'uninitialized constant ActiveRecord::Base::NonexistentModel', e.message + assert_equal 'ActiveRecord::Base::NonexistentModel', e.name end def test_compute_type_no_method_error @@ -1373,6 +1380,8 @@ class BasicsTest < ActiveRecord::TestCase }) rd, wr = IO.pipe + rd.binmode + wr.binmode ActiveRecord::Base.connection_handler.clear_all_connections! diff --git a/activerecord/test/cases/batches_test.rb b/activerecord/test/cases/batches_test.rb index 38c2560d69..c12fa03015 100644 --- a/activerecord/test/cases/batches_test.rb +++ b/activerecord/test/cases/batches_test.rb @@ -35,6 +35,14 @@ class EachTest < ActiveRecord::TestCase end end + if Enumerator.method_defined? :size + def test_each_should_return_a_sized_enumerator + assert_equal 11, Post.find_each(:batch_size => 1).size + assert_equal 5, Post.find_each(:batch_size => 2, :start => 7).size + assert_equal 11, Post.find_each(:batch_size => 10_000).size + end + end + def test_each_enumerator_should_execute_one_query_per_batch assert_queries(@total + 1) do Post.find_each(:batch_size => 1).with_index do |post, index| @@ -46,7 +54,9 @@ class EachTest < ActiveRecord::TestCase def test_each_should_raise_if_select_is_set_without_id assert_raise(RuntimeError) do - Post.select(:title).find_each(:batch_size => 1) { |post| post } + Post.select(:title).find_each(batch_size: 1) { |post| + flunk "should not call this block" + } end end @@ -151,6 +161,12 @@ class EachTest < ActiveRecord::TestCase assert_equal special_posts_ids, posts.map(&:id) end + def test_find_in_batches_should_not_modify_passed_options + assert_nothing_raised do + Post.find_in_batches({ batch_size: 42, start: 1 }.freeze){} + end + end + def test_find_in_batches_should_use_any_column_as_primary_key nick_order_subscribers = Subscriber.order('nick asc') start_nick = nick_order_subscribers.second.nick @@ -170,4 +186,27 @@ class EachTest < ActiveRecord::TestCase end end end + + def test_find_in_batches_should_return_an_enumerator + enum = nil + assert_queries(0) do + enum = Post.find_in_batches(:batch_size => 1) + end + assert_queries(4) do + enum.first(4) do |batch| + assert_kind_of Array, batch + assert_kind_of Post, batch.first + end + end + end + + if Enumerator.method_defined? :size + def test_find_in_batches_should_return_a_sized_enumerator + assert_equal 11, Post.find_in_batches(:batch_size => 1).size + assert_equal 6, Post.find_in_batches(:batch_size => 2).size + assert_equal 4, Post.find_in_batches(:batch_size => 2, :start => 4).size + assert_equal 4, Post.find_in_batches(:batch_size => 3).size + assert_equal 1, Post.find_in_batches(:batch_size => 10_000).size + end + end end diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb index 2c41656b3d..db999f90ab 100644 --- a/activerecord/test/cases/calculations_test.rb +++ b/activerecord/test/cases/calculations_test.rb @@ -211,6 +211,10 @@ class CalculationsTest < ActiveRecord::TestCase assert_equal 19.83, NumericData.sum(:bank_balance) end + def test_should_return_type_casted_values_with_group_and_expression + assert_equal 0.5, Account.group(:firm_name).sum('0.01 * credit_limit')['37signals'] + end + def test_should_group_by_summed_field_with_conditions c = Account.where('firm_id > 1').group(:firm_id).sum(:credit_limit) assert_nil c[1] @@ -274,7 +278,7 @@ class CalculationsTest < ActiveRecord::TestCase c = Company.group("UPPER(#{QUOTED_TYPE})").count(:all) assert_equal 2, c[nil] assert_equal 1, c['DEPENDENTFIRM'] - assert_equal 4, c['CLIENT'] + assert_equal 5, c['CLIENT'] assert_equal 2, c['FIRM'] end @@ -282,7 +286,7 @@ class CalculationsTest < ActiveRecord::TestCase c = Company.group("UPPER(companies.#{QUOTED_TYPE})").count(:all) assert_equal 2, c[nil] assert_equal 1, c['DEPENDENTFIRM'] - assert_equal 4, c['CLIENT'] + assert_equal 5, c['CLIENT'] assert_equal 2, c['FIRM'] end @@ -462,14 +466,14 @@ class CalculationsTest < ActiveRecord::TestCase def test_distinct_is_honored_when_used_with_count_operation_after_group # Count the number of authors for approved topics approved_topics_count = Topic.group(:approved).count(:author_name)[true] - assert_equal approved_topics_count, 3 + assert_equal approved_topics_count, 4 # Count the number of distinct authors for approved Topics distinct_authors_for_approved_count = Topic.group(:approved).distinct.count(:author_name)[true] - assert_equal distinct_authors_for_approved_count, 2 + assert_equal distinct_authors_for_approved_count, 3 end def test_pluck - assert_equal [1,2,3,4], Topic.order(:id).pluck(:id) + assert_equal [1,2,3,4,5], Topic.order(:id).pluck(:id) end def test_pluck_without_column_names @@ -505,7 +509,7 @@ class CalculationsTest < ActiveRecord::TestCase end def test_pluck_with_qualified_column_name - assert_equal [1,2,3,4], Topic.order(:id).pluck("topics.id") + assert_equal [1,2,3,4,5], Topic.order(:id).pluck("topics.id") end def test_pluck_auto_table_name_prefix @@ -553,11 +557,13 @@ class CalculationsTest < ActiveRecord::TestCase def test_pluck_multiple_columns assert_equal [ [1, "The First Topic"], [2, "The Second Topic of the day"], - [3, "The Third Topic of the day"], [4, "The Fourth Topic of the day"] + [3, "The Third Topic of the day"], [4, "The Fourth Topic of the day"], + [5, "The Fifth Topic of the day"] ], Topic.order(:id).pluck(:id, :title) assert_equal [ [1, "The First Topic", "David"], [2, "The Second Topic of the day", "Mary"], - [3, "The Third Topic of the day", "Carl"], [4, "The Fourth Topic of the day", "Carl"] + [3, "The Third Topic of the day", "Carl"], [4, "The Fourth Topic of the day", "Carl"], + [5, "The Fifth Topic of the day", "Jason"] ], Topic.order(:id).pluck(:id, :title, :author_name) end @@ -583,7 +589,7 @@ class CalculationsTest < ActiveRecord::TestCase def test_pluck_replaces_select_clause taks_relation = Topic.select(:approved, :id).order(:id) - assert_equal [1,2,3,4], taks_relation.pluck(:id) - assert_equal [false, true, true, true], taks_relation.pluck(:approved) + assert_equal [1,2,3,4,5], taks_relation.pluck(:id) + assert_equal [false, true, true, true, true], taks_relation.pluck(:approved) end end diff --git a/activerecord/test/cases/column_definition_test.rb b/activerecord/test/cases/column_definition_test.rb index dbb2f223cd..c7b64f29c3 100644 --- a/activerecord/test/cases/column_definition_test.rb +++ b/activerecord/test/cases/column_definition_test.rb @@ -82,7 +82,7 @@ module ActiveRecord assert_equal "", not_null_text_column.default end - def test_has_default_should_return_false_for_blog_and_test_data_types + def test_has_default_should_return_false_for_blob_and_text_data_types blob_column = MysqlAdapter::Column.new("title", nil, "blob") assert !blob_column.has_default? @@ -116,7 +116,7 @@ module ActiveRecord assert_equal "", not_null_text_column.default end - def test_has_default_should_return_false_for_blog_and_test_data_types + def test_has_default_should_return_false_for_blob_and_text_data_types blob_column = Mysql2Adapter::Column.new("title", nil, "blob") assert !blob_column.has_default? diff --git a/activerecord/test/cases/connection_adapters/connection_handler_test.rb b/activerecord/test/cases/connection_adapters/connection_handler_test.rb index 3e33b30144..599e8c762c 100644 --- a/activerecord/test/cases/connection_adapters/connection_handler_test.rb +++ b/activerecord/test/cases/connection_adapters/connection_handler_test.rb @@ -2,6 +2,126 @@ require "cases/helper" module ActiveRecord module ConnectionAdapters + + class MergeAndResolveDefaultUrlConfigTest < ActiveRecord::TestCase + + def klass + ActiveRecord::ConnectionHandling::MergeAndResolveDefaultUrlConfig + end + + def setup + @previous_database_url = ENV.delete("DATABASE_URL") + end + + def teardown + ENV["DATABASE_URL"] = @previous_database_url + end + + def test_environment_does_not_exist_in_config_url_does_exist + ENV['DATABASE_URL'] = "postgres://localhost/foo" + config = { "not_production" => { "adapter" => "not_postgres", "database" => "not_foo" } } + actual = klass.new(config).resolve + expect_prod = { "adapter"=>"postgresql", "database"=>"foo", "host"=>"localhost" } + assert_equal expect_prod, actual["production"] + end + + def test_string_connection + config = { "production" => "postgres://localhost/foo" } + actual = klass.new(config).resolve + expected = { "production" => + { "adapter" => "postgresql", + "database" => "foo", + "host" => "localhost" + } + } + assert_equal expected, actual + end + + def test_url_sub_key + config = { "production" => { "url" => "postgres://localhost/foo" } } + actual = klass.new(config).resolve + expected = { "production" => + { "adapter" => "postgresql", + "database" => "foo", + "host" => "localhost" + } + } + assert_equal expected, actual + end + + def test_hash + config = { "production" => { "adapter" => "postgres", "database" => "foo" } } + actual = klass.new(config).resolve + assert_equal config, actual + end + + def test_blank + config = {} + actual = klass.new(config).resolve + assert_equal config, actual + end + + def test_blank_with_database_url + ENV['DATABASE_URL'] = "postgres://localhost/foo" + + config = {} + actual = klass.new(config).resolve + expected = { "adapter" => "postgresql", + "database" => "foo", + "host" => "localhost" } + assert_equal expected, actual["production"] + assert_equal expected, actual["development"] + assert_equal expected, actual["test"] + assert_equal nil, actual[:production] + assert_equal nil, actual[:development] + assert_equal nil, actual[:test] + end + + def test_url_sub_key_with_database_url + ENV['DATABASE_URL'] = "NOT-POSTGRES://localhost/NOT_FOO" + + config = { "production" => { "url" => "postgres://localhost/foo" } } + actual = klass.new(config).resolve + expected = { "production" => + { "adapter" => "postgresql", + "database" => "foo", + "host" => "localhost" + } + } + assert_equal expected, actual + end + + def test_merge_no_conflicts_with_database_url + ENV['DATABASE_URL'] = "postgres://localhost/foo" + + config = {"production" => { "pool" => "5" } } + actual = klass.new(config).resolve + expected = { "production" => + { "adapter" => "postgresql", + "database" => "foo", + "host" => "localhost", + "pool" => "5" + } + } + assert_equal expected, actual + end + + def test_merge_conflicts_with_database_url + ENV['DATABASE_URL'] = "postgres://localhost/foo" + + config = {"production" => { "adapter" => "NOT-POSTGRES", "database" => "NOT-FOO", "pool" => "5" } } + actual = klass.new(config).resolve + expected = { "production" => + { "adapter" => "postgresql", + "database" => "foo", + "host" => "localhost", + "pool" => "5" + } + } + assert_equal expected, actual + end + end + class ConnectionHandlerTest < ActiveRecord::TestCase def setup @klass = Class.new(Base) { def self.name; 'klass'; end } diff --git a/activerecord/test/cases/connection_management_test.rb b/activerecord/test/cases/connection_management_test.rb index 00667cc52e..77d9ae9b8e 100644 --- a/activerecord/test/cases/connection_management_test.rb +++ b/activerecord/test/cases/connection_management_test.rb @@ -31,6 +31,8 @@ module ActiveRecord object_id = ActiveRecord::Base.connection.object_id rd, wr = IO.pipe + rd.binmode + wr.binmode pid = fork { rd.close diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb index 2da51ea015..1cf215017b 100644 --- a/activerecord/test/cases/connection_pool_test.rb +++ b/activerecord/test/cases/connection_pool_test.rb @@ -142,7 +142,7 @@ module ActiveRecord connections = @pool.connections.dup connections.each do |conn| - conn.extend(Module.new { def active?; false; end; }) + conn.extend(Module.new { def active_threadsafe?; false; end; }) end @pool.reap diff --git a/activerecord/test/cases/connection_specification/resolver_test.rb b/activerecord/test/cases/connection_specification/resolver_test.rb index c8dfc3244b..fdd1914cba 100644 --- a/activerecord/test/cases/connection_specification/resolver_test.rb +++ b/activerecord/test/cases/connection_specification/resolver_test.rb @@ -4,58 +4,92 @@ module ActiveRecord module ConnectionAdapters class ConnectionSpecification class ResolverTest < ActiveRecord::TestCase - def resolve(spec) - Resolver.new(spec, {}).spec.config + def resolve(spec, config={}) + Resolver.new(config).resolve(spec) + end + + def spec(spec, config={}) + Resolver.new(config).spec(spec) end def test_url_invalid_adapter - assert_raises(LoadError) do - resolve 'ridiculous://foo?encoding=utf8' + error = assert_raises(LoadError) do + spec 'ridiculous://foo?encoding=utf8' end + + assert_match "Could not load 'active_record/connection_adapters/ridiculous_adapter'", error.message end # The abstract adapter is used simply to bypass the bit of code that # checks that the adapter file can be required in. + def test_url_from_environment + spec = resolve :production, 'production' => 'abstract://foo?encoding=utf8' + assert_equal({ + "adapter" => "abstract", + "host" => "foo", + "encoding" => "utf8" }, spec) + end + + def test_url_sub_key + spec = resolve :production, 'production' => {"url" => 'abstract://foo?encoding=utf8'} + assert_equal({ + "adapter" => "abstract", + "host" => "foo", + "encoding" => "utf8" }, spec) + end + + def test_url_sub_key_merges_correctly + hash = {"url" => 'abstract://foo?encoding=utf8&', "adapter" => "sqlite3", "host" => "bar", "pool" => "3"} + spec = resolve :production, 'production' => hash + assert_equal({ + "adapter" => "abstract", + "host" => "foo", + "encoding" => "utf8", + "pool" => "3" }, spec) + end + def test_url_host_no_db spec = resolve 'abstract://foo?encoding=utf8' assert_equal({ - adapter: "abstract", - host: "foo", - encoding: "utf8" }, spec) + "adapter" => "abstract", + "host" => "foo", + "encoding" => "utf8" }, spec) end def test_url_host_db spec = resolve 'abstract://foo/bar?encoding=utf8' assert_equal({ - adapter: "abstract", - database: "bar", - host: "foo", - encoding: "utf8" }, spec) + "adapter" => "abstract", + "database" => "bar", + "host" => "foo", + "encoding" => "utf8" }, spec) end def test_url_port spec = resolve 'abstract://foo:123?encoding=utf8' assert_equal({ - adapter: "abstract", - port: 123, - host: "foo", - encoding: "utf8" }, spec) + "adapter" => "abstract", + "port" => 123, + "host" => "foo", + "encoding" => "utf8" }, spec) end def test_encoded_password password = 'am@z1ng_p@ssw0rd#!' encoded_password = URI.encode_www_form_component(password) spec = resolve "abstract://foo:#{encoded_password}@localhost/bar" - assert_equal password, spec[:password] + assert_equal password, spec["password"] end - def test_descriptive_error_message_when_adapter_is_missing - error = assert_raise(LoadError) do - resolve(adapter: 'non-existing') - end + def test_url_host_db_for_sqlite3 + spec = resolve 'sqlite3://foo:bar@dburl:9000/foo_test' + assert_equal('/foo_test', spec["database"]) + end - assert_match "Could not load 'active_record/connection_adapters/non-existing_adapter'", error.message + def test_url_host_memory_db_for_sqlite3 + spec = resolve 'sqlite3://foo:bar@dburl:9000/:memory:' + assert_equal(':memory:', spec["database"]) end end end diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb index 9d7f57bf85..df4183c065 100644 --- a/activerecord/test/cases/dirty_test.rb +++ b/activerecord/test/cases/dirty_test.rb @@ -584,6 +584,14 @@ class DirtyTest < ActiveRecord::TestCase end end + def test_datetime_attribute_doesnt_change_if_zone_is_modified_in_string + time_in_paris = Time.utc(2014, 1, 1, 12, 0, 0).in_time_zone('Paris') + pirate = Pirate.create!(:catchphrase => 'rrrr', :created_on => time_in_paris) + + pirate.created_on = pirate.created_on.in_time_zone('Tokyo').to_s + assert !pirate.created_on_changed? + end + test "partial insert" do with_partial_writes Person do jon = nil diff --git a/activerecord/test/cases/enum_test.rb b/activerecord/test/cases/enum_test.rb index 35e8a98156..f8ebd7caee 100644 --- a/activerecord/test/cases/enum_test.rb +++ b/activerecord/test/cases/enum_test.rb @@ -16,7 +16,7 @@ class EnumTest < ActiveRecord::TestCase assert @book.unread? end - test "query state with symbol" do + test "query state with strings" do assert_equal "proposed", @book.status assert_equal "unread", @book.read_status end @@ -51,6 +51,77 @@ class EnumTest < ActiveRecord::TestCase assert @book.written? end + test "enum changed attributes" do + old_status = @book.status + @book.status = :published + assert_equal old_status, @book.changed_attributes[:status] + end + + test "enum changes" do + old_status = @book.status + @book.status = :published + assert_equal [old_status, 'published'], @book.changes[:status] + end + + test "enum attribute was" do + old_status = @book.status + @book.status = :published + assert_equal old_status, @book.attribute_was(:status) + end + + test "enum attribute changed" do + @book.status = :published + assert @book.attribute_changed?(:status) + end + + test "enum attribute changed to" do + @book.status = :published + assert @book.attribute_changed?(:status, to: 'published') + end + + test "enum attribute changed from" do + old_status = @book.status + @book.status = :published + assert @book.attribute_changed?(:status, from: old_status) + end + + test "enum attribute changed from old status to new status" do + old_status = @book.status + @book.status = :published + assert @book.attribute_changed?(:status, from: old_status, to: 'published') + end + + test "enum didn't change" do + old_status = @book.status + @book.status = old_status + assert_not @book.attribute_changed?(:status) + end + + test "persist changes that are dirty" do + @book.status = :published + assert @book.attribute_changed?(:status) + @book.status = :written + assert @book.attribute_changed?(:status) + end + + test "reverted changes that are not dirty" do + old_status = @book.status + @book.status = :published + assert @book.attribute_changed?(:status) + @book.status = old_status + assert_not @book.attribute_changed?(:status) + end + + test "reverted changes are not dirty going from nil to value and back" do + book = Book.create!(nullable_status: nil) + + book.nullable_status = :married + assert book.attribute_changed?(:nullable_status) + + book.nullable_status = nil + assert_not book.attribute_changed?(:nullable_status) + end + test "assign non existing value raises an error" do e = assert_raises(ArgumentError) do @book.status = :unknown @@ -58,9 +129,124 @@ class EnumTest < ActiveRecord::TestCase assert_equal "'unknown' is not a valid status", e.message end + test "assign nil value" do + @book.status = nil + assert @book.status.nil? + end + + test "assign empty string value" do + @book.status = '' + assert @book.status.nil? + end + + test "assign long empty string value" do + @book.status = ' ' + assert @book.status.nil? + end + test "constant to access the mapping" do - assert_equal 0, Book::STATUS[:proposed] - assert_equal 1, Book::STATUS["written"] - assert_equal 2, Book::STATUS[:published] + assert_equal 0, Book.statuses[:proposed] + assert_equal 1, Book.statuses["written"] + assert_equal 2, Book.statuses[:published] + end + + test "building new objects with enum scopes" do + assert Book.written.build.written? + assert Book.read.build.read? + end + + test "creating new objects with enum scopes" do + assert Book.written.create.written? + assert Book.read.create.read? + end + + test "_before_type_cast returns the enum label (required for form fields)" do + assert_equal "proposed", @book.status_before_type_cast + end + + test "reserved enum names" do + klass = Class.new(ActiveRecord::Base) do + self.table_name = "books" + enum status: [:proposed, :written, :published] + end + + conflicts = [ + :column, # generates class method .columns, which conflicts with an AR method + :logger, # generates #logger, which conflicts with an AR method + :attributes, # generates #attributes=, which conflicts with an AR method + ] + + conflicts.each_with_index do |name, i| + assert_raises(ArgumentError, "enum name `#{name}` should not be allowed") do + klass.class_eval { enum name => ["value_#{i}"] } + end + end + end + + test "reserved enum values" do + klass = Class.new(ActiveRecord::Base) do + self.table_name = "books" + enum status: [:proposed, :written, :published] + end + + conflicts = [ + :new, # generates a scope that conflicts with an AR class method + :valid, # generates #valid?, which conflicts with an AR method + :save, # generates #save!, which conflicts with an AR method + :proposed, # same value as an existing enum + ] + + conflicts.each_with_index do |value, i| + assert_raises(ArgumentError, "enum value `#{value}` should not be allowed") do + klass.class_eval { enum "status_#{i}" => [value] } + end + end + end + + test "overriding enum method should not raise" do + assert_nothing_raised do + Class.new(ActiveRecord::Base) do + self.table_name = "books" + + def published! + super + "do publish work..." + end + + enum status: [:proposed, :written, :published] + + def written! + super + "do written work..." + end + end + end + end + + test "validate uniqueness" do + klass = Class.new(ActiveRecord::Base) do + def self.name; 'Book'; end + enum status: [:proposed, :written] + validates_uniqueness_of :status + end + klass.delete_all + klass.create!(status: "proposed") + book = klass.new(status: "written") + assert book.valid? + book.status = "proposed" + assert_not book.valid? + end + + test "validate inclusion of value in array" do + klass = Class.new(ActiveRecord::Base) do + def self.name; 'Book'; end + enum status: [:proposed, :written] + validates_inclusion_of :status, in: ["written"] + end + klass.delete_all + invalid_book = klass.new(status: "proposed") + assert_not invalid_book.valid? + valid_book = klass.new(status: "written") + assert valid_book.valid? end end diff --git a/activerecord/test/cases/finder_respond_to_test.rb b/activerecord/test/cases/finder_respond_to_test.rb index 3ff22f222f..6ab2657c44 100644 --- a/activerecord/test/cases/finder_respond_to_test.rb +++ b/activerecord/test/cases/finder_respond_to_test.rb @@ -5,6 +5,11 @@ class FinderRespondToTest < ActiveRecord::TestCase fixtures :topics + def test_should_preserve_normal_respond_to_behaviour_on_base + assert_respond_to ActiveRecord::Base, :new + assert !ActiveRecord::Base.respond_to?(:find_by_something) + end + def test_should_preserve_normal_respond_to_behaviour_and_respond_to_newly_added_method class << Topic; self; end.send(:define_method, :method_added_for_finder_respond_to_test) { } assert_respond_to Topic, :method_added_for_finder_respond_to_test diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index 735f804184..98b1dafdf0 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -12,6 +12,7 @@ require 'models/developer' require 'models/customer' require 'models/toy' require 'models/matey' +require 'models/dog' class FinderTest < ActiveRecord::TestCase fixtures :companies, :topics, :entrants, :developers, :developers_projects, :posts, :comments, :accounts, :authors, :customers, :categories, :categorizations @@ -57,15 +58,25 @@ class FinderTest < ActiveRecord::TestCase assert_equal false, Topic.exists?(45) assert_equal false, Topic.exists?(Topic.new) - begin - assert_equal false, Topic.exists?("foo") - rescue ActiveRecord::StatementInvalid - # PostgreSQL complains about string comparison with integer field - rescue Exception - flunk + assert_raise(NoMethodError) { Topic.exists?([1,2]) } + end + + def test_exists_fails_when_parameter_has_invalid_type + if current_adapter?(:PostgreSQLAdapter, :MysqlAdapter) + assert_raises ActiveRecord::StatementInvalid do + Topic.exists?(("9"*53).to_i) # number that's bigger than int + end + else + assert_equal false, Topic.exists?(("9"*53).to_i) # number that's bigger than int end - assert_raise(NoMethodError) { Topic.exists?([1,2]) } + if current_adapter?(:PostgreSQLAdapter) + assert_raises ActiveRecord::StatementInvalid do + Topic.exists?("foo") + end + else + assert_equal false, Topic.exists?("foo") + end end def test_exists_does_not_select_columns_without_alias @@ -253,6 +264,94 @@ class FinderTest < ActiveRecord::TestCase end end + def test_second + assert_equal topics(:second).title, Topic.second.title + end + + def test_second_with_offset + assert_equal topics(:fifth), Topic.offset(3).second + end + + def test_second_have_primary_key_order_by_default + expected = topics(:second) + expected.touch # PostgreSQL changes the default order if no order clause is used + assert_equal expected, Topic.second + end + + def test_model_class_responds_to_second_bang + assert Topic.second! + Topic.delete_all + assert_raises ActiveRecord::RecordNotFound do + Topic.second! + end + end + + def test_third + assert_equal topics(:third).title, Topic.third.title + end + + def test_third_with_offset + assert_equal topics(:fifth), Topic.offset(2).third + end + + def test_third_have_primary_key_order_by_default + expected = topics(:third) + expected.touch # PostgreSQL changes the default order if no order clause is used + assert_equal expected, Topic.third + end + + def test_model_class_responds_to_third_bang + assert Topic.third! + Topic.delete_all + assert_raises ActiveRecord::RecordNotFound do + Topic.third! + end + end + + def test_fourth + assert_equal topics(:fourth).title, Topic.fourth.title + end + + def test_fourth_with_offset + assert_equal topics(:fifth), Topic.offset(1).fourth + end + + def test_fourth_have_primary_key_order_by_default + expected = topics(:fourth) + expected.touch # PostgreSQL changes the default order if no order clause is used + assert_equal expected, Topic.fourth + end + + def test_model_class_responds_to_fourth_bang + assert Topic.fourth! + Topic.delete_all + assert_raises ActiveRecord::RecordNotFound do + Topic.fourth! + end + end + + def test_fifth + assert_equal topics(:fifth).title, Topic.fifth.title + end + + def test_fifth_with_offset + assert_equal topics(:fifth), Topic.offset(0).fifth + end + + def test_fifth_have_primary_key_order_by_default + expected = topics(:fifth) + expected.touch # PostgreSQL changes the default order if no order clause is used + assert_equal expected, Topic.fifth + end + + def test_model_class_responds_to_fifth_bang + assert Topic.fifth! + Topic.delete_all + assert_raises ActiveRecord::RecordNotFound do + Topic.fifth! + end + end + def test_last_bang_present assert_nothing_raised do assert_equal topics(:second), Topic.where("title = 'The Second Topic of the day'").last! @@ -266,7 +365,7 @@ class FinderTest < ActiveRecord::TestCase end def test_model_class_responds_to_last_bang - assert_equal topics(:fourth), Topic.last! + assert_equal topics(:fifth), Topic.last! assert_raises ActiveRecord::RecordNotFound do Topic.delete_all Topic.last! @@ -641,6 +740,13 @@ class FinderTest < ActiveRecord::TestCase assert_raise(ActiveRecord::RecordNotFound) { Topic.find_by_title!("The First Topic!") } end + def test_find_by_on_attribute_that_is_a_reserved_word + dog_alias = 'Dog' + dog = Dog.create(alias: dog_alias) + + assert_equal dog, Dog.find_by_alias(dog_alias) + end + def test_find_by_one_attribute_that_is_an_alias assert_equal topics(:first), Topic.find_by_heading("The First Topic") assert_nil Topic.find_by_heading("The First Topic!") @@ -720,6 +826,15 @@ class FinderTest < ActiveRecord::TestCase assert_raise(ArgumentError) { Topic.find_by_title_and_author_name("The First Topic") } end + def test_find_last_with_offset + devs = Developer.order('id') + + assert_equal devs[2], Developer.offset(2).first + assert_equal devs[-3], Developer.offset(2).last + assert_equal devs[-3], Developer.offset(2).last + assert_equal devs[-3], Developer.offset(2).order('id DESC').first + end + def test_find_by_nil_attribute topic = Topic.find_by_last_read nil assert_not_nil topic @@ -795,8 +910,8 @@ class FinderTest < ActiveRecord::TestCase end def test_select_values - assert_equal ["1","2","3","4","5","6","7","8","9", "10"], Company.connection.select_values("SELECT id FROM companies ORDER BY id").map! { |i| i.to_s } - assert_equal ["37signals","Summit","Microsoft", "Flamboyant Software", "Ex Nihilo", "RailsCore", "Leetsoft", "Jadedpixel", "Odegy", "Ex Nihilo Part Deux"], Company.connection.select_values("SELECT name FROM companies ORDER BY id") + assert_equal ["1","2","3","4","5","6","7","8","9", "10", "11"], Company.connection.select_values("SELECT id FROM companies ORDER BY id").map! { |i| i.to_s } + assert_equal ["37signals","Summit","Microsoft", "Flamboyant Software", "Ex Nihilo", "RailsCore", "Leetsoft", "Jadedpixel", "Odegy", "Ex Nihilo Part Deux", "Apex"], Company.connection.select_values("SELECT name FROM companies ORDER BY id") end def test_select_rows @@ -837,10 +952,8 @@ class FinderTest < ActiveRecord::TestCase end def test_with_limiting_with_custom_select - skip 'broken test' if current_adapter?(:MysqlAdapter) - posts = Post.references(:authors).merge( - :includes => :author, :select => ' posts.*, authors.id as "author_id"', + :includes => :author, :select => 'posts.*, authors.id as "author_id"', :limit => 3, :order => 'posts.id' ).to_a assert_equal 3, posts.size @@ -848,14 +961,23 @@ class FinderTest < ActiveRecord::TestCase end def test_find_one_message_with_custom_primary_key - Toy.primary_key = :name - begin - Toy.find 'Hello World!' - rescue ActiveRecord::RecordNotFound => e - assert_equal 'Couldn\'t find Toy with name=Hello World!', e.message + table_with_custom_primary_key do |model| + model.primary_key = :name + e = assert_raises(ActiveRecord::RecordNotFound) do + model.find 'Hello World!' + end + assert_equal %Q{Couldn't find MercedesCar with 'name'=Hello World!}, e.message + end + end + + def test_find_some_message_with_custom_primary_key + table_with_custom_primary_key do |model| + model.primary_key = :name + e = assert_raises(ActiveRecord::RecordNotFound) do + model.find 'Hello', 'World!' + end + assert_equal %Q{Couldn't find all MercedesCars with 'name': (Hello, World!) (found 0 results, but was looking for 2)}, e.message end - ensure - Toy.reset_primary_key end def test_find_without_primary_key @@ -877,10 +999,11 @@ class FinderTest < ActiveRecord::TestCase end end - def with_env_tz(new_tz = 'US/Eastern') - old_tz, ENV['TZ'] = ENV['TZ'], new_tz - yield - ensure - old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ') + def table_with_custom_primary_key + yield(Class.new(Toy) do + def self.name + 'MercedesCar' + end + end) end end diff --git a/activerecord/test/cases/fixture_set/file_test.rb b/activerecord/test/cases/fixture_set/file_test.rb index a029fedbd3..92efa8aca7 100644 --- a/activerecord/test/cases/fixture_set/file_test.rb +++ b/activerecord/test/cases/fixture_set/file_test.rb @@ -68,6 +68,61 @@ module ActiveRecord end end + def test_render_context_helper + ActiveRecord::FixtureSet.context_class.class_eval do + def fixture_helper + "Fixture helper" + end + end + yaml = "one:\n name: <%= fixture_helper %>\n" + tmp_yaml ['curious', 'yml'], yaml do |t| + golden = + [["one", {"name" => "Fixture helper"}]] + assert_equal golden, File.open(t.path) { |fh| fh.to_a } + end + ActiveRecord::FixtureSet.context_class.class_eval do + remove_method :fixture_helper + end + end + + def test_render_context_lookup_scope + yaml = <<END +one: + ActiveRecord: <%= defined? ActiveRecord %> + ActiveRecord_FixtureSet: <%= defined? ActiveRecord::FixtureSet %> + FixtureSet: <%= defined? FixtureSet %> + ActiveRecord_FixtureSet_File: <%= defined? ActiveRecord::FixtureSet::File %> + File: <%= File.name %> +END + + golden = [['one', { + 'ActiveRecord' => 'constant', + 'ActiveRecord_FixtureSet' => 'constant', + 'FixtureSet' => nil, + 'ActiveRecord_FixtureSet_File' => 'constant', + 'File' => 'File' + }]] + + tmp_yaml ['curious', 'yml'], yaml do |t| + assert_equal golden, File.open(t.path) { |fh| fh.to_a } + end + end + + # Make sure that each fixture gets its own rendering context so that + # fixtures are independent. + def test_independent_render_contexts + yaml1 = "<% def leaked_method; 'leak'; end %>\n" + yaml2 = "one:\n name: <%= leaked_method %>\n" + tmp_yaml ['leaky', 'yml'], yaml1 do |t1| + tmp_yaml ['curious', 'yml'], yaml2 do |t2| + File.open(t1.path) { |fh| fh.to_a } + assert_raises(NameError) do + File.open(t2.path) { |fh| fh.to_a } + end + end + 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 f3a4887a85..37c6af74da 100644 --- a/activerecord/test/cases/fixtures_test.rb +++ b/activerecord/test/cases/fixtures_test.rb @@ -628,7 +628,9 @@ class LoadAllFixturesTest < ActiveRecord::TestCase self.class.fixture_path = FIXTURES_ROOT + "/all" self.class.fixtures :all - assert_equal %w(admin/accounts admin/users developers people tasks), fixture_table_names.sort + if File.symlink? FIXTURES_ROOT + "/all/admin" + assert_equal %w(admin/accounts admin/users developers people tasks), fixture_table_names.sort + end ensure ActiveRecord::FixtureSet.reset_cache end @@ -639,7 +641,9 @@ class LoadAllFixturesWithPathnameTest < ActiveRecord::TestCase self.class.fixture_path = Pathname.new(FIXTURES_ROOT).join('all') self.class.fixtures :all - assert_equal %w(admin/accounts admin/users developers people tasks), fixture_table_names.sort + if File.symlink? FIXTURES_ROOT + "/all/admin" + assert_equal %w(admin/accounts admin/users developers people tasks), fixture_table_names.sort + end ensure ActiveRecord::FixtureSet.reset_cache end diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb index 34e8f1be0f..8a49dfbb44 100644 --- a/activerecord/test/cases/helper.rb +++ b/activerecord/test/cases/helper.rb @@ -20,6 +20,9 @@ Thread.abort_on_exception = true # Show backtraces for deprecated behavior for quicker cleanup. ActiveSupport::Deprecation.debug = true +# Disable available locale checks to avoid warnings running the test suite. +I18n.enforce_available_locales = false + # Connect to the database ARTest.connect @@ -38,6 +41,11 @@ def in_memory_db? ActiveRecord::Base.connection_pool.spec.config[:database] == ":memory:" end +def mysql_56? + current_adapter?(:Mysql2Adapter) && + ActiveRecord::Base.connection.send(:version).join(".") >= "5.6.0" +end + def supports_savepoints? ActiveRecord::Base.connection.supports_savepoints? end @@ -149,23 +157,6 @@ end load_schema -class << Time - unless method_defined? :now_before_time_travel - alias_method :now_before_time_travel, :now - end - - def now - (@now ||= nil) || now_before_time_travel - end - - def travel_to(time, &block) - @now = time - block.call - ensure - @now = nil - end -end - class SQLSubscriber attr_reader :logged attr_reader :payloads @@ -183,7 +174,6 @@ class SQLSubscriber def finish(name, id, payload); end end - module InTimeZone private diff --git a/activerecord/test/cases/hot_compatibility_test.rb b/activerecord/test/cases/hot_compatibility_test.rb index 96e581ab4c..367d04a154 100644 --- a/activerecord/test/cases/hot_compatibility_test.rb +++ b/activerecord/test/cases/hot_compatibility_test.rb @@ -5,7 +5,7 @@ class HotCompatibilityTest < ActiveRecord::TestCase setup do @klass = Class.new(ActiveRecord::Base) do - connection.create_table :hot_compatibilities do |t| + connection.create_table :hot_compatibilities, force: true do |t| t.string :foo t.string :bar end diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb index 73cf99a5d7..e2ff2aa451 100644 --- a/activerecord/test/cases/inheritance_test.rb +++ b/activerecord/test/cases/inheritance_test.rb @@ -1,10 +1,11 @@ -require "cases/helper" +require 'cases/helper' require 'models/company' require 'models/person' require 'models/post' require 'models/project' require 'models/subscriber' require 'models/vegetables' +require 'models/shop' class InheritanceTest < ActiveRecord::TestCase fixtures :companies, :projects, :subscribers, :accounts, :vegetables @@ -128,6 +129,17 @@ class InheritanceTest < ActiveRecord::TestCase assert_kind_of Cabbage, cabbage end + def test_alt_becomes_bang_resets_inheritance_type_column + vegetable = Vegetable.create!(name: "Red Pepper") + assert_nil vegetable.custom_type + + cabbage = vegetable.becomes!(Cabbage) + assert_equal "Cabbage", cabbage.custom_type + + vegetable = cabbage.becomes!(Vegetable) + assert_nil cabbage.custom_type + end + def test_inheritance_find_all companies = Company.all.merge!(:order => 'id').to_a assert_kind_of Firm, companies[0], "37signals should be a firm" @@ -176,14 +188,14 @@ class InheritanceTest < ActiveRecord::TestCase e = assert_raises(NotImplementedError) do AbstractCompany.new end - assert_equal("AbstractCompany is an abstract class and can not be instantiated.", e.message) + assert_equal("AbstractCompany is an abstract class and cannot be instantiated.", e.message) end def test_new_with_ar_base e = assert_raises(NotImplementedError) do ActiveRecord::Base.new end - assert_equal("ActiveRecord::Base is an abstract class and can not be instantiated.", e.message) + assert_equal("ActiveRecord::Base is an abstract class and cannot be instantiated.", e.message) end def test_new_with_invalid_type @@ -210,9 +222,9 @@ class InheritanceTest < ActiveRecord::TestCase end def test_inheritance_condition - assert_equal 10, Company.count + assert_equal 11, Company.count assert_equal 2, Firm.count - assert_equal 4, Client.count + assert_equal 5, Client.count end def test_alt_inheritance_condition @@ -356,4 +368,10 @@ class InheritanceComputeTypeTest < ActiveRecord::TestCase ensure ActiveRecord::Base.store_full_sti_class = true end + + def test_sti_type_from_attributes_disabled_in_non_sti_class + phone = Shop::Product::Type.new(name: 'Phone') + product = Shop::Product.new(:type => phone) + assert product.save + end end diff --git a/activerecord/test/cases/integration_test.rb b/activerecord/test/cases/integration_test.rb index 07ffcef875..dfb8a608cb 100644 --- a/activerecord/test/cases/integration_test.rb +++ b/activerecord/test/cases/integration_test.rb @@ -1,12 +1,13 @@ +# encoding: utf-8 + require 'cases/helper' require 'models/company' require 'models/developer' -require 'models/car' -require 'models/bulb' require 'models/owner' +require 'models/pet' class IntegrationTest < ActiveRecord::TestCase - fixtures :companies, :developers, :owners + fixtures :companies, :developers, :owners, :pets def test_to_param_should_return_string assert_kind_of String, Client.first.to_param @@ -46,6 +47,12 @@ class IntegrationTest < ActiveRecord::TestCase assert_equal '4-ab-ab-ab-ab-ab-ab', firm.to_param end + def test_to_param_class_method_multibyte_character + firm = Firm.find(4) + firm.name = "戦場ヶ原 ひたぎ" + assert_equal '4', firm.to_param + end + def test_to_param_class_method_uses_default_if_blank firm = Firm.find(4) firm.name = nil @@ -83,13 +90,14 @@ class IntegrationTest < ActiveRecord::TestCase end def test_cache_key_changes_when_child_touched - car = Car.create - Bulb.create(car: car) + owner = owners(:blackbeard) + pet = pets(:parrot) + + owner.update_column :updated_at, Time.current + key = owner.cache_key - key = car.cache_key - car.bulb.touch - car.reload - assert_not_equal key, car.cache_key + assert pet.touch + assert_not_equal key, owner.reload.cache_key end def test_cache_key_format_for_existing_record_with_nil_updated_timestamps diff --git a/activerecord/test/cases/invertible_migration_test.rb b/activerecord/test/cases/invertible_migration_test.rb index 428145d00b..2b1749cf70 100644 --- a/activerecord/test/cases/invertible_migration_test.rb +++ b/activerecord/test/cases/invertible_migration_test.rb @@ -106,6 +106,22 @@ module ActiveRecord end end + class RevertNamedIndexMigration1 < SilentMigration + def change + create_table("horses") do |t| + t.column :content, :string + t.column :remind_at, :datetime + end + add_index :horses, :content + end + end + + class RevertNamedIndexMigration2 < SilentMigration + def change + add_index :horses, :content, name: "horses_index_named" + end + end + def teardown %w[horses new_horses].each do |table| if ActiveRecord::Base.connection.table_exists?(table) @@ -255,5 +271,20 @@ module ActiveRecord ActiveRecord::Base.table_name_prefix = ActiveRecord::Base.table_name_suffix = '' end + # MySQL 5.7 and Oracle do not allow to create duplicate indexes on the same columns + unless current_adapter?(:MysqlAdapter, :Mysql2Adapter, :OracleAdapter) + def test_migrate_revert_add_index_with_name + RevertNamedIndexMigration1.new.migrate(:up) + RevertNamedIndexMigration2.new.migrate(:up) + RevertNamedIndexMigration2.new.migrate(:down) + + connection = ActiveRecord::Base.connection + assert connection.index_exists?(:horses, :content), + "index on content should exist" + assert !connection.index_exists?(:horses, :content, name: "horses_index_named"), + "horses_index_named index should not exist" + end + end + end end diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb index a16ed963fe..c373dc1511 100644 --- a/activerecord/test/cases/locking_test.rb +++ b/activerecord/test/cases/locking_test.rb @@ -431,6 +431,17 @@ unless current_adapter?(:SybaseAdapter, :OpenBaseAdapter) || in_memory_db? assert_equal old, person.reload.first_name end + if current_adapter?(:PostgreSQLAdapter) + def test_lock_sending_custom_lock_statement + Person.transaction do + person = Person.find(1) + assert_sql(/LIMIT 1 FOR SHARE NOWAIT/) do + person.lock!('FOR SHARE NOWAIT') + end + end + end + end + if current_adapter?(:PostgreSQLAdapter, :OracleAdapter) def test_no_locks_no_wait first, second = duel { Person.find 1 } diff --git a/activerecord/test/cases/migration/change_schema_test.rb b/activerecord/test/cases/migration/change_schema_test.rb index e43e256d24..294f2eb9fe 100644 --- a/activerecord/test/cases/migration/change_schema_test.rb +++ b/activerecord/test/cases/migration/change_schema_test.rb @@ -308,6 +308,22 @@ module ActiveRecord assert_equal 2000, connection.select_values("SELECT money FROM testings").first.to_i end + def test_change_column_null + testing_table_with_only_foo_attribute do + notnull_migration = Class.new(ActiveRecord::Migration) do + def change + change_column_null :testings, :foo, false + end + end + notnull_migration.new.suppress_messages do + notnull_migration.migrate(:up) + assert_equal false, connection.columns(:testings).find{ |c| c.name == "foo"}.null + notnull_migration.migrate(:down) + assert connection.columns(:testings).find{ |c| c.name == "foo"}.null + end + end + end + def test_column_exists connection.create_table :testings do |t| t.column :foo, :string diff --git a/activerecord/test/cases/migration/change_table_test.rb b/activerecord/test/cases/migration/change_table_test.rb index 8065541bfe..c1d7cd5874 100644 --- a/activerecord/test/cases/migration/change_table_test.rb +++ b/activerecord/test/cases/migration/change_table_test.rb @@ -5,7 +5,7 @@ module ActiveRecord class Migration class TableTest < ActiveRecord::TestCase def setup - @connection = MiniTest::Mock.new + @connection = Minitest::Mock.new end def teardown diff --git a/activerecord/test/cases/migration/command_recorder_test.rb b/activerecord/test/cases/migration/command_recorder_test.rb index 1b205d372f..a925cf4c05 100644 --- a/activerecord/test/cases/migration/command_recorder_test.rb +++ b/activerecord/test/cases/migration/command_recorder_test.rb @@ -4,7 +4,8 @@ module ActiveRecord class Migration class CommandRecorderTest < ActiveRecord::TestCase def setup - @recorder = CommandRecorder.new + connection = ActiveRecord::Base.connection + @recorder = CommandRecorder.new(connection) end def test_respond_to_delegates @@ -173,13 +174,13 @@ module ActiveRecord end def test_invert_add_index - remove = @recorder.inverse_of :add_index, [:table, [:one, :two], options: true] - assert_equal [:remove_index, [:table, {column: [:one, :two], options: true}]], remove + remove = @recorder.inverse_of :add_index, [:table, [:one, :two]] + assert_equal [:remove_index, [:table, {column: [:one, :two]}]], remove end def test_invert_add_index_with_name remove = @recorder.inverse_of :add_index, [:table, [:one, :two], name: "new_index"] - assert_equal [:remove_index, [:table, {column: [:one, :two], name: "new_index"}]], remove + assert_equal [:remove_index, [:table, {name: "new_index"}]], remove end def test_invert_add_index_with_no_options diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index 7c3988859f..1bda472d23 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -79,6 +79,10 @@ class MigrationTest < ActiveRecord::TestCase ActiveRecord::Migrator.migrations_paths = old_path end + def test_migration_version + ActiveRecord::Migrator.run(:up, MIGRATIONS_ROOT + "/version_check", 20131219224947) + end + def test_create_table_with_force_true_does_not_drop_nonexisting_table if Person.connection.table_exists?(:testings2) Person.connection.drop_table :testings2 @@ -324,6 +328,7 @@ class MigrationTest < ActiveRecord::TestCase end def test_proper_table_name_on_migrator + reminder_class = new_isolated_reminder_class assert_deprecated do assert_equal "table", ActiveRecord::Migrator.proper_table_name('table') end @@ -331,30 +336,30 @@ class MigrationTest < ActiveRecord::TestCase assert_equal "table", ActiveRecord::Migrator.proper_table_name(:table) end assert_deprecated do - assert_equal "reminders", ActiveRecord::Migrator.proper_table_name(Reminder) + assert_equal "reminders", ActiveRecord::Migrator.proper_table_name(reminder_class) end - Reminder.reset_table_name + reminder_class.reset_table_name assert_deprecated do - assert_equal Reminder.table_name, ActiveRecord::Migrator.proper_table_name(Reminder) + assert_equal reminder_class.table_name, ActiveRecord::Migrator.proper_table_name(reminder_class) end # Use the model's own prefix/suffix if a model is given ActiveRecord::Base.table_name_prefix = "ARprefix_" ActiveRecord::Base.table_name_suffix = "_ARsuffix" - Reminder.table_name_prefix = 'prefix_' - Reminder.table_name_suffix = '_suffix' - Reminder.reset_table_name + reminder_class.table_name_prefix = 'prefix_' + reminder_class.table_name_suffix = '_suffix' + reminder_class.reset_table_name assert_deprecated do - assert_equal "prefix_reminders_suffix", ActiveRecord::Migrator.proper_table_name(Reminder) + assert_equal "prefix_reminders_suffix", ActiveRecord::Migrator.proper_table_name(reminder_class) end - Reminder.table_name_prefix = '' - Reminder.table_name_suffix = '' - Reminder.reset_table_name + reminder_class.table_name_prefix = '' + reminder_class.table_name_suffix = '' + reminder_class.reset_table_name # Use AR::Base's prefix/suffix if string or symbol is given ActiveRecord::Base.table_name_prefix = "prefix_" ActiveRecord::Base.table_name_suffix = "_suffix" - Reminder.reset_table_name + reminder_class.reset_table_name assert_deprecated do assert_equal "prefix_table_suffix", ActiveRecord::Migrator.proper_table_name('table') end @@ -364,28 +369,29 @@ class MigrationTest < ActiveRecord::TestCase end def test_proper_table_name_on_migration + reminder_class = new_isolated_reminder_class migration = ActiveRecord::Migration.new assert_equal "table", migration.proper_table_name('table') assert_equal "table", migration.proper_table_name(:table) - assert_equal "reminders", migration.proper_table_name(Reminder) - Reminder.reset_table_name - assert_equal Reminder.table_name, migration.proper_table_name(Reminder) + assert_equal "reminders", migration.proper_table_name(reminder_class) + reminder_class.reset_table_name + assert_equal reminder_class.table_name, migration.proper_table_name(reminder_class) # Use the model's own prefix/suffix if a model is given ActiveRecord::Base.table_name_prefix = "ARprefix_" ActiveRecord::Base.table_name_suffix = "_ARsuffix" - Reminder.table_name_prefix = 'prefix_' - Reminder.table_name_suffix = '_suffix' - Reminder.reset_table_name - assert_equal "prefix_reminders_suffix", migration.proper_table_name(Reminder) - Reminder.table_name_prefix = '' - Reminder.table_name_suffix = '' - Reminder.reset_table_name + reminder_class.table_name_prefix = 'prefix_' + reminder_class.table_name_suffix = '_suffix' + reminder_class.reset_table_name + assert_equal "prefix_reminders_suffix", migration.proper_table_name(reminder_class) + reminder_class.table_name_prefix = '' + reminder_class.table_name_suffix = '' + reminder_class.reset_table_name # Use AR::Base's prefix/suffix if string or symbol is given ActiveRecord::Base.table_name_prefix = "prefix_" ActiveRecord::Base.table_name_suffix = "_suffix" - Reminder.reset_table_name + reminder_class.reset_table_name assert_equal "prefix_table_suffix", migration.proper_table_name('table', migration.table_name_options) assert_equal "prefix_table_suffix", migration.proper_table_name(:table, migration.table_name_options) end @@ -443,6 +449,32 @@ class MigrationTest < ActiveRecord::TestCase Person.connection.drop_table :binary_testings rescue nil end + def test_create_table_with_query + Person.connection.drop_table :table_from_query_testings rescue nil + Person.connection.create_table(:person, force: true) + + Person.connection.create_table :table_from_query_testings, as: "SELECT id FROM person" + + columns = Person.connection.columns(:table_from_query_testings) + assert_equal 1, columns.length + assert_equal "id", columns.first.name + + Person.connection.drop_table :table_from_query_testings rescue nil + end + + def test_create_table_with_query_from_relation + Person.connection.drop_table :table_from_query_testings rescue nil + Person.connection.create_table(:person, force: true) + + Person.connection.create_table :table_from_query_testings, as: Person.select(:id) + + columns = Person.connection.columns(:table_from_query_testings) + assert_equal 1, columns.length + assert_equal "id", columns.first.name + + Person.connection.drop_table :table_from_query_testings rescue nil + end + if current_adapter? :OracleAdapter def test_create_table_with_custom_sequence_name # table name is 29 chars, the standard sequence name will @@ -502,11 +534,13 @@ class MigrationTest < ActiveRecord::TestCase end protected - def with_env_tz(new_tz = 'US/Eastern') - old_tz, ENV['TZ'] = ENV['TZ'], new_tz - yield - ensure - old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ') + # This is needed to isolate class_attribute assignments like `table_name_prefix` + # for each test case. + def new_isolated_reminder_class + Class.new(Reminder) { + def self.name; "Reminder"; end + def self.base_class; self; end + } end end @@ -743,7 +777,7 @@ class CopyMigrationsTest < ActiveRecord::TestCase @migrations_path = MIGRATIONS_ROOT + "/valid_with_timestamps" @existing_migrations = Dir[@migrations_path + "/*.rb"] - Time.travel_to(Time.utc(2010, 7, 26, 10, 10, 10)) do + travel_to(Time.utc(2010, 7, 26, 10, 10, 10)) do copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"}) assert File.exist?(@migrations_path + "/20100726101010_people_have_hobbies.bukkits.rb") assert File.exist?(@migrations_path + "/20100726101011_people_have_descriptions.bukkits.rb") @@ -768,7 +802,7 @@ class CopyMigrationsTest < ActiveRecord::TestCase sources[:bukkits] = MIGRATIONS_ROOT + "/to_copy_with_timestamps" sources[:omg] = MIGRATIONS_ROOT + "/to_copy_with_timestamps2" - Time.travel_to(Time.utc(2010, 7, 26, 10, 10, 10)) do + travel_to(Time.utc(2010, 7, 26, 10, 10, 10)) do copied = ActiveRecord::Migration.copy(@migrations_path, sources) assert File.exist?(@migrations_path + "/20100726101010_people_have_hobbies.bukkits.rb") assert File.exist?(@migrations_path + "/20100726101011_people_have_descriptions.bukkits.rb") @@ -788,7 +822,7 @@ class CopyMigrationsTest < ActiveRecord::TestCase @migrations_path = MIGRATIONS_ROOT + "/valid_with_timestamps" @existing_migrations = Dir[@migrations_path + "/*.rb"] - Time.travel_to(Time.utc(2010, 2, 20, 10, 10, 10)) do + travel_to(Time.utc(2010, 2, 20, 10, 10, 10)) do ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"}) assert File.exist?(@migrations_path + "/20100301010102_people_have_hobbies.bukkits.rb") assert File.exist?(@migrations_path + "/20100301010103_people_have_descriptions.bukkits.rb") @@ -863,7 +897,7 @@ class CopyMigrationsTest < ActiveRecord::TestCase @migrations_path = MIGRATIONS_ROOT + "/non_existing" @existing_migrations = [] - Time.travel_to(Time.utc(2010, 7, 26, 10, 10, 10)) do + travel_to(Time.utc(2010, 7, 26, 10, 10, 10)) do copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"}) assert File.exist?(@migrations_path + "/20100726101010_people_have_hobbies.bukkits.rb") assert File.exist?(@migrations_path + "/20100726101011_people_have_descriptions.bukkits.rb") @@ -878,7 +912,7 @@ class CopyMigrationsTest < ActiveRecord::TestCase @migrations_path = MIGRATIONS_ROOT + "/empty" @existing_migrations = [] - Time.travel_to(Time.utc(2010, 7, 26, 10, 10, 10)) do + travel_to(Time.utc(2010, 7, 26, 10, 10, 10)) do copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"}) assert File.exist?(@migrations_path + "/20100726101010_people_have_hobbies.bukkits.rb") assert File.exist?(@migrations_path + "/20100726101011_people_have_descriptions.bukkits.rb") diff --git a/activerecord/test/cases/mixin_test.rb b/activerecord/test/cases/mixin_test.rb index ad0d5cce27..7ddb2bfee1 100644 --- a/activerecord/test/cases/mixin_test.rb +++ b/activerecord/test/cases/mixin_test.rb @@ -6,10 +6,14 @@ end class TouchTest < ActiveRecord::TestCase fixtures :mixins - def setup + setup do travel_to Time.now end + teardown do + travel_back + end + def test_update stamped = Mixin.new diff --git a/activerecord/test/cases/multiple_db_test.rb b/activerecord/test/cases/multiple_db_test.rb index 2e386a172a..3831de6ae3 100644 --- a/activerecord/test/cases/multiple_db_test.rb +++ b/activerecord/test/cases/multiple_db_test.rb @@ -101,7 +101,7 @@ class MultipleDbTest < ActiveRecord::TestCase College.first.courses.first end ensure - ActiveRecord::Base.establish_connection 'arunit' + ActiveRecord::Base.establish_connection :arunit end end end diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb index 6cd3e2154e..b9f0624f76 100644 --- a/activerecord/test/cases/persistence_test.rb +++ b/activerecord/test/cases/persistence_test.rb @@ -152,6 +152,20 @@ class PersistenceTest < ActiveRecord::TestCase assert_equal original_errors, client.errors end + def test_dupd_becomes_persists_changes_from_the_original + original = topics(:first) + copy = original.dup.becomes(Reply) + copy.save! + assert_equal "The First Topic", Topic.find(copy.id).title + end + + def test_becomes_includes_changed_attributes + company = Company.new(name: "37signals") + client = company.becomes(Client) + assert_equal "37signals", client.name + assert_equal %w{name}, client.changed + end + def test_delete_many original_count = Topic.count Topic.delete(deleting = [1, 2]) @@ -726,7 +740,7 @@ class PersistenceTest < ActiveRecord::TestCase assert_raise(ActiveRecord::RecordInvalid) { reply.update!(title: nil, content: "Have a nice evening") } ensure - Reply.reset_callbacks(:validate) + Reply.clear_validators! end def test_update_attributes! @@ -747,7 +761,7 @@ class PersistenceTest < ActiveRecord::TestCase assert_raise(ActiveRecord::RecordInvalid) { reply.update_attributes!(title: nil, content: "Have a nice evening") } ensure - Reply.reset_callbacks(:validate) + Reply.clear_validators! end def test_destroyed_returns_boolean diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb index 1b915387be..51ddd406ed 100644 --- a/activerecord/test/cases/primary_keys_test.rb +++ b/activerecord/test/cases/primary_keys_test.rb @@ -180,6 +180,11 @@ class PrimaryKeysTest < ActiveRecord::TestCase assert !col1.equal?(col2) end end + + def test_auto_detect_primary_key_from_schema + MixedCaseMonkey.reset_primary_key + assert_equal "monkeyID", MixedCaseMonkey.primary_key + end end class PrimaryKeyWithNoConnectionTest < ActiveRecord::TestCase @@ -214,4 +219,3 @@ if current_adapter?(:MysqlAdapter, :Mysql2Adapter) end end end - diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb index 136fda664c..da8ae672fe 100644 --- a/activerecord/test/cases/query_cache_test.rb +++ b/activerecord/test/cases/query_cache_test.rb @@ -8,7 +8,7 @@ require 'rack' class QueryCacheTest < ActiveRecord::TestCase fixtures :tasks, :topics, :categories, :posts, :categories_posts - def setup + teardown do Task.connection.clear_query_cache ActiveRecord::Base.connection.disable_query_cache! end @@ -134,6 +134,15 @@ class QueryCacheTest < ActiveRecord::TestCase end end + def test_find_queries_with_multi_cache_blocks + Task.cache do + Task.cache do + assert_queries(2) { Task.find(1); Task.find(2) } + end + assert_queries(0) { Task.find(1); Task.find(1); Task.find(2) } + end + end + def test_count_queries_with_cache Task.cache do assert_queries(1) { Task.count; Task.count } @@ -205,7 +214,7 @@ class QueryCacheExpiryTest < ActiveRecord::TestCase Post.find(1) # change the column definition - Post.connection.change_column :posts, :title, :string, :limit => 80 + Post.connection.change_column :posts, :title, :string, limit: 80 assert_nothing_raised { Post.find(1) } # restore the old definition @@ -232,7 +241,6 @@ class QueryCacheExpiryTest < ActiveRecord::TestCase def test_update Task.connection.expects(:clear_query_cache).times(2) - Task.cache do task = Task.find(1) task.starting = Time.now.utc @@ -242,7 +250,6 @@ class QueryCacheExpiryTest < ActiveRecord::TestCase def test_destroy Task.connection.expects(:clear_query_cache).times(2) - Task.cache do Task.find(1).destroy end @@ -250,7 +257,6 @@ class QueryCacheExpiryTest < ActiveRecord::TestCase def test_insert ActiveRecord::Base.connection.expects(:clear_query_cache).times(2) - Task.cache do Task.create! end diff --git a/activerecord/test/cases/reaper_test.rb b/activerecord/test/cases/reaper_test.rb index e53a27d5dd..b62a41c08e 100644 --- a/activerecord/test/cases/reaper_test.rb +++ b/activerecord/test/cases/reaper_test.rb @@ -69,7 +69,7 @@ module ActiveRecord conn = pool.checkout count = pool.connections.length - conn.extend(Module.new { def active?; false; end; }) + conn.extend(Module.new { def active_threadsafe?; false; end; }) while count == pool.connections.length Thread.pass diff --git a/activerecord/test/cases/relation/delegation_test.rb b/activerecord/test/cases/relation/delegation_test.rb index 13677797cf..9b2bfed039 100644 --- a/activerecord/test/cases/relation/delegation_test.rb +++ b/activerecord/test/cases/relation/delegation_test.rb @@ -6,95 +6,63 @@ module ActiveRecord class DelegationTest < ActiveRecord::TestCase fixtures :posts - def assert_responds(target, method) - assert target.respond_to?(method) - assert_nothing_raised do - method_arity = target.to_a.method(method).arity + def call_method(target, method) + method_arity = target.to_a.method(method).arity - if method_arity.zero? - target.send(method) - elsif method_arity < 0 - if method == :shuffle! - target.send(method) - else - target.send(method, 1) - end + if method_arity.zero? + target.public_send(method) + elsif method_arity < 0 + if method == :shuffle! + target.public_send(method) else - raise NotImplementedError + target.public_send(method, 1) end + elsif method_arity == 1 + target.public_send(method, 1) + else + raise NotImplementedError end end end - class DelegationAssociationTest < DelegationTest - def target - Post.first.comments - end + module DelegationWhitelistBlacklistTests + ARRAY_DELEGATES = [ + :+, :-, :|, :&, :[], + :all?, :collect, :detect, :each, :each_cons, :each_with_index, + :exclude?, :find_all, :flat_map, :group_by, :include?, :length, + :map, :none?, :one?, :partition, :reject, :reverse, + :sample, :second, :sort, :sort_by, :third, + :to_ary, :to_set, :to_xml, :to_yaml + ] - [:map, :collect].each do |method| - test "##{method} is delegated" do - assert_responds(target, method) - assert_equal(target.pluck(:body), target.send(method) {|post| post.body }) - end - - test "##{method}! is not delegated" do - assert_deprecated do - assert_responds(target, "#{method}!") - end + ARRAY_DELEGATES.each do |method| + define_method "test_delegates_#{method}_to_Array" do + assert_respond_to target, method end end - [:compact!, :flatten!, :reject!, :reverse!, :rotate!, - :shuffle!, :slice!, :sort!, :sort_by!].each do |method| - test "##{method} delegation is deprecated" do - assert_deprecated do - assert_responds(target, method) - end - end - end - - [:select!, :uniq!].each do |method| - test "##{method} is implemented" do - assert_responds(target, method) + ActiveRecord::Delegation::BLACKLISTED_ARRAY_METHODS.each do |method| + define_method "test_#{method}_is_not_delegated_to_Array" do + assert_raises(NoMethodError) { call_method(target, method) } end end end - class DelegationRelationTest < DelegationTest - fixtures :comments + class DelegationAssociationTest < DelegationTest + include DelegationWhitelistBlacklistTests def target - Comment.where(body: 'Normal type') + Post.first.comments end + end - [:map, :collect].each do |method| - test "##{method} is delegated" do - assert_responds(target, method) - assert_equal(target.pluck(:body), target.send(method) {|post| post.body }) - end - - test "##{method}! is not delegated" do - assert_deprecated do - assert_responds(target, "#{method}!") - end - end - end + class DelegationRelationTest < DelegationTest + include DelegationWhitelistBlacklistTests - [:compact!, :flatten!, :reject!, :reverse!, :rotate!, - :shuffle!, :slice!, :sort!, :sort_by!].each do |method| - test "##{method} delegation is deprecated" do - assert_deprecated do - assert_responds(target, method) - end - end - end + fixtures :comments - [:select!, :uniq!].each do |method| - test "##{method} triggers an immutable error" do - assert_raises ActiveRecord::ImmutableRelation do - assert_responds(target, method) - end - end + def target + Comment.all end end end diff --git a/activerecord/test/cases/relation/mutation_test.rb b/activerecord/test/cases/relation/mutation_test.rb index 7cb2a19bee..4fafa668fb 100644 --- a/activerecord/test/cases/relation/mutation_test.rb +++ b/activerecord/test/cases/relation/mutation_test.rb @@ -14,6 +14,10 @@ module ActiveRecord def relation_delegate_class(klass) self.class.relation_delegate_class(klass) end + + def attribute_alias?(name) + false + end end def relation diff --git a/activerecord/test/cases/relation/where_chain_test.rb b/activerecord/test/cases/relation/where_chain_test.rb index d44b4dfe3d..fd2420cb88 100644 --- a/activerecord/test/cases/relation/where_chain_test.rb +++ b/activerecord/test/cases/relation/where_chain_test.rb @@ -23,6 +23,12 @@ module ActiveRecord assert_equal([expected], relation.where_values) end + def test_not_with_nil + assert_raise ArgumentError do + Post.where.not(nil) + end + end + def test_not_in expected = Arel::Nodes::NotIn.new(Post.arel_table[@name], %w[hello goodbye]) relation = Post.where.not(title: %w[hello goodbye]) diff --git a/activerecord/test/cases/relation/where_test.rb b/activerecord/test/cases/relation/where_test.rb index 56e4605ccc..937f226b1d 100644 --- a/activerecord/test/cases/relation/where_test.rb +++ b/activerecord/test/cases/relation/where_test.rb @@ -35,6 +35,21 @@ module ActiveRecord assert_equal Post.where(author_id: 1).to_sql, Post.where(author: author).to_sql end + def test_belongs_to_nil_where + assert_equal Post.where(author_id: nil).to_sql, Post.where(author: nil).to_sql + end + + def test_belongs_to_array_value_where + assert_equal Post.where(author_id: [1,2]).to_sql, Post.where(author: [1,2]).to_sql + end + + def test_belongs_to_nested_relation_where + expected = Post.where(author_id: Author.where(id: [1,2])).to_sql + actual = Post.where(author: Author.where(id: [1,2])).to_sql + + assert_equal expected, actual + end + def test_belongs_to_nested_where parent = Comment.new parent.id = 1 @@ -55,6 +70,25 @@ module ActiveRecord assert_equal expected.to_sql, actual.to_sql end + def test_polymorphic_nested_array_where + treasure = Treasure.new + treasure.id = 1 + hidden = HiddenTreasure.new + hidden.id = 2 + + expected = PriceEstimate.where(estimate_of_type: 'Treasure', estimate_of_id: [treasure, hidden]) + actual = PriceEstimate.where(estimate_of: [treasure, hidden]) + + assert_equal expected.to_sql, actual.to_sql + end + + def test_polymorphic_nested_relation_where + expected = PriceEstimate.where(estimate_of_type: 'Treasure', estimate_of_id: Treasure.where(id: [1,2])) + actual = PriceEstimate.where(estimate_of: Treasure.where(id: [1,2])) + + assert_equal expected.to_sql, actual.to_sql + end + def test_polymorphic_sti_shallow_where treasure = HiddenTreasure.new treasure.id = 1 diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb index 70d113fb39..15611656fd 100644 --- a/activerecord/test/cases/relation_test.rb +++ b/activerecord/test/cases/relation_test.rb @@ -207,6 +207,16 @@ module ActiveRecord assert_equal 3, authors(:david).posts.merge(posts_with_special_comments_with_ratings).count.length end + def test_relation_merging_with_joins_as_join_dependency_pick_proper_parent + post = Post.create!(title: "haha", body: "huhu") + comment = post.comments.create!(body: "hu") + 3.times { comment.ratings.create! } + + relation = Post.joins(:comments).merge Comment.joins(:ratings) + + assert_equal 3, relation.where(id: post.id).pluck(:id).size + end + def test_respond_to_for_non_selected_element post = Post.select(:title).first assert_equal false, post.respond_to?(:body), "post should not respond_to?(:body) since invoking it raises exception" @@ -221,6 +231,5 @@ module ActiveRecord posts_with_special_comments_with_ratings = Post.group("posts.id").joins(:special_comments).merge(special_comments_with_ratings) assert_equal 3, authors(:david).posts.merge(posts_with_special_comments_with_ratings).count.length end - end end diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index baa3acf3fb..72b96dd3e9 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -65,7 +65,7 @@ class RelationTest < ActiveRecord::TestCase def test_scoped topics = Topic.all assert_kind_of ActiveRecord::Relation, topics - assert_equal 4, topics.size + assert_equal 5, topics.size end def test_to_json @@ -86,14 +86,14 @@ class RelationTest < ActiveRecord::TestCase def test_scoped_all topics = Topic.all.to_a assert_kind_of Array, topics - assert_no_queries { assert_equal 4, topics.size } + assert_no_queries { assert_equal 5, topics.size } end def test_loaded_all topics = Topic.all assert_queries(1) do - 2.times { assert_equal 4, topics.to_a.size } + 2.times { assert_equal 5, topics.to_a.size } end assert topics.loaded? @@ -151,6 +151,14 @@ class RelationTest < ActiveRecord::TestCase assert_equal relation.to_a, Comment.select('a.*').from(relation, :a).to_a end + def test_finding_with_subquery_without_select_does_not_change_the_select + relation = Topic.where(approved: true) + assert_raises(ActiveRecord::StatementInvalid) do + Topic.from(relation).to_a + end + end + + def test_finding_with_conditions assert_equal ["David"], Author.where(:name => 'David').map(&:name) assert_equal ['Mary'], Author.where(["name = ?", 'Mary']).map(&:name) @@ -159,52 +167,100 @@ class RelationTest < ActiveRecord::TestCase def test_finding_with_order topics = Topic.order('id') - assert_equal 4, topics.to_a.size + assert_equal 5, topics.to_a.size assert_equal topics(:first).title, topics.first.title end - def test_finding_with_arel_order topics = Topic.order(Topic.arel_table[:id].asc) - assert_equal 4, topics.to_a.size + assert_equal 5, topics.to_a.size assert_equal topics(:first).title, topics.first.title end def test_finding_with_assoc_order topics = Topic.order(:id => :desc) - assert_equal 4, topics.to_a.size - assert_equal topics(:fourth).title, topics.first.title + assert_equal 5, topics.to_a.size + assert_equal topics(:fifth).title, topics.first.title end def test_finding_with_reverted_assoc_order topics = Topic.order(:id => :asc).reverse_order - assert_equal 4, topics.to_a.size - assert_equal topics(:fourth).title, topics.first.title + assert_equal 5, topics.to_a.size + assert_equal topics(:fifth).title, topics.first.title end def test_order_with_hash_and_symbol_generates_the_same_sql assert_equal Topic.order(:id).to_sql, Topic.order(:id => :asc).to_sql end + def test_finding_with_desc_order_with_string + topics = Topic.order(id: "desc") + assert_equal 5, topics.to_a.size + assert_equal [topics(:fifth), topics(:fourth), topics(:third), topics(:second), topics(:first)], topics.to_a + end + + def test_finding_with_asc_order_with_string + topics = Topic.order(id: 'asc') + assert_equal 5, topics.to_a.size + assert_equal [topics(:first), topics(:second), topics(:third), topics(:fourth), topics(:fifth)], topics.to_a + end + + def test_support_upper_and_lower_case_directions + assert_includes Topic.order(id: "ASC").to_sql, "ASC" + assert_includes Topic.order(id: "asc").to_sql, "ASC" + assert_includes Topic.order(id: :ASC).to_sql, "ASC" + assert_includes Topic.order(id: :asc).to_sql, "ASC" + + assert_includes Topic.order(id: "DESC").to_sql, "DESC" + assert_includes Topic.order(id: "desc").to_sql, "DESC" + assert_includes Topic.order(id: :DESC).to_sql, "DESC" + assert_includes Topic.order(id: :desc).to_sql,"DESC" + end + def test_raising_exception_on_invalid_hash_params - assert_raise(ArgumentError) { Topic.order(:name, "id DESC", :id => :DeSc) } + e = assert_raise(ArgumentError) { Topic.order(:name, "id DESC", id: :asfsdf) } + assert_equal 'Direction "asfsdf" is invalid. Valid directions are: [:asc, :desc, :ASC, :DESC, "asc", "desc", "ASC", "DESC"]', e.message end def test_finding_last_with_arel_order topics = Topic.order(Topic.arel_table[:id].asc) - assert_equal topics(:fourth).title, topics.last.title + assert_equal topics(:fifth).title, topics.last.title end def test_finding_with_order_concatenated topics = Topic.order('author_name').order('title') - assert_equal 4, topics.to_a.size + assert_equal 5, topics.to_a.size assert_equal topics(:fourth).title, topics.first.title end + def test_finding_with_order_by_aliased_attributes + topics = Topic.order(:heading) + assert_equal 5, topics.to_a.size + assert_equal topics(:fifth).title, topics.first.title + end + + def test_finding_with_assoc_order_by_aliased_attributes + topics = Topic.order(heading: :desc) + assert_equal 5, topics.to_a.size + assert_equal topics(:third).title, topics.first.title + end + def test_finding_with_reorder topics = Topic.order('author_name').order('title').reorder('id').to_a topics_titles = topics.map{ |t| t.title } - assert_equal ['The First Topic', 'The Second Topic of the day', 'The Third Topic of the day', 'The Fourth Topic of the day'], topics_titles + assert_equal ['The First Topic', 'The Second Topic of the day', 'The Third Topic of the day', 'The Fourth Topic of the day', 'The Fifth Topic of the day'], topics_titles + end + + def test_finding_with_reorder_by_aliased_attributes + topics = Topic.order('author_name').reorder(:heading) + assert_equal 5, topics.to_a.size + assert_equal topics(:fifth).title, topics.first.title + end + + def test_finding_with_assoc_reorder_by_aliased_attributes + topics = Topic.order('author_name').reorder(heading: :desc) + assert_equal 5, topics.to_a.size + assert_equal topics(:third).title, topics.first.title end def test_finding_with_order_and_take @@ -295,7 +351,7 @@ class RelationTest < ActiveRecord::TestCase def test_null_relation_calculations_methods assert_no_queries do assert_equal 0, Developer.none.count - assert_equal 0, Developer.none.calculate(:count, nil) + assert_equal 0, Developer.none.calculate(:count, nil, {}) assert_equal nil, Developer.none.calculate(:average, 'salary') end end @@ -770,6 +826,13 @@ class RelationTest < ActiveRecord::TestCase assert_equal david.salary, developer.salary end + def test_select_takes_an_aliased_attribute + first = topics(:first) + + topic = Topic.where(id: first.id).select(:heading).first + assert_equal first.heading, topic.heading + end + def test_select_argument_error assert_raises(ArgumentError) { Developer.select } end @@ -1500,32 +1563,63 @@ class RelationTest < ActiveRecord::TestCase end end + test "joins with select" do + posts = Post.joins(:author).select("id", "authors.author_address_id").order("posts.id").limit(3) + assert_equal [1, 2, 4], posts.map(&:id) + assert_equal [1, 1, 1], posts.map(&:author_address_id) + end + test "delegations do not leak to other classes" do Topic.all.by_lifo assert Topic.all.class.method_defined?(:by_lifo) assert !Post.all.respond_to?(:by_lifo) end - class OMGTopic < ActiveRecord::Base - self.table_name = 'topics' + def test_unscope_removes_binds + left = Post.where(id: Arel::Nodes::BindParam.new('?')) + column = Post.columns_hash['id'] + left.bind_values += [[column, 20]] - def self.__omg__ - "omgtopic" - end + relation = left.unscope(where: :id) + assert_equal [], relation.bind_values end - test "delegations do not clash across classes" do - begin - class ::Array - def __omg__ - "array" - end - end + def test_merging_removes_rhs_bind_parameters + left = Post.where(id: Arel::Nodes::BindParam.new('?')) + column = Post.columns_hash['id'] + left.bind_values += [[column, 20]] + right = Post.where(id: 10) - assert_equal "array", Topic.all.__omg__ - assert_equal "omgtopic", OMGTopic.all.__omg__ - ensure - Array.send(:remove_method, :__omg__) - end + merged = left.merge(right) + assert_equal [], merged.bind_values + end + + def test_merging_keeps_lhs_bind_parameters + column = Post.columns_hash['id'] + binds = [[column, 20]] + + right = Post.where(id: Arel::Nodes::BindParam.new('?')) + right.bind_values += binds + left = Post.where(id: 10) + + merged = left.merge(right) + assert_equal binds, merged.bind_values + end + + def test_merging_reorders_bind_params + post = Post.first + id_column = Post.columns_hash['id'] + title_column = Post.columns_hash['title'] + + bv = Post.connection.substitute_at id_column, 0 + + right = Post.where(id: bv) + right.bind_values += [[id_column, post.id]] + + left = Post.where(title: bv) + left.bind_values += [[title_column, post.title]] + + merged = left.merge(right) + assert_equal post, merged.first end end diff --git a/activerecord/test/cases/result_test.rb b/activerecord/test/cases/result_test.rb index b6c583dbf5..2131b32a0c 100644 --- a/activerecord/test/cases/result_test.rb +++ b/activerecord/test/cases/result_test.rb @@ -5,14 +5,16 @@ module ActiveRecord def result Result.new(['col_1', 'col_2'], [ ['row 1 col 1', 'row 1 col 2'], - ['row 2 col 1', 'row 2 col 2'] + ['row 2 col 1', 'row 2 col 2'], + ['row 3 col 1', 'row 3 col 2'], ]) end def test_to_hash_returns_row_hashes assert_equal [ {'col_1' => 'row 1 col 1', 'col_2' => 'row 1 col 2'}, - {'col_1' => 'row 2 col 1', 'col_2' => 'row 2 col 2'} + {'col_1' => 'row 2 col 1', 'col_2' => 'row 2 col 2'}, + {'col_1' => 'row 3 col 1', 'col_2' => 'row 3 col 2'}, ], result.to_hash end @@ -28,5 +30,11 @@ module ActiveRecord assert_kind_of Integer, index end end + + if Enumerator.method_defined? :size + def test_each_without_block_returns_a_sized_enumerator + assert_equal 3, result.each.size + end + end end end diff --git a/activerecord/test/cases/sanitize_test.rb b/activerecord/test/cases/sanitize_test.rb index 766b2ff2ef..954eab8022 100644 --- a/activerecord/test/cases/sanitize_test.rb +++ b/activerecord/test/cases/sanitize_test.rb @@ -46,4 +46,9 @@ class SanitizeTest < ActiveRecord::TestCase select_author_sql = Post.send(:sanitize_sql_array, ['id in (:post_ids)', post_ids: david_posts]) assert_match(sub_query_pattern, select_author_sql, 'should sanitize `Relation` as subquery for named bind variables') end + + def test_sanitize_sql_array_handles_empty_statement + select_author_sql = Post.send(:sanitize_sql_array, ['']) + assert_equal('', select_author_sql) + end end diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb index 741827446d..575eb34a9c 100644 --- a/activerecord/test/cases/schema_dumper_test.rb +++ b/activerecord/test/cases/schema_dumper_test.rb @@ -177,7 +177,7 @@ class SchemaDumperTest < ActiveRecord::TestCase def test_schema_dumps_index_columns_in_right_order index_definition = standard_dump.split(/\n/).grep(/add_index.*companies/).first.strip - if current_adapter?(:MysqlAdapter) || current_adapter?(:Mysql2Adapter) || current_adapter?(:PostgreSQLAdapter) + if current_adapter?(:MysqlAdapter, :Mysql2Adapter, :PostgreSQLAdapter) assert_equal 'add_index "companies", ["firm_id", "type", "rating"], name: "company_index", using: :btree', index_definition else assert_equal 'add_index "companies", ["firm_id", "type", "rating"], name: "company_index"', index_definition @@ -188,8 +188,10 @@ class SchemaDumperTest < ActiveRecord::TestCase index_definition = standard_dump.split(/\n/).grep(/add_index.*company_partial_index/).first.strip if current_adapter?(:PostgreSQLAdapter) assert_equal 'add_index "companies", ["firm_id", "type"], name: "company_partial_index", where: "(rating > 10)", using: :btree', index_definition - elsif current_adapter?(:MysqlAdapter) || current_adapter?(:Mysql2Adapter) + elsif current_adapter?(:MysqlAdapter, :Mysql2Adapter) assert_equal 'add_index "companies", ["firm_id", "type"], name: "company_partial_index", using: :btree', index_definition + elsif current_adapter?(:SQLite3Adapter) && ActiveRecord::Base.connection.supports_partial_index? + assert_equal 'add_index "companies", ["firm_id", "type"], name: "company_partial_index", where: "rating > 10"', index_definition else assert_equal 'add_index "companies", ["firm_id", "type"], name: "company_partial_index"', index_definition end @@ -317,6 +319,13 @@ class SchemaDumperTest < ActiveRecord::TestCase end end + def test_schema_dump_includes_citext_shorthand_definition + output = standard_dump + if %r{create_table "postgresql_citext"} =~ output + assert_match %r[t.citext "text_citext"], output + end + end + def test_schema_dump_includes_ltrees_shorthand_definition output = standard_dump if %r{create_table "postgresql_ltrees"} =~ output diff --git a/activerecord/test/cases/scoping/default_scoping_test.rb b/activerecord/test/cases/scoping/default_scoping_test.rb index 11dd32bfb8..9a4d8c6740 100644 --- a/activerecord/test/cases/scoping/default_scoping_test.rb +++ b/activerecord/test/cases/scoping/default_scoping_test.rb @@ -149,6 +149,16 @@ class DefaultScopingTest < ActiveRecord::TestCase assert_equal expected, received end + def test_unscope_string_where_clauses_involved + dev_relation = Developer.order('salary DESC').where("created_at > ?", 1.year.ago) + expected = dev_relation.collect { |dev| dev.name } + + dev_ordered_relation = DeveloperOrderedBySalary.where(name: 'Jamis').where("created_at > ?", 1.year.ago) + received = dev_ordered_relation.unscope(where: [:name]).collect { |dev| dev.name } + + assert_equal expected, received + end + def test_unscope_with_grouping_attributes expected = Developer.order('salary DESC').collect { |dev| dev.name } received = DeveloperOrderedBySalary.group(:name).unscope(:group).collect { |dev| dev.name } @@ -178,7 +188,7 @@ class DefaultScopingTest < ActiveRecord::TestCase def test_unscope_select expected = Developer.order('salary ASC').collect { |dev| dev.name } - received = Developer.order('salary DESC').reverse_order.select(:name => "Jamis").unscope(:select).collect { |dev| dev.name } + received = Developer.order('salary DESC').reverse_order.select(:name).unscope(:select).collect { |dev| dev.name } assert_equal expected, received expected_2 = Developer.all.collect { |dev| dev.id } @@ -385,4 +395,22 @@ class DefaultScopingTest < ActiveRecord::TestCase threads.each(&:join) end end + + test "additional conditions are ANDed with the default scope" do + scope = DeveloperCalledJamis.where(name: "David") + assert_equal 2, scope.where_values.length + assert_equal [], scope.to_a + end + + test "additional conditions in a scope are ANDed with the default scope" do + scope = DeveloperCalledJamis.david + assert_equal 2, scope.where_values.length + assert_equal [], scope.to_a + end + + test "a scope can remove the condition from the default scope" do + scope = DeveloperCalledJamis.david2 + assert_equal 1, scope.where_values.length + assert_equal Developer.where(name: "David").map(&:id), scope.map(&:id) + end end diff --git a/activerecord/test/cases/scoping/named_scoping_test.rb b/activerecord/test/cases/scoping/named_scoping_test.rb index 72c9787b84..f0ad9ebb8a 100644 --- a/activerecord/test/cases/scoping/named_scoping_test.rb +++ b/activerecord/test/cases/scoping/named_scoping_test.rb @@ -266,6 +266,65 @@ class NamedScopingTest < ActiveRecord::TestCase assert_equal 'lifo', topic.author_name end + def test_reserved_scope_names + klass = Class.new(ActiveRecord::Base) do + self.table_name = "topics" + + scope :approved, -> { where(approved: true) } + + class << self + public + def pub; end + + private + def pri; end + + protected + def pro; end + end + end + + subklass = Class.new(klass) + + conflicts = [ + :create, # public class method on AR::Base + :relation, # private class method on AR::Base + :new, # redefined class method on AR::Base + :all, # a default scope + ] + + non_conflicts = [ + :find_by_title, # dynamic finder method + :approved, # existing scope + :pub, # existing public class method + :pri, # existing private class method + :pro, # existing protected class method + :open, # a ::Kernel method + ] + + conflicts.each do |name| + assert_raises(ArgumentError, "scope `#{name}` should not be allowed") do + klass.class_eval { scope name, ->{ where(approved: true) } } + end + + assert_raises(ArgumentError, "scope `#{name}` should not be allowed") do + subklass.class_eval { scope name, ->{ where(approved: true) } } + end + end + + non_conflicts.each do |name| + assert_nothing_raised do + silence_warnings do + klass.class_eval { scope name, ->{ where(approved: true) } } + end + end + + assert_nothing_raised do + subklass.class_eval { scope name, ->{ where(approved: true) } } + end + end + end + # Method delegation for scope names which look like /\A[a-zA-Z_]\w*[!?]?\z/ # has been done by evaluating a string with a plain def statement. For scope # names which contain spaces this approach doesn't work. @@ -344,13 +403,13 @@ class NamedScopingTest < ActiveRecord::TestCase end def test_scopes_batch_finders - assert_equal 3, Topic.approved.count + assert_equal 4, Topic.approved.count - assert_queries(4) do + assert_queries(5) do Topic.approved.find_each(:batch_size => 1) {|t| assert t.approved? } end - assert_queries(2) do + assert_queries(3) do Topic.approved.find_in_batches(:batch_size => 2) do |group| group.each {|t| assert t.approved? } end @@ -366,7 +425,7 @@ class NamedScopingTest < ActiveRecord::TestCase def test_scopes_on_relations # Topic.replied approved_topics = Topic.all.approved.order('id DESC') - assert_equal topics(:fourth), approved_topics.first + assert_equal topics(:fifth), approved_topics.first replied_approved_topics = approved_topics.replied assert_equal topics(:third), replied_approved_topics.first diff --git a/activerecord/test/cases/store_test.rb b/activerecord/test/cases/store_test.rb index 0c9f7ccd55..978cee9cfb 100644 --- a/activerecord/test/cases/store_test.rb +++ b/activerecord/test/cases/store_test.rb @@ -162,4 +162,32 @@ class StoreTest < ActiveRecord::TestCase assert_equal [:color], first_model.stored_attributes[:data] assert_equal [:width, :height], second_model.stored_attributes[:data] end + + test "YAML coder initializes the store when a Nil value is given" do + assert_equal({}, @john.params) + end + + test "attributes_for_coder should return stored fields already serialized" do + attributes = { + "id" => @john.id, + "name"=> @john.name, + "settings" => "--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess\ncolor: black\n", + "preferences" => "--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess\nremember_login: true\n", + "json_data" => "{\"height\":\"tall\"}", "json_data_empty"=>"{\"is_a_good_guy\":true}", + "params" => "--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess {}\n", + "account_id"=> @john.account_id + } + + assert_equal attributes, @john.attributes_for_coder + end + + test "dump, load and dump again a model" do + dumped = YAML.dump(@john) + loaded = YAML.load(dumped) + assert_equal @john, loaded + + second_dump = YAML.dump(loaded) + assert_equal dumped, second_dump + assert_equal @john, YAML.load(second_dump) + end end diff --git a/activerecord/test/cases/tasks/database_tasks_test.rb b/activerecord/test/cases/tasks/database_tasks_test.rb index e9000fef25..bf9e14fa4f 100644 --- a/activerecord/test/cases/tasks/database_tasks_test.rb +++ b/activerecord/test/cases/tasks/database_tasks_test.rb @@ -1,4 +1,5 @@ require 'cases/helper' +require 'active_record/tasks/database_tasks' module ActiveRecord module DatabaseTasksSetupper @@ -128,11 +129,22 @@ module ActiveRecord ) end - def test_creates_test_database_when_environment_is_database + def test_creates_test_and_development_databases_when_env_was_not_specified ActiveRecord::Tasks::DatabaseTasks.expects(:create). with('database' => 'dev-db') ActiveRecord::Tasks::DatabaseTasks.expects(:create). with('database' => 'test-db') + ENV.expects(:[]).with('RAILS_ENV').returns(nil) + + ActiveRecord::Tasks::DatabaseTasks.create_current( + ActiveSupport::StringInquirer.new('development') + ) + end + + def test_creates_only_development_database_when_rails_env_is_development + ActiveRecord::Tasks::DatabaseTasks.expects(:create). + with('database' => 'dev-db') + ENV.expects(:[]).with('RAILS_ENV').returns('development') ActiveRecord::Tasks::DatabaseTasks.create_current( ActiveSupport::StringInquirer.new('development') @@ -142,7 +154,7 @@ module ActiveRecord def test_establishes_connection_for_the_given_environment ActiveRecord::Tasks::DatabaseTasks.stubs(:create).returns true - ActiveRecord::Base.expects(:establish_connection).with('development') + ActiveRecord::Base.expects(:establish_connection).with(:development) ActiveRecord::Tasks::DatabaseTasks.create_current( ActiveSupport::StringInquirer.new('development') @@ -238,11 +250,22 @@ module ActiveRecord ) end - def test_creates_test_database_when_environment_is_database + def test_drops_test_and_development_databases_when_env_was_not_specified ActiveRecord::Tasks::DatabaseTasks.expects(:drop). with('database' => 'dev-db') ActiveRecord::Tasks::DatabaseTasks.expects(:drop). with('database' => 'test-db') + ENV.expects(:[]).with('RAILS_ENV').returns(nil) + + ActiveRecord::Tasks::DatabaseTasks.drop_current( + ActiveSupport::StringInquirer.new('development') + ) + end + + def test_drops_only_development_database_when_rails_env_is_development + ActiveRecord::Tasks::DatabaseTasks.expects(:drop). + with('database' => 'dev-db') + ENV.expects(:[]).with('RAILS_ENV').returns('development') ActiveRecord::Tasks::DatabaseTasks.drop_current( ActiveSupport::StringInquirer.new('development') diff --git a/activerecord/test/cases/timestamp_test.rb b/activerecord/test/cases/timestamp_test.rb index 2953b2b2be..717e0e1866 100644 --- a/activerecord/test/cases/timestamp_test.rb +++ b/activerecord/test/cases/timestamp_test.rb @@ -272,36 +272,62 @@ class TimestampTest < ActiveRecord::TestCase assert_not_equal time, old_pet.updated_at end - def test_changing_parent_of_a_record_touches_both_new_and_old_polymorphic_parent_record - klass = Class.new(ActiveRecord::Base) do - def self.name; 'Toy'; end + def test_changing_parent_of_a_record_touches_both_new_and_old_polymorphic_parent_record_changes_within_same_class + car_class = Class.new(ActiveRecord::Base) do + def self.name; 'Car'; end end - wheel_klass = Class.new(ActiveRecord::Base) do + wheel_class = Class.new(ActiveRecord::Base) do def self.name; 'Wheel'; end belongs_to :wheelable, :polymorphic => true, :touch => true end - toy1 = klass.find(1) - toy2 = klass.find(2) + car1 = car_class.find(1) + car2 = car_class.find(2) - wheel = wheel_klass.new - wheel.wheelable = toy1 - wheel.save! + wheel = wheel_class.create!(wheelable: car1) time = 3.days.ago.at_beginning_of_hour - toy1.update_columns(updated_at: time) - toy2.update_columns(updated_at: time) + car1.update_columns(updated_at: time) + car2.update_columns(updated_at: time) - wheel.wheelable = toy2 + wheel.wheelable = car2 wheel.save! - toy1.reload - toy2.reload + assert_not_equal time, car1.reload.updated_at + assert_not_equal time, car2.reload.updated_at + end + + def test_changing_parent_of_a_record_touches_both_new_and_old_polymorphic_parent_record_changes_with_other_class + car_class = Class.new(ActiveRecord::Base) do + def self.name; 'Car'; end + end + + toy_class = Class.new(ActiveRecord::Base) do + def self.name; 'Toy'; end + end + + wheel_class = Class.new(ActiveRecord::Base) do + def self.name; 'Wheel'; end + belongs_to :wheelable, :polymorphic => true, :touch => true + end + + car = car_class.find(1) + toy = toy_class.find(3) + + wheel = wheel_class.create!(wheelable: car) + + time = 3.days.ago.at_beginning_of_hour + + car.update_columns(updated_at: time) + toy.update_columns(updated_at: time) + + wheel.wheelable = toy + wheel.save! - assert_not_equal time, toy1.updated_at - assert_not_equal time, toy2.updated_at + assert_not_equal time, car.reload.updated_at + assert_not_equal time, toy.reload.updated_at end def test_clearing_association_touches_the_old_record diff --git a/activerecord/test/cases/transaction_callbacks_test.rb b/activerecord/test/cases/transaction_callbacks_test.rb index 5644a35385..3d64ecb464 100644 --- a/activerecord/test/cases/transaction_callbacks_test.rb +++ b/activerecord/test/cases/transaction_callbacks_test.rb @@ -1,9 +1,11 @@ require "cases/helper" +require 'models/owner' +require 'models/pet' require 'models/topic' class TransactionCallbacksTest < ActiveRecord::TestCase self.use_transactional_fixtures = false - fixtures :topics + fixtures :topics, :owners, :pets class ReplyWithCallbacks < ActiveRecord::Base self.table_name = :topics @@ -14,6 +16,11 @@ class TransactionCallbacksTest < ActiveRecord::TestCase after_commit :do_after_commit, on: :create + attr_accessor :save_on_after_create + after_create do + self.save! if save_on_after_create + end + def history @history ||= [] end @@ -28,14 +35,14 @@ class TransactionCallbacksTest < ActiveRecord::TestCase has_many :replies, class_name: "ReplyWithCallbacks", foreign_key: "parent_id" - after_commit{|record| record.send(:do_after_commit, nil)} - after_commit(:on => :create){|record| record.send(:do_after_commit, :create)} - after_commit(:on => :update){|record| record.send(:do_after_commit, :update)} - after_commit(:on => :destroy){|record| record.send(:do_after_commit, :destroy)} - after_rollback{|record| record.send(:do_after_rollback, nil)} - after_rollback(:on => :create){|record| record.send(:do_after_rollback, :create)} - after_rollback(:on => :update){|record| record.send(:do_after_rollback, :update)} - after_rollback(:on => :destroy){|record| record.send(:do_after_rollback, :destroy)} + after_commit { |record| record.do_after_commit(nil) } + after_commit(on: :create) { |record| record.do_after_commit(:create) } + after_commit(on: :update) { |record| record.do_after_commit(:update) } + after_commit(on: :destroy) { |record| record.do_after_commit(:destroy) } + after_rollback { |record| record.do_after_rollback(nil) } + after_rollback(on: :create) { |record| record.do_after_rollback(:create) } + after_rollback(on: :update) { |record| record.do_after_rollback(:update) } + after_rollback(on: :destroy) { |record| record.do_after_rollback(:destroy) } def history @history ||= [] @@ -65,7 +72,7 @@ class TransactionCallbacksTest < ActiveRecord::TestCase end def setup - @first, @second = TopicWithCallbacks.find(1, 3).sort_by { |t| t.id } + @first = TopicWithCallbacks.find(1) end def test_call_after_commit_after_transaction_commits @@ -77,40 +84,25 @@ class TransactionCallbacksTest < ActiveRecord::TestCase end def test_only_call_after_commit_on_update_after_transaction_commits_for_existing_record - @first.after_commit_block(:create){|r| r.history << :commit_on_create} - @first.after_commit_block(:update){|r| r.history << :commit_on_update} - @first.after_commit_block(:destroy){|r| r.history << :commit_on_destroy} - @first.after_rollback_block(:create){|r| r.history << :rollback_on_create} - @first.after_rollback_block(:update){|r| r.history << :rollback_on_update} - @first.after_rollback_block(:destroy){|r| r.history << :rollback_on_destroy} + add_transaction_execution_blocks @first @first.save! assert_equal [:commit_on_update], @first.history end def test_only_call_after_commit_on_destroy_after_transaction_commits_for_destroyed_record - @first.after_commit_block(:create){|r| r.history << :commit_on_create} - @first.after_commit_block(:update){|r| r.history << :commit_on_update} - @first.after_commit_block(:destroy){|r| r.history << :commit_on_destroy} - @first.after_rollback_block(:create){|r| r.history << :rollback_on_create} - @first.after_rollback_block(:update){|r| r.history << :rollback_on_update} - @first.after_rollback_block(:destroy){|r| r.history << :rollback_on_destroy} + add_transaction_execution_blocks @first @first.destroy assert_equal [:commit_on_destroy], @first.history end def test_only_call_after_commit_on_create_after_transaction_commits_for_new_record - @new_record = TopicWithCallbacks.new(:title => "New topic", :written_on => Date.today) - @new_record.after_commit_block(:create){|r| r.history << :commit_on_create} - @new_record.after_commit_block(:update){|r| r.history << :commit_on_update} - @new_record.after_commit_block(:destroy){|r| r.history << :commit_on_destroy} - @new_record.after_rollback_block(:create){|r| r.history << :rollback_on_create} - @new_record.after_rollback_block(:update){|r| r.history << :rollback_on_update} - @new_record.after_rollback_block(:destroy){|r| r.history << :rollback_on_destroy} - - @new_record.save! - assert_equal [:commit_on_create], @new_record.history + new_record = TopicWithCallbacks.new(:title => "New topic", :written_on => Date.today) + add_transaction_execution_blocks new_record + + new_record.save! + assert_equal [:commit_on_create], new_record.history end def test_only_call_after_commit_on_create_after_transaction_commits_for_new_record_if_create_succeeds_creating_through_association @@ -120,6 +112,23 @@ class TransactionCallbacksTest < ActiveRecord::TestCase assert_equal [], reply.history end + def test_only_call_after_commit_on_create_and_doesnt_leaky + r = ReplyWithCallbacks.new(content: 'foo') + r.save_on_after_create = true + r.save! + r.content = 'bar' + r.save! + r.save! + assert_equal [:commit_on_create], r.history + end + + def test_only_call_after_commit_on_update_after_transaction_commits_for_existing_record_on_touch + add_transaction_execution_blocks @first + + @first.touch + assert_equal [:commit_on_update], @first.history + end + def test_call_after_rollback_after_transaction_rollsback @first.after_commit_block{|r| r.history << :after_commit} @first.after_rollback_block{|r| r.history << :after_rollback} @@ -133,12 +142,7 @@ class TransactionCallbacksTest < ActiveRecord::TestCase end def test_only_call_after_rollback_on_update_after_transaction_rollsback_for_existing_record - @first.after_commit_block(:create){|r| r.history << :commit_on_create} - @first.after_commit_block(:update){|r| r.history << :commit_on_update} - @first.after_commit_block(:destroy){|r| r.history << :commit_on_destroy} - @first.after_rollback_block(:create){|r| r.history << :rollback_on_create} - @first.after_rollback_block(:update){|r| r.history << :rollback_on_update} - @first.after_rollback_block(:destroy){|r| r.history << :rollback_on_destroy} + add_transaction_execution_blocks @first Topic.transaction do @first.save! @@ -148,13 +152,19 @@ class TransactionCallbacksTest < ActiveRecord::TestCase assert_equal [:rollback_on_update], @first.history end + def test_only_call_after_rollback_on_update_after_transaction_rollsback_for_existing_record_on_touch + add_transaction_execution_blocks @first + + Topic.transaction do + @first.touch + raise ActiveRecord::Rollback + end + + assert_equal [:rollback_on_update], @first.history + end + def test_only_call_after_rollback_on_destroy_after_transaction_rollsback_for_destroyed_record - @first.after_commit_block(:create){|r| r.history << :commit_on_create} - @first.after_commit_block(:update){|r| r.history << :commit_on_update} - @first.after_commit_block(:destroy){|r| r.history << :commit_on_update} - @first.after_rollback_block(:create){|r| r.history << :rollback_on_create} - @first.after_rollback_block(:update){|r| r.history << :rollback_on_update} - @first.after_rollback_block(:destroy){|r| r.history << :rollback_on_destroy} + add_transaction_execution_blocks @first Topic.transaction do @first.destroy @@ -165,20 +175,15 @@ class TransactionCallbacksTest < ActiveRecord::TestCase end def test_only_call_after_rollback_on_create_after_transaction_rollsback_for_new_record - @new_record = TopicWithCallbacks.new(:title => "New topic", :written_on => Date.today) - @new_record.after_commit_block(:create){|r| r.history << :commit_on_create} - @new_record.after_commit_block(:update){|r| r.history << :commit_on_update} - @new_record.after_commit_block(:destroy){|r| r.history << :commit_on_destroy} - @new_record.after_rollback_block(:create){|r| r.history << :rollback_on_create} - @new_record.after_rollback_block(:update){|r| r.history << :rollback_on_update} - @new_record.after_rollback_block(:destroy){|r| r.history << :rollback_on_destroy} + new_record = TopicWithCallbacks.new(:title => "New topic", :written_on => Date.today) + add_transaction_execution_blocks new_record Topic.transaction do - @new_record.save! + new_record.save! raise ActiveRecord::Rollback end - assert_equal [:rollback_on_create], @new_record.history + assert_equal [:rollback_on_create], new_record.history end def test_call_after_rollback_when_commit_fails @@ -205,23 +210,24 @@ class TransactionCallbacksTest < ActiveRecord::TestCase @first.after_rollback_block{|r| r.rollbacks(1)} @first.after_commit_block{|r| r.commits(1)} - def @second.rollbacks(i=0); @rollbacks ||= 0; @rollbacks += i if i; end - def @second.commits(i=0); @commits ||= 0; @commits += i if i; end - @second.after_rollback_block{|r| r.rollbacks(1)} - @second.after_commit_block{|r| r.commits(1)} + second = TopicWithCallbacks.find(3) + def second.rollbacks(i=0); @rollbacks ||= 0; @rollbacks += i if i; end + def second.commits(i=0); @commits ||= 0; @commits += i if i; end + second.after_rollback_block{|r| r.rollbacks(1)} + second.after_commit_block{|r| r.commits(1)} Topic.transaction do @first.save! Topic.transaction(:requires_new => true) do - @second.save! + second.save! raise ActiveRecord::Rollback end end assert_equal 1, @first.commits assert_equal 0, @first.rollbacks - assert_equal 0, @second.commits - assert_equal 1, @second.rollbacks + assert_equal 0, second.commits + assert_equal 1, second.rollbacks end def test_only_call_after_rollback_on_records_rolled_back_to_a_savepoint_when_release_savepoint_fails @@ -252,33 +258,61 @@ class TransactionCallbacksTest < ActiveRecord::TestCase def @first.last_after_transaction_error; @last_transaction_error; end @first.after_commit_block{|r| r.last_after_transaction_error = :commit; raise "fail!";} @first.after_rollback_block{|r| r.last_after_transaction_error = :rollback; raise "fail!";} - @second.after_commit_block{|r| r.history << :after_commit} - @second.after_rollback_block{|r| r.history << :after_rollback} + + second = TopicWithCallbacks.find(3) + second.after_commit_block{|r| r.history << :after_commit} + second.after_rollback_block{|r| r.history << :after_rollback} Topic.transaction do @first.save! - @second.save! + second.save! end assert_equal :commit, @first.last_after_transaction_error - assert_equal [:after_commit], @second.history + assert_equal [:after_commit], second.history - @second.history.clear + second.history.clear Topic.transaction do @first.save! - @second.save! + second.save! raise ActiveRecord::Rollback end assert_equal :rollback, @first.last_after_transaction_error - assert_equal [:after_rollback], @second.history + assert_equal [:after_rollback], second.history end def test_after_rollback_callbacks_should_validate_on_condition - assert_raise(ArgumentError) { Topic.send(:after_rollback, :on => :save) } + assert_raise(ArgumentError) { Topic.after_rollback(on: :save) } end def test_after_commit_callbacks_should_validate_on_condition - assert_raise(ArgumentError) { Topic.send(:after_commit, :on => :save) } + assert_raise(ArgumentError) { Topic.after_commit(on: :save) } end + + def test_saving_a_record_with_a_belongs_to_that_specifies_touching_the_parent_should_call_callbacks_on_the_parent_object + pet = Pet.first + owner = pet.owner + flag = false + + owner.on_after_commit do + flag = true + end + + pet.name = "Fluffy the Third" + pet.save + + assert flag + end + + private + + def add_transaction_execution_blocks(record) + record.after_commit_block(:create) { |r| r.history << :commit_on_create } + record.after_commit_block(:update) { |r| r.history << :commit_on_update } + record.after_commit_block(:destroy) { |r| r.history << :commit_on_destroy } + record.after_rollback_block(:create) { |r| r.history << :rollback_on_create } + record.after_rollback_block(:update) { |r| r.history << :rollback_on_update } + record.after_rollback_block(:destroy) { |r| r.history << :rollback_on_destroy } + end end class CallbacksOnMultipleActionsTest < ActiveRecord::TestCase diff --git a/activerecord/test/cases/transaction_isolation_test.rb b/activerecord/test/cases/transaction_isolation_test.rb index 84c16fb109..f89c26532d 100644 --- a/activerecord/test/cases/transaction_isolation_test.rb +++ b/activerecord/test/cases/transaction_isolation_test.rb @@ -28,8 +28,8 @@ if ActiveRecord::Base.connection.supports_transaction_isolation? end setup do - Tag.establish_connection 'arunit' - Tag2.establish_connection 'arunit' + Tag.establish_connection :arunit + Tag2.establish_connection :arunit Tag.destroy_all end diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb index 89dab16975..1664f1a096 100644 --- a/activerecord/test/cases/transactions_test.rb +++ b/activerecord/test/cases/transactions_test.rb @@ -430,17 +430,26 @@ class TransactionTest < ActiveRecord::TestCase end def test_restore_active_record_state_for_all_records_in_a_transaction + topic_without_callbacks = Class.new(ActiveRecord::Base) do + self.table_name = 'topics' + end + topic_1 = Topic.new(:title => 'test_1') topic_2 = Topic.new(:title => 'test_2') + topic_3 = topic_without_callbacks.new(:title => 'test_3') + Topic.transaction do assert topic_1.save assert topic_2.save + assert topic_3.save @first.save @second.destroy assert topic_1.persisted?, 'persisted' assert_not_nil topic_1.id assert topic_2.persisted?, 'persisted' assert_not_nil topic_2.id + assert topic_3.persisted?, 'persisted' + assert_not_nil topic_3.id assert @first.persisted?, 'persisted' assert_not_nil @first.id assert @second.destroyed?, 'destroyed' @@ -451,6 +460,8 @@ class TransactionTest < ActiveRecord::TestCase assert_nil topic_1.id assert !topic_2.persisted?, 'not persisted' assert_nil topic_2.id + assert !topic_3.persisted?, 'not persisted' + assert_nil topic_3.id assert @first.persisted?, 'persisted' assert_not_nil @first.id assert !@second.destroyed?, 'not destroyed' diff --git a/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb b/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb index a73c3bf1af..13d4d85afa 100644 --- a/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb +++ b/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb @@ -3,7 +3,7 @@ require 'models/topic' class I18nGenerateMessageValidationTest < ActiveRecord::TestCase def setup - Topic.reset_callbacks(:validate) + Topic.clear_validators! @topic = Topic.new I18n.backend = I18n::Backend::Simple.new end diff --git a/activerecord/test/cases/validations/presence_validation_test.rb b/activerecord/test/cases/validations/presence_validation_test.rb index 1de8934406..3790d3c8cf 100644 --- a/activerecord/test/cases/validations/presence_validation_test.rb +++ b/activerecord/test/cases/validations/presence_validation_test.rb @@ -3,6 +3,8 @@ require "cases/helper" require 'models/man' require 'models/face' require 'models/interest' +require 'models/speedometer' +require 'models/dashboard' class PresenceValidationTest < ActiveRecord::TestCase class Boy < Man; end @@ -48,4 +50,18 @@ class PresenceValidationTest < ActiveRecord::TestCase i2.mark_for_destruction assert b.invalid? end + + def test_validates_presence_doesnt_convert_to_array + Speedometer.validates_presence_of :dashboard + + dash = Dashboard.new + + # dashboard has to_a method + def dash.to_a; ['(/)', '(\)']; end + + s = Speedometer.new + s.dashboard = dash + + assert_nothing_raised { s.valid? } + end end diff --git a/activerecord/test/cases/validations/uniqueness_validation_test.rb b/activerecord/test/cases/validations/uniqueness_validation_test.rb index 71cb9d7b1e..74c696c858 100644 --- a/activerecord/test/cases/validations/uniqueness_validation_test.rb +++ b/activerecord/test/cases/validations/uniqueness_validation_test.rb @@ -63,6 +63,14 @@ class UniquenessValidationTest < ActiveRecord::TestCase assert t2.save, "Should now save t2 as unique" end + def test_validate_uniqueness_with_alias_attribute + Topic.alias_attribute :new_title, :title + Topic.validates_uniqueness_of(:new_title) + + topic = Topic.new(new_title: 'abc') + assert topic.valid? + end + def test_validates_uniqueness_with_nil_value Topic.validates_uniqueness_of(:title) diff --git a/activerecord/test/cases/validations_test.rb b/activerecord/test/cases/validations_test.rb index 3f587d177b..de618902aa 100644 --- a/activerecord/test/cases/validations_test.rb +++ b/activerecord/test/cases/validations_test.rb @@ -56,13 +56,11 @@ class ValidationsTest < ActiveRecord::TestCase assert_raise(ActiveRecord::RecordInvalid) { WrongReply.create! } assert_raise(ActiveRecord::RecordInvalid) { WrongReply.new.save! } - begin - r = WrongReply.new + r = WrongReply.new + invalid = assert_raise ActiveRecord::RecordInvalid do r.save! - flunk - rescue ActiveRecord::RecordInvalid => invalid - assert_equal r, invalid.record end + assert_equal r, invalid.record end def test_exception_on_create_bang_many @@ -93,7 +91,7 @@ class ValidationsTest < ActiveRecord::TestCase assert reply.save(:validate => false) end - def test_validates_acceptance_of_with_non_existant_table + def test_validates_acceptance_of_with_non_existent_table Object.const_set :IncorporealModel, Class.new(ActiveRecord::Base) assert_nothing_raised ActiveRecord::StatementInvalid do diff --git a/activerecord/test/fixtures/companies.yml b/activerecord/test/fixtures/companies.yml index 0766e92027..ab9d5378ad 100644 --- a/activerecord/test/fixtures/companies.yml +++ b/activerecord/test/fixtures/companies.yml @@ -57,3 +57,11 @@ odegy: id: 9 name: Odegy type: ExclusivelyDependentFirm + +another_first_firm_client: + id: 11 + type: Client + firm_id: 1 + client_of: 1 + name: Apex + firm_name: 37signals diff --git a/activerecord/test/fixtures/computers.yml b/activerecord/test/fixtures/computers.yml index daf969d7da..7281a4d768 100644 --- a/activerecord/test/fixtures/computers.yml +++ b/activerecord/test/fixtures/computers.yml @@ -1,4 +1,5 @@ workstation: id: 1 + system: 'Linux' developer: 1 extendedWarranty: 1 diff --git a/activerecord/test/fixtures/topics.yml b/activerecord/test/fixtures/topics.yml index 2b042bd135..bf049abbf1 100644 --- a/activerecord/test/fixtures/topics.yml +++ b/activerecord/test/fixtures/topics.yml @@ -40,3 +40,10 @@ fourth: type: Reply parent_id: 3 +fifth: + id: 5 + title: The Fifth Topic of the day + author_name: Jason + written_on: 2013-07-13t12:11:00.0099+01:00 + content: Omakase + approved: true diff --git a/activerecord/test/migrations/version_check/20131219224947_migration_version_check.rb b/activerecord/test/migrations/version_check/20131219224947_migration_version_check.rb new file mode 100644 index 0000000000..9d46485a31 --- /dev/null +++ b/activerecord/test/migrations/version_check/20131219224947_migration_version_check.rb @@ -0,0 +1,8 @@ +class MigrationVersionCheck < ActiveRecord::Migration + def self.up + raise "incorrect migration version" unless version == 20131219224947 + end + + def self.down + end +end diff --git a/activerecord/test/models/admin/user.rb b/activerecord/test/models/admin/user.rb index 4c3b71e8f9..48a110bd23 100644 --- a/activerecord/test/models/admin/user.rb +++ b/activerecord/test/models/admin/user.rb @@ -14,6 +14,7 @@ class Admin::User < ActiveRecord::Base end belongs_to :account + store :params, accessors: [ :token ], coder: YAML store :settings, :accessors => [ :color, :homepage ] store_accessor :settings, :favorite_food store :preferences, :accessors => [ :remember_login ] diff --git a/activerecord/test/models/author.rb b/activerecord/test/models/author.rb index 794d1af43d..c197951c71 100644 --- a/activerecord/test/models/author.rb +++ b/activerecord/test/models/author.rb @@ -29,7 +29,7 @@ class Author < ActiveRecord::Base has_many :thinking_posts, -> { where(:title => 'So I was thinking') }, :dependent => :delete_all, :class_name => 'Post' has_many :welcome_posts, -> { where(:title => 'Welcome to the weblog') }, :class_name => 'Post' - has_many :welcome_posts_with_comment, + has_many :welcome_posts_with_one_comment, -> { where(title: 'Welcome to the weblog').where('comments_count = ?', 1) }, class_name: 'Post' has_many :welcome_posts_with_comments, diff --git a/activerecord/test/models/book.rb b/activerecord/test/models/book.rb index 4cb2c7606b..2170018068 100644 --- a/activerecord/test/models/book.rb +++ b/activerecord/test/models/book.rb @@ -9,6 +9,7 @@ class Book < ActiveRecord::Base enum status: [:proposed, :written, :published] enum read_status: {unread: 0, reading: 2, read: 3} + enum nullable_status: [:single, :married] def published! super diff --git a/activerecord/test/models/bulb.rb b/activerecord/test/models/bulb.rb index 4361188e21..831a0d5387 100644 --- a/activerecord/test/models/bulb.rb +++ b/activerecord/test/models/bulb.rb @@ -43,3 +43,9 @@ class FunkyBulb < Bulb raise "before_destroy was called" end end + +class FailedBulb < Bulb + before_destroy do + false + end +end diff --git a/activerecord/test/models/car.rb b/activerecord/test/models/car.rb index 8f3b70a7c6..db0f93f63b 100644 --- a/activerecord/test/models/car.rb +++ b/activerecord/test/models/car.rb @@ -2,6 +2,7 @@ class Car < ActiveRecord::Base has_many :bulbs has_many :all_bulbs, -> { unscope where: :name }, class_name: "Bulb" has_many :funky_bulbs, class_name: 'FunkyBulb', dependent: :destroy + has_many :failed_bulbs, class_name: 'FailedBulb', dependent: :destroy has_many :foo_bulbs, -> { where(:name => 'foo') }, :class_name => "Bulb" has_one :bulb @@ -14,7 +15,6 @@ class Car < ActiveRecord::Base scope :incl_engines, -> { includes(:engines) } scope :order_using_new_style, -> { order('name asc') } - end class CoolCar < Car diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb index a26de55758..762259ffa3 100644 --- a/activerecord/test/models/developer.rb +++ b/activerecord/test/models/developer.rb @@ -36,6 +36,10 @@ class Developer < ActiveRecord::Base end has_and_belongs_to_many :special_projects, :join_table => 'developers_projects', :association_foreign_key => 'project_id' + has_and_belongs_to_many :sym_special_projects, + :join_table => :developers_projects, + :association_foreign_key => 'project_id', + :class_name => 'SpecialProject' has_many :audit_logs has_many :contracts @@ -161,6 +165,8 @@ class DeveloperCalledJamis < ActiveRecord::Base default_scope { where(:name => 'Jamis') } scope :poor, -> { where('salary < 150000') } + scope :david, -> { where name: "David" } + scope :david2, -> { unscoped.where name: "David" } end class PoorDeveloperCalledJamis < ActiveRecord::Base diff --git a/activerecord/test/models/electron.rb b/activerecord/test/models/electron.rb index 35af9f679b..6fc270673f 100644 --- a/activerecord/test/models/electron.rb +++ b/activerecord/test/models/electron.rb @@ -1,3 +1,5 @@ class Electron < ActiveRecord::Base belongs_to :molecule + + validates_presence_of :name end diff --git a/activerecord/test/models/mixed_case_monkey.rb b/activerecord/test/models/mixed_case_monkey.rb index 4d37371777..1c35006665 100644 --- a/activerecord/test/models/mixed_case_monkey.rb +++ b/activerecord/test/models/mixed_case_monkey.rb @@ -1,5 +1,3 @@ class MixedCaseMonkey < ActiveRecord::Base - self.primary_key = 'monkeyID' - belongs_to :man end diff --git a/activerecord/test/models/molecule.rb b/activerecord/test/models/molecule.rb index 69325b8d29..26870c8f88 100644 --- a/activerecord/test/models/molecule.rb +++ b/activerecord/test/models/molecule.rb @@ -1,4 +1,6 @@ class Molecule < ActiveRecord::Base belongs_to :liquid has_many :electrons + + accepts_nested_attributes_for :electrons end diff --git a/activerecord/test/models/owner.rb b/activerecord/test/models/owner.rb index 1c7ed4aa3e..cf24502d3a 100644 --- a/activerecord/test/models/owner.rb +++ b/activerecord/test/models/owner.rb @@ -2,4 +2,21 @@ class Owner < ActiveRecord::Base self.primary_key = :owner_id has_many :pets, -> { order 'pets.name desc' } has_many :toys, :through => :pets + + after_commit :execute_blocks + + def blocks + @blocks ||= [] + end + + def on_after_commit(&block) + blocks << block + end + + def execute_blocks + blocks.each do |block| + block.call(self) + end + @blocks = [] + end end diff --git a/activerecord/test/models/pirate.rb b/activerecord/test/models/pirate.rb index 170fc2ffe3..7bb0caf44b 100644 --- a/activerecord/test/models/pirate.rb +++ b/activerecord/test/models/pirate.rb @@ -13,6 +13,7 @@ class Pirate < ActiveRecord::Base :after_add => proc {|p,pa| p.ship_log << "after_adding_proc_parrot_#{pa.id || '<new>'}"}, :before_remove => proc {|p,pa| p.ship_log << "before_removing_proc_parrot_#{pa.id}"}, :after_remove => proc {|p,pa| p.ship_log << "after_removing_proc_parrot_#{pa.id}"} + has_and_belongs_to_many :autosaved_parrots, class_name: "Parrot", autosave: true has_many :treasures, :as => :looter has_many :treasure_estimates, :through => :treasures, :source => :price_estimates diff --git a/activerecord/test/models/shop.rb b/activerecord/test/models/shop.rb index 81414227ea..607a0a5b41 100644 --- a/activerecord/test/models/shop.rb +++ b/activerecord/test/models/shop.rb @@ -5,6 +5,11 @@ module Shop class Product < ActiveRecord::Base has_many :variants, :dependent => :delete_all + belongs_to :type + + class Type < ActiveRecord::Base + has_many :products + end end class Variant < ActiveRecord::Base diff --git a/activerecord/test/models/topic.rb b/activerecord/test/models/topic.rb index 40c8e97fc2..f81ffe1d90 100644 --- a/activerecord/test/models/topic.rb +++ b/activerecord/test/models/topic.rb @@ -106,6 +106,10 @@ class ImportantTopic < Topic serialize :important, Hash end +class DefaultRejectedTopic < Topic + default_scope -> { where(approved: false) } +end + class BlankTopic < Topic # declared here to make sure that dynamic finder with a bang can find a model that responds to `blank?` def blank? diff --git a/activerecord/test/schema/oracle_specific_schema.rb b/activerecord/test/schema/oracle_specific_schema.rb index 3314687445..a7817772f4 100644 --- a/activerecord/test/schema/oracle_specific_schema.rb +++ b/activerecord/test/schema/oracle_specific_schema.rb @@ -3,7 +3,6 @@ ActiveRecord::Schema.define do execute "drop table test_oracle_defaults" rescue nil execute "drop sequence test_oracle_defaults_seq" rescue nil execute "drop sequence companies_nonstd_seq" rescue nil - execute "drop synonym subjects" rescue nil execute "drop table defaults" rescue nil execute "drop sequence defaults_seq" rescue nil @@ -22,8 +21,6 @@ create sequence test_oracle_defaults_seq minvalue 10000 execute "create sequence companies_nonstd_seq minvalue 10000" - execute "create synonym subjects for topics" - execute <<-SQL CREATE TABLE defaults ( id integer not null, diff --git a/activerecord/test/schema/postgresql_specific_schema.rb b/activerecord/test/schema/postgresql_specific_schema.rb index 6b7012a172..4fcbf4dbd2 100644 --- a/activerecord/test/schema/postgresql_specific_schema.rb +++ b/activerecord/test/schema/postgresql_specific_schema.rb @@ -1,7 +1,7 @@ ActiveRecord::Schema.define do - %w(postgresql_ranges postgresql_tsvectors postgresql_hstores postgresql_arrays postgresql_moneys postgresql_numbers postgresql_times postgresql_network_addresses postgresql_bit_strings postgresql_uuids postgresql_ltrees - postgresql_oids postgresql_xml_data_type defaults geometrics postgresql_timestamp_with_zones postgresql_partitioned_table postgresql_partitioned_table_parent postgresql_json_data_type).each do |table_name| + %w(postgresql_tsvectors postgresql_hstores postgresql_arrays postgresql_moneys postgresql_numbers postgresql_times postgresql_network_addresses postgresql_bit_strings postgresql_uuids postgresql_ltrees + postgresql_oids postgresql_xml_data_type defaults geometrics postgresql_timestamp_with_zones postgresql_partitioned_table postgresql_partitioned_table_parent postgresql_json_data_type postgresql_citext).each do |table_name| execute "DROP TABLE IF EXISTS #{quote_table_name table_name}" end @@ -74,18 +74,6 @@ _SQL ); _SQL - execute <<_SQL if supports_ranges? - CREATE TABLE postgresql_ranges ( - id SERIAL PRIMARY KEY, - date_range daterange, - num_range numrange, - ts_range tsrange, - tstz_range tstzrange, - int4_range int4range, - int8_range int8range - ); -_SQL - execute <<_SQL CREATE TABLE postgresql_tsvectors ( id SERIAL PRIMARY KEY, @@ -111,6 +99,15 @@ _SQL _SQL end + if 't' == select_value("select 'citext'=ANY(select typname from pg_type)") + execute <<_SQL + CREATE TABLE postgresql_citext ( + id SERIAL PRIMARY KEY, + text_citext citext default ''::citext + ); +_SQL + end + if 't' == select_value("select 'json'=ANY(select typname from pg_type)") execute <<_SQL CREATE TABLE postgresql_json_data_type ( @@ -221,4 +218,3 @@ _SQL t.text :text, limit: 100_000 end end - diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index 92e2e4d409..b44e72a67c 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -45,6 +45,7 @@ ActiveRecord::Schema.define do t.string :preferences, null: true, default: '', limit: 1024 t.string :json_data, null: true, limit: 1024 t.string :json_data_empty, null: true, default: "", limit: 1024 + t.text :params t.references :account end @@ -96,6 +97,7 @@ ActiveRecord::Schema.define do t.column :name, :string t.column :status, :integer, default: 0 t.column :read_status, :integer, default: 0 + t.column :nullable_status, :integer end create_table :booleans, force: true do |t| @@ -196,6 +198,7 @@ ActiveRecord::Schema.define do end create_table :computers, force: true do |t| + t.string :system t.integer :developer, null: false t.integer :extendedWarranty, null: false end @@ -245,6 +248,7 @@ ActiveRecord::Schema.define do t.integer :trainer_id t.integer :breeder_id t.integer :dog_lover_id + t.string :alias end create_table :edges, force: true, id: false do |t| @@ -555,9 +559,14 @@ ActiveRecord::Schema.define do create_table :products, force: true do |t| t.references :collection + t.references :type t.string :name end + create_table :product_types, force: true do |t| + t.string :name + end + create_table :projects, force: true do |t| t.string :name t.string :type @@ -665,7 +674,11 @@ ActiveRecord::Schema.define do t.string :title t.string :author_name t.string :author_email_address - t.datetime :written_on + if mysql_56? + t.datetime :written_on, limit: 6 + else + t.datetime :written_on + end t.time :bonus_time t.date :last_read # use VARCHAR2(4000) instead of CLOB datatype as CLOB data type has many limitations in diff --git a/activerecord/test/support/connection.rb b/activerecord/test/support/connection.rb index 196b3a9493..d11fd9cfc1 100644 --- a/activerecord/test/support/connection.rb +++ b/activerecord/test/support/connection.rb @@ -15,7 +15,7 @@ module ARTest puts "Using #{connection_name}" ActiveRecord::Base.logger = ActiveSupport::Logger.new("debug.log", 0, 100 * 1024 * 1024) ActiveRecord::Base.configurations = connection_config - ActiveRecord::Base.establish_connection 'arunit' - ARUnit2Model.establish_connection 'arunit2' + ActiveRecord::Base.establish_connection :arunit + ARUnit2Model.establish_connection :arunit2 end end |