diff options
Diffstat (limited to 'activerecord/test/cases')
122 files changed, 6333 insertions, 5117 deletions
diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb index f1023ed7ef..852fc0e26e 100644 --- a/activerecord/test/cases/adapter_test.rb +++ b/activerecord/test/cases/adapter_test.rb @@ -1,159 +1,162 @@ require "cases/helper" -class AdapterTest < ActiveRecord::TestCase - def setup - @connection = ActiveRecord::Base.connection - end - - def test_tables - tables = @connection.tables - assert tables.include?("accounts") - assert tables.include?("authors") - assert tables.include?("tasks") - assert tables.include?("topics") - end +module ActiveRecord + class AdapterTest < ActiveRecord::TestCase + def setup + @connection = ActiveRecord::Base.connection + end - def test_table_exists? - assert @connection.table_exists?("accounts") - assert !@connection.table_exists?("nonexistingtable") - end + def test_tables + tables = @connection.tables + assert tables.include?("accounts") + assert tables.include?("authors") + assert tables.include?("tasks") + assert tables.include?("topics") + end - def test_indexes - idx_name = "accounts_idx" - - if @connection.respond_to?(:indexes) - indexes = @connection.indexes("accounts") - assert indexes.empty? - - @connection.add_index :accounts, :firm_id, :name => idx_name - indexes = @connection.indexes("accounts") - assert_equal "accounts", indexes.first.table - # OpenBase does not have the concept of a named index - # Indexes are merely properties of columns. - assert_equal idx_name, indexes.first.name unless current_adapter?(:OpenBaseAdapter) - assert !indexes.first.unique - assert_equal ["firm_id"], indexes.first.columns - else - warn "#{@connection.class} does not respond to #indexes" + def test_table_exists? + assert @connection.table_exists?("accounts") + assert !@connection.table_exists?("nonexistingtable") + assert !@connection.table_exists?(nil) end - ensure - @connection.remove_index(:accounts, :name => idx_name) rescue nil - end + def test_indexes + idx_name = "accounts_idx" + + if @connection.respond_to?(:indexes) + indexes = @connection.indexes("accounts") + assert indexes.empty? + + @connection.add_index :accounts, :firm_id, :name => idx_name + indexes = @connection.indexes("accounts") + assert_equal "accounts", indexes.first.table + # OpenBase does not have the concept of a named index + # Indexes are merely properties of columns. + assert_equal idx_name, indexes.first.name unless current_adapter?(:OpenBaseAdapter) + assert !indexes.first.unique + assert_equal ["firm_id"], indexes.first.columns + else + warn "#{@connection.class} does not respond to #indexes" + end - def test_current_database - if @connection.respond_to?(:current_database) - assert_equal ARTest.connection_config['arunit']['database'], @connection.current_database + ensure + @connection.remove_index(:accounts, :name => idx_name) rescue nil end - end - if current_adapter?(:MysqlAdapter) - def test_charset - assert_not_nil @connection.charset - assert_not_equal 'character_set_database', @connection.charset - assert_equal @connection.show_variable('character_set_database'), @connection.charset + def test_current_database + if @connection.respond_to?(:current_database) + assert_equal ARTest.connection_config['arunit']['database'], @connection.current_database + end end - def test_collation - assert_not_nil @connection.collation - assert_not_equal 'collation_database', @connection.collation - assert_equal @connection.show_variable('collation_database'), @connection.collation - end + if current_adapter?(:MysqlAdapter) + def test_charset + assert_not_nil @connection.charset + assert_not_equal 'character_set_database', @connection.charset + assert_equal @connection.show_variable('character_set_database'), @connection.charset + end - def test_show_nonexistent_variable_returns_nil - assert_nil @connection.show_variable('foo_bar_baz') - end + def test_collation + assert_not_nil @connection.collation + assert_not_equal 'collation_database', @connection.collation + assert_equal @connection.show_variable('collation_database'), @connection.collation + end - def test_not_specifying_database_name_for_cross_database_selects - begin - assert_nothing_raised do - ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations['arunit'].except(:database)) + def test_show_nonexistent_variable_returns_nil + assert_nil @connection.show_variable('foo_bar_baz') + end - config = ARTest.connection_config - ActiveRecord::Base.connection.execute( - "SELECT #{config['arunit']['database']}.pirates.*, #{config['arunit2']['database']}.courses.* " \ - "FROM #{config['arunit']['database']}.pirates, #{config['arunit2']['database']}.courses" - ) + def test_not_specifying_database_name_for_cross_database_selects + begin + assert_nothing_raised do + ActiveRecord::Model.establish_connection(ActiveRecord::Base.configurations['arunit'].except(:database)) + + config = ARTest.connection_config + ActiveRecord::Model.connection.execute( + "SELECT #{config['arunit']['database']}.pirates.*, #{config['arunit2']['database']}.courses.* " \ + "FROM #{config['arunit']['database']}.pirates, #{config['arunit2']['database']}.courses" + ) + end + ensure + ActiveRecord::Model.establish_connection 'arunit' end - ensure - ActiveRecord::Base.establish_connection 'arunit' end end - end - def test_table_alias - def @connection.test_table_alias_length() 10; end - class << @connection - alias_method :old_table_alias_length, :table_alias_length - alias_method :table_alias_length, :test_table_alias_length - end + def test_table_alias + def @connection.test_table_alias_length() 10; end + class << @connection + alias_method :old_table_alias_length, :table_alias_length + alias_method :table_alias_length, :test_table_alias_length + end - assert_equal 'posts', @connection.table_alias_for('posts') - assert_equal 'posts_comm', @connection.table_alias_for('posts_comments') - assert_equal 'dbo_posts', @connection.table_alias_for('dbo.posts') + assert_equal 'posts', @connection.table_alias_for('posts') + assert_equal 'posts_comm', @connection.table_alias_for('posts_comments') + assert_equal 'dbo_posts', @connection.table_alias_for('dbo.posts') - class << @connection - remove_method :table_alias_length - alias_method :table_alias_length, :old_table_alias_length + class << @connection + remove_method :table_alias_length + alias_method :table_alias_length, :old_table_alias_length + end end - end - # test resetting sequences in odd tables in postgreSQL - if ActiveRecord::Base.connection.respond_to?(:reset_pk_sequence!) - require 'models/movie' - require 'models/subscriber' + # test resetting sequences in odd tables in postgreSQL + if ActiveRecord::Base.connection.respond_to?(:reset_pk_sequence!) + require 'models/movie' + require 'models/subscriber' - def test_reset_empty_table_with_custom_pk - Movie.delete_all - Movie.connection.reset_pk_sequence! 'movies' - assert_equal 1, Movie.create(:name => 'fight club').id - end + def test_reset_empty_table_with_custom_pk + Movie.delete_all + Movie.connection.reset_pk_sequence! 'movies' + assert_equal 1, Movie.create(:name => 'fight club').id + end - if ActiveRecord::Base.connection.adapter_name != "FrontBase" - def test_reset_table_with_non_integer_pk - Subscriber.delete_all - Subscriber.connection.reset_pk_sequence! 'subscribers' - sub = Subscriber.new(:name => 'robert drake') - sub.id = 'bob drake' - assert_nothing_raised { sub.save! } + if ActiveRecord::Base.connection.adapter_name != "FrontBase" + def test_reset_table_with_non_integer_pk + Subscriber.delete_all + Subscriber.connection.reset_pk_sequence! 'subscribers' + sub = Subscriber.new(:name => 'robert drake') + sub.id = 'bob drake' + assert_nothing_raised { sub.save! } + end end end - end - def test_uniqueness_violations_are_translated_to_specific_exception - @connection.execute "INSERT INTO subscribers(nick) VALUES('me')" - assert_raises(ActiveRecord::RecordNotUnique) do + def test_uniqueness_violations_are_translated_to_specific_exception @connection.execute "INSERT INTO subscribers(nick) VALUES('me')" + assert_raises(ActiveRecord::RecordNotUnique) do + @connection.execute "INSERT INTO subscribers(nick) VALUES('me')" + end end - end - def test_foreign_key_violations_are_translated_to_specific_exception - unless @connection.adapter_name == 'SQLite' - assert_raises(ActiveRecord::InvalidForeignKey) do - # Oracle adapter uses prefetched primary key values from sequence and passes them to connection adapter insert method - if @connection.prefetch_primary_key? - id_value = @connection.next_sequence_value(@connection.default_sequence_name("fk_test_has_fk", "id")) - @connection.execute "INSERT INTO fk_test_has_fk (id, fk_id) VALUES (#{id_value},0)" - else - @connection.execute "INSERT INTO fk_test_has_fk (fk_id) VALUES (0)" + def test_foreign_key_violations_are_translated_to_specific_exception + unless @connection.adapter_name == 'SQLite' + assert_raises(ActiveRecord::InvalidForeignKey) do + # Oracle adapter uses prefetched primary key values from sequence and passes them to connection adapter insert method + if @connection.prefetch_primary_key? + id_value = @connection.next_sequence_value(@connection.default_sequence_name("fk_test_has_fk", "id")) + @connection.execute "INSERT INTO fk_test_has_fk (id, fk_id) VALUES (#{id_value},0)" + else + @connection.execute "INSERT INTO fk_test_has_fk (fk_id) VALUES (0)" + end end end end - end - def test_disable_referential_integrity - assert_nothing_raised do - @connection.disable_referential_integrity do - # Oracle adapter uses prefetched primary key values from sequence and passes them to connection adapter insert method - if @connection.prefetch_primary_key? - id_value = @connection.next_sequence_value(@connection.default_sequence_name("fk_test_has_fk", "id")) - @connection.execute "INSERT INTO fk_test_has_fk (id, fk_id) VALUES (#{id_value},0)" - else - @connection.execute "INSERT INTO fk_test_has_fk (fk_id) VALUES (0)" + def test_disable_referential_integrity + assert_nothing_raised do + @connection.disable_referential_integrity do + # Oracle adapter uses prefetched primary key values from sequence and passes them to connection adapter insert method + if @connection.prefetch_primary_key? + id_value = @connection.next_sequence_value(@connection.default_sequence_name("fk_test_has_fk", "id")) + @connection.execute "INSERT INTO fk_test_has_fk (id, fk_id) VALUES (#{id_value},0)" + else + @connection.execute "INSERT INTO fk_test_has_fk (fk_id) VALUES (0)" + end + # should deleted created record as otherwise disable_referential_integrity will try to enable contraints after executed block + # and will fail (at least on Oracle) + @connection.execute "DELETE FROM fk_test_has_fk" end - # should deleted created record as otherwise disable_referential_integrity will try to enable contraints after executed block - # and will fail (at least on Oracle) - @connection.execute "DELETE FROM fk_test_has_fk" end end end diff --git a/activerecord/test/cases/adapters/mysql/connection_test.rb b/activerecord/test/cases/adapters/mysql/connection_test.rb index 2a89430da9..fa2ba8d592 100644 --- a/activerecord/test/cases/adapters/mysql/connection_test.rb +++ b/activerecord/test/cases/adapters/mysql/connection_test.rb @@ -3,13 +3,13 @@ require "cases/helper" class MysqlConnectionTest < ActiveRecord::TestCase def setup super - @connection = ActiveRecord::Base.connection + @connection = ActiveRecord::Model.connection end def test_mysql_reconnect_attribute_after_connection_with_reconnect_true run_without_connection do |orig_connection| - ActiveRecord::Base.establish_connection(orig_connection.merge({:reconnect => true})) - assert ActiveRecord::Base.connection.raw_connection.reconnect + ActiveRecord::Model.establish_connection(orig_connection.merge({:reconnect => true})) + assert ActiveRecord::Model.connection.raw_connection.reconnect end end @@ -25,8 +25,8 @@ class MysqlConnectionTest < ActiveRecord::TestCase def test_mysql_reconnect_attribute_after_connection_with_reconnect_false run_without_connection do |orig_connection| - ActiveRecord::Base.establish_connection(orig_connection.merge({:reconnect => false})) - assert !ActiveRecord::Base.connection.raw_connection.reconnect + ActiveRecord::Model.establish_connection(orig_connection.merge({:reconnect => false})) + assert !ActiveRecord::Model.connection.raw_connection.reconnect end end @@ -114,7 +114,7 @@ class MysqlConnectionTest < ActiveRecord::TestCase # Test that MySQL allows multiple results for stored procedures if defined?(Mysql) && Mysql.const_defined?(:CLIENT_MULTI_RESULTS) def test_multi_results - rows = ActiveRecord::Base.connection.select_rows('CALL ten();') + rows = ActiveRecord::Model.connection.select_rows('CALL ten();') assert_equal 10, rows[0][0].to_i, "ten() did not return 10 as expected: #{rows.inspect}" assert @connection.active?, "Bad connection use by 'MysqlAdapter.select_rows'" end @@ -123,11 +123,11 @@ class MysqlConnectionTest < ActiveRecord::TestCase private def run_without_connection - original_connection = ActiveRecord::Base.remove_connection + original_connection = ActiveRecord::Model.remove_connection begin yield original_connection ensure - ActiveRecord::Base.establish_connection(original_connection) + ActiveRecord::Model.establish_connection(original_connection) end end end diff --git a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb index 146b77a95c..475a292f85 100644 --- a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb +++ b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb @@ -17,11 +17,7 @@ module ActiveRecord end def test_client_encoding - if "<3".respond_to?(:encoding) - assert_equal Encoding::UTF_8, @conn.client_encoding - else - assert_equal 'utf8', @conn.client_encoding - end + assert_equal Encoding::UTF_8, @conn.client_encoding end def test_exec_insert_number @@ -41,17 +37,55 @@ module ActiveRecord value = result.rows.last.last - if "<3".respond_to?(:encoding) - # FIXME: this should probably be inside the mysql AR adapter? - value.force_encoding(@conn.client_encoding) + # FIXME: this should probably be inside the mysql AR adapter? + value.force_encoding(@conn.client_encoding) - # The strings in this file are utf-8, so transcode to utf-8 - value.encode!(Encoding::UTF_8) - end + # The strings in this file are utf-8, so transcode to utf-8 + value.encode!(Encoding::UTF_8) assert_equal str, value end + def test_tables_quoting + begin + @conn.tables(nil, "foo-bar", nil) + flunk + rescue => e + # assertion for *quoted* database properly + assert_match(/database 'foo-bar'/, e.inspect) + end + end + + def test_pk_and_sequence_for + pk, seq = @conn.pk_and_sequence_for('ex') + assert_equal 'id', pk + assert_equal @conn.default_sequence_name('ex', 'id'), seq + end + + def test_pk_and_sequence_for_with_non_standard_primary_key + @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, + PRIMARY KEY (`code`)) + eosql + pk, seq = @conn.pk_and_sequence_for('ex_with_non_standard_pk') + assert_equal 'code', pk + assert_equal @conn.default_sequence_name('ex_with_non_standard_pk', 'code'), seq + end + + def test_pk_and_sequence_for_with_custom_index_type_pk + @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, + PRIMARY KEY USING BTREE (`id`)) + eosql + pk, seq = @conn.pk_and_sequence_for('ex_with_custom_index_type_pk') + assert_equal 'id', pk + assert_equal @conn.default_sequence_name('ex_with_custom_index_type_pk', 'id'), seq + end + private def insert(ctx, data) binds = data.map { |name, value| diff --git a/activerecord/test/cases/adapters/mysql/reserved_word_test.rb b/activerecord/test/cases/adapters/mysql/reserved_word_test.rb index 292c7efebb..6faceaf7c0 100644 --- a/activerecord/test/cases/adapters/mysql/reserved_word_test.rb +++ b/activerecord/test/cases/adapters/mysql/reserved_word_test.rb @@ -130,7 +130,7 @@ class MysqlReservedWordTest < ActiveRecord::TestCase end def test_associations_work_with_reserved_words - assert_nothing_raised { Select.find(:all, :include => [:groups]) } + assert_nothing_raised { Select.scoped(:includes => [:groups]).all } end #the following functions were added to DRY test cases diff --git a/activerecord/test/cases/adapters/mysql/schema_test.rb b/activerecord/test/cases/adapters/mysql/schema_test.rb index 1aa034ed53..d94bb629a7 100644 --- a/activerecord/test/cases/adapters/mysql/schema_test.rb +++ b/activerecord/test/cases/adapters/mysql/schema_test.rb @@ -14,13 +14,13 @@ module ActiveRecord @db_name = db @omgpost = Class.new(ActiveRecord::Base) do - set_table_name "#{db}.#{table}" + self.table_name = "#{db}.#{table}" def self.name; 'Post'; end end end def test_schema - assert @omgpost.find(:first) + assert @omgpost.first end def test_primary_key diff --git a/activerecord/test/cases/adapters/mysql2/bind_parameter_test.rb b/activerecord/test/cases/adapters/mysql2/bind_parameter_test.rb index cd9c1041dc..5e8065d80d 100644 --- a/activerecord/test/cases/adapters/mysql2/bind_parameter_test.rb +++ b/activerecord/test/cases/adapters/mysql2/bind_parameter_test.rb @@ -9,7 +9,7 @@ module ActiveRecord def test_update_question_marks str = "foo?bar" - x = Topic.find :first + x = Topic.first x.title = str x.content = str x.save! @@ -28,7 +28,7 @@ module ActiveRecord def test_update_null_bytes str = "foo\0bar" - x = Topic.find :first + x = Topic.first x.title = str x.content = str x.save! diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb index 26091c713b..8e2b9ca9a5 100644 --- a/activerecord/test/cases/adapters/mysql2/connection_test.rb +++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb @@ -3,7 +3,7 @@ require "cases/helper" class MysqlConnectionTest < ActiveRecord::TestCase def setup super - @connection = ActiveRecord::Base.connection + @connection = ActiveRecord::Model.connection end def test_no_automatic_reconnection_after_timeout @@ -32,11 +32,11 @@ class MysqlConnectionTest < ActiveRecord::TestCase private def run_without_connection - original_connection = ActiveRecord::Base.remove_connection + original_connection = ActiveRecord::Model.remove_connection begin yield original_connection ensure - ActiveRecord::Base.establish_connection(original_connection) + ActiveRecord::Model.establish_connection(original_connection) end end end diff --git a/activerecord/test/cases/adapters/mysql2/explain_test.rb b/activerecord/test/cases/adapters/mysql2/explain_test.rb index 8ea777b72b..68ed361aeb 100644 --- a/activerecord/test/cases/adapters/mysql2/explain_test.rb +++ b/activerecord/test/cases/adapters/mysql2/explain_test.rb @@ -9,12 +9,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 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 %(EXPLAIN for: SELECT `audit_logs`.* FROM `audit_logs` WHERE `audit_logs`.`developer_id` IN (1)), explain assert_match %(audit_logs | ALL), explain end end diff --git a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb index 3a9744e78f..32d4282623 100644 --- a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb +++ b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb @@ -130,7 +130,7 @@ class MysqlReservedWordTest < ActiveRecord::TestCase end def test_associations_work_with_reserved_words - assert_nothing_raised { Select.find(:all, :include => [:groups]) } + assert_nothing_raised { Select.scoped(:includes => [:groups]).all } end #the following functions were added to DRY test cases diff --git a/activerecord/test/cases/adapters/mysql2/schema_test.rb b/activerecord/test/cases/adapters/mysql2/schema_test.rb index 49514e1539..2c0ed73c92 100644 --- a/activerecord/test/cases/adapters/mysql2/schema_test.rb +++ b/activerecord/test/cases/adapters/mysql2/schema_test.rb @@ -14,13 +14,13 @@ module ActiveRecord @db_name = db @omgpost = Class.new(ActiveRecord::Base) do - set_table_name "#{db}.#{table}" + self.table_name = "#{db}.#{table}" def self.name; 'Post'; end end end def test_schema - assert @omgpost.find(:first) + assert @omgpost.first end def test_primary_key @@ -35,6 +35,17 @@ module ActiveRecord def test_table_exists_wrong_schema assert(!@connection.table_exists?("#{@db_name}.zomg"), "table should not exist") end + + def test_tables_quoting + begin + @connection.tables(nil, "foo-bar", nil) + flunk + rescue => e + # assertion for *quoted* database properly + assert_match(/database 'foo-bar'/, e.inspect) + end + end + end end end diff --git a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb index e4746d4aa3..447d729e52 100644 --- a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb +++ b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb @@ -21,6 +21,18 @@ class PostgresqlActiveSchemaTest < ActiveRecord::TestCase assert_equal %(CREATE DATABASE "aimonetti" ENCODING = 'latin1'), create_database(:aimonetti, :encoding => :latin1) end + def test_add_index + # add_index calls index_name_exists? which can't work since execute is stubbed + ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.send(:define_method, :index_name_exists?) do |*| + false + end + + expected = %(CREATE UNIQUE INDEX "index_people_on_last_name" ON "people" ("last_name") WHERE state = 'active') + assert_equal expected, add_index(:people, :last_name, :unique => true, :where => "state = 'active'") + + ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.send(:remove_method, :index_name_exists?) + end + private def method_missing(method_symbol, *arguments) ActiveRecord::Base.connection.send(method_symbol, *arguments) diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb index 21b97b3b39..4baec749ff 100644 --- a/activerecord/test/cases/adapters/postgresql/connection_test.rb +++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb @@ -2,6 +2,9 @@ require "cases/helper" module ActiveRecord class PostgresqlConnectionTest < ActiveRecord::TestCase + class NonExistentTable < ActiveRecord::Base + end + def setup super @connection = ActiveRecord::Base.connection @@ -10,5 +13,17 @@ module ActiveRecord def test_encoding assert_not_nil @connection.encoding end + + # Ensure, we can set connection params using the example of Generic + # Query Optimizer (geqo). It is 'on' per default. + def test_connection_options + params = ActiveRecord::Base.connection_config.dup + params[:options] = "-c geqo=off" + NonExistentTable.establish_connection(params) + + # Verify the connection param has been applied. + expect = NonExistentTable.connection.query('show geqo').first.first + assert_equal 'off', expect + end end end diff --git a/activerecord/test/cases/adapters/postgresql/explain_test.rb b/activerecord/test/cases/adapters/postgresql/explain_test.rb index 0d599ed37f..619d581d5f 100644 --- a/activerecord/test/cases/adapters/postgresql/explain_test.rb +++ b/activerecord/test/cases/adapters/postgresql/explain_test.rb @@ -9,6 +9,7 @@ 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 %(QUERY PLAN), explain assert_match %(Index Scan using developers_pkey on developers), explain end @@ -16,9 +17,18 @@ module ActiveRecord def test_explain_with_eager_loading explain = Developer.where(:id => 1).includes(:audit_logs).explain assert_match %(QUERY PLAN), explain + assert_match %(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = 1), explain assert_match %(Index Scan using developers_pkey on developers), explain + assert_match %(EXPLAIN for: SELECT "audit_logs".* FROM "audit_logs" WHERE "audit_logs"."developer_id" IN (1)), explain assert_match %(Seq Scan on audit_logs), explain end + + def test_dont_explain_for_set_search_path + queries = Thread.current[:available_queries_for_explain] = [] + ActiveRecord::Base.connection.schema_search_path = "public" + assert queries.empty? + end + end end end diff --git a/activerecord/test/cases/adapters/postgresql/hstore_test.rb b/activerecord/test/cases/adapters/postgresql/hstore_test.rb new file mode 100644 index 0000000000..23bafde17b --- /dev/null +++ b/activerecord/test/cases/adapters/postgresql/hstore_test.rb @@ -0,0 +1,157 @@ +# encoding: utf-8 + +require "cases/helper" +require 'active_record/base' +require 'active_record/connection_adapters/postgresql_adapter' + +class PostgresqlHstoreTest < ActiveRecord::TestCase + class Hstore < ActiveRecord::Base + self.table_name = 'hstores' + end + + def setup + @connection = ActiveRecord::Base.connection + begin + @connection.transaction do + @connection.create_table('hstores') do |t| + t.hstore 'tags', :default => '' + end + end + rescue ActiveRecord::StatementInvalid + return skip "do not test on PG without hstore" + end + @column = Hstore.columns.find { |c| c.name == 'tags' } + end + + def teardown + @connection.execute 'drop table if exists hstores' + end + + def test_column + assert_equal :hstore, @column.type + end + + def test_type_cast_hstore + assert @column + + data = "\"1\"=>\"2\"" + hash = @column.class.string_to_hstore data + assert_equal({'1' => '2'}, hash) + assert_equal({'1' => '2'}, @column.type_cast(data)) + + assert_equal({}, @column.type_cast("")) + assert_equal({'key'=>nil}, @column.type_cast('key => NULL')) + assert_equal({'c'=>'}','"a"'=>'b "a b'}, @column.type_cast(%q(c=>"}", "\"a\""=>"b \"a b"))) + end + + def test_gen1 + assert_equal(%q(" "=>""), @column.class.hstore_to_string({' '=>''})) + end + + def test_gen2 + assert_equal(%q(","=>""), @column.class.hstore_to_string({','=>''})) + end + + def test_gen3 + assert_equal(%q("="=>""), @column.class.hstore_to_string({'='=>''})) + end + + def test_gen4 + assert_equal(%q(">"=>""), @column.class.hstore_to_string({'>'=>''})) + end + + def test_parse1 + assert_equal({'a'=>nil,'b'=>nil,'c'=>'NuLl','null'=>'c'}, @column.type_cast('a=>null,b=>NuLl,c=>"NuLl",null=>c')) + end + + def test_parse2 + assert_equal({" " => " "}, @column.type_cast("\\ =>\\ ")) + end + + def test_parse3 + assert_equal({"=" => ">"}, @column.type_cast("==>>")) + end + + def test_parse4 + assert_equal({"=a"=>"q=w"}, @column.type_cast('\=a=>q=w')) + end + + def test_parse5 + assert_equal({"=a"=>"q=w"}, @column.type_cast('"=a"=>q\=w')) + end + + def test_parse6 + assert_equal({"\"a"=>"q>w"}, @column.type_cast('"\"a"=>q>w')) + end + + def test_parse7 + assert_equal({"\"a"=>"q\"w"}, @column.type_cast('\"a=>q"w')) + end + + def test_rewrite + @connection.execute "insert into hstores (tags) VALUES ('1=>2')" + x = Hstore.first + x.tags = { '"a\'' => 'b' } + assert x.save! + end + + + def test_select + @connection.execute "insert into hstores (tags) VALUES ('1=>2')" + x = Hstore.first + assert_equal({'1' => '2'}, x.tags) + end + + def test_select_multikey + @connection.execute "insert into hstores (tags) VALUES ('1=>2,2=>3')" + x = Hstore.first + assert_equal({'1' => '2', '2' => '3'}, x.tags) + end + + def test_create + assert_cycle('a' => 'b', '1' => '2') + end + + def test_nil + assert_cycle('a' => nil) + end + + def test_quotes + assert_cycle('a' => 'b"ar', '1"foo' => '2') + end + + def test_whitespace + assert_cycle('a b' => 'b ar', '1"foo' => '2') + end + + def test_backslash + assert_cycle('a\\b' => 'b\\ar', '1"foo' => '2') + end + + def test_comma + assert_cycle('a, b' => 'bar', '1"foo' => '2') + end + + def test_arrow + assert_cycle('a=>b' => 'bar', '1"foo' => '2') + end + + def test_quoting_special_characters + assert_cycle('ca' => 'cà', 'ac' => 'àc') + end + + private + def assert_cycle hash + # test creation + x = Hstore.create!(:tags => hash) + x.reload + assert_equal(hash, x.tags) + + # test updating + x = Hstore.create!(:tags => {}) + x.tags = hash + x.save! + x.reload + assert_equal(hash, x.tags) + 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 d57794daf8..92e31a3e44 100644 --- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb +++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb @@ -49,6 +49,33 @@ module ActiveRecord assert_equal expect, id end + def test_insert_sql_with_returning_disabled + connection = connection_without_insert_returning + id = connection.insert_sql("insert into postgresql_partitioned_table_parent (number) VALUES (1)") + expect = connection.query('select max(id) from postgresql_partitioned_table_parent').first.first + assert_equal expect, id + end + + def test_exec_insert_with_returning_disabled + connection = connection_without_insert_returning + result = connection.exec_insert("insert into postgresql_partitioned_table_parent (number) VALUES (1)", nil, [], 'id', 'postgresql_partitioned_table_parent_id_seq') + expect = connection.query('select max(id) from postgresql_partitioned_table_parent').first.first + assert_equal expect, result.rows.first.first + end + + def test_exec_insert_with_returning_disabled_and_no_sequence_name_given + connection = connection_without_insert_returning + result = connection.exec_insert("insert into postgresql_partitioned_table_parent (number) VALUES (1)", nil, [], 'id') + expect = connection.query('select max(id) from postgresql_partitioned_table_parent').first.first + assert_equal expect, result.rows.first.first + end + + def test_sql_for_insert_with_returning_disabled + connection = connection_without_insert_returning + result = connection.sql_for_insert('sql', nil, nil, nil, 'binds') + assert_equal ['sql', 'binds'], result + end + def test_serial_sequence assert_equal 'public.accounts_id_seq', @connection.serial_sequence('accounts', 'id') @@ -179,6 +206,17 @@ module ActiveRecord assert_equal Arel.sql('$2'), bind 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 + end + + def test_distinct_with_nulls + assert_equal "DISTINCT posts.title, posts.updater_id AS alias_0", @connection.distinct("posts.title", ["posts.updater_id desc nulls first"]) + assert_equal "DISTINCT posts.title, posts.updater_id AS alias_0", @connection.distinct("posts.title", ["posts.updater_id desc nulls last"]) + end + private def insert(ctx, data) binds = data.map { |name, value| @@ -193,6 +231,10 @@ module ActiveRecord ctx.exec_insert(sql, 'SQL', binds) end + + def connection_without_insert_returning + ActiveRecord::Base.postgresql_connection(ActiveRecord::Base.configurations['arunit'].merge(:insert_returning => false)) + end end end end diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb index 467e5d7b86..9208f53997 100644 --- a/activerecord/test/cases/adapters/postgresql/schema_test.rb +++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb @@ -24,25 +24,27 @@ class SchemaTest < ActiveRecord::TestCase 'moment timestamp without time zone default now()' ] PK_TABLE_NAME = 'table_with_pk' + UNMATCHED_SEQUENCE_NAME = 'unmatched_primary_key_default_value_seq' + UNMATCHED_PK_TABLE_NAME = 'table_with_unmatched_sequence_for_pk' class Thing1 < ActiveRecord::Base - set_table_name "test_schema.things" + self.table_name = "test_schema.things" end class Thing2 < ActiveRecord::Base - set_table_name "test_schema2.things" + self.table_name = "test_schema2.things" end class Thing3 < ActiveRecord::Base - set_table_name 'test_schema."things.table"' + self.table_name = 'test_schema."things.table"' end class Thing4 < ActiveRecord::Base - set_table_name 'test_schema."Things"' + self.table_name = 'test_schema."Things"' end class Thing5 < ActiveRecord::Base - set_table_name 'things' + self.table_name = 'things' end def setup @@ -60,6 +62,8 @@ class SchemaTest < ActiveRecord::TestCase @connection.execute "CREATE INDEX #{INDEX_D_NAME} ON #{SCHEMA_NAME}.#{TABLE_NAME} USING btree (#{INDEX_D_COLUMN} DESC);" @connection.execute "CREATE INDEX #{INDEX_D_NAME} ON #{SCHEMA2_NAME}.#{TABLE_NAME} USING btree (#{INDEX_D_COLUMN} DESC);" @connection.execute "CREATE TABLE #{SCHEMA_NAME}.#{PK_TABLE_NAME} (id serial primary key)" + @connection.execute "CREATE SEQUENCE #{SCHEMA_NAME}.#{UNMATCHED_SEQUENCE_NAME}" + @connection.execute "CREATE TABLE #{SCHEMA_NAME}.#{UNMATCHED_PK_TABLE_NAME} (id integer NOT NULL DEFAULT nextval('#{SCHEMA_NAME}.#{UNMATCHED_SEQUENCE_NAME}'::regclass), CONSTRAINT unmatched_pkey PRIMARY KEY (id))" end def teardown @@ -67,12 +71,55 @@ class SchemaTest < ActiveRecord::TestCase @connection.execute "DROP SCHEMA #{SCHEMA_NAME} CASCADE" end + def test_schema_names + assert_equal ["public", "test_schema", "test_schema2"], @connection.schema_names + end + + def test_create_schema + begin + @connection.create_schema "test_schema3" + assert @connection.schema_names.include? "test_schema3" + ensure + @connection.drop_schema "test_schema3" + end + end + + def test_raise_create_schema_with_existing_schema + begin + @connection.create_schema "test_schema3" + assert_raises(ActiveRecord::StatementInvalid) do + @connection.create_schema "test_schema3" + end + ensure + @connection.drop_schema "test_schema3" + end + end + + def test_drop_schema + begin + @connection.create_schema "test_schema3" + ensure + @connection.drop_schema "test_schema3" + end + assert !@connection.schema_names.include?("test_schema3") + end + + def test_raise_drop_schema_with_nonexisting_schema + assert_raises(ActiveRecord::StatementInvalid) do + @connection.drop_schema "test_schema3" + end + end + def test_schema_change_with_prepared_stmt + altered = false @connection.exec_query "select * from developers where id = $1", 'sql', [[nil, 1]] @connection.exec_query "alter table developers add column zomg int", 'sql', [] + altered = true @connection.exec_query "select * from developers where id = $1", 'sql', [[nil, 1]] ensure - @connection.exec_query "alter table developers drop column if exists zomg", 'sql', [] + # We are not using DROP COLUMN IF EXISTS because that syntax is only + # supported by pg 9.X + @connection.exec_query("alter table developers drop column zomg", 'sql', []) if altered end def test_table_exists? @@ -175,13 +222,13 @@ class SchemaTest < ActiveRecord::TestCase end def test_raise_on_unquoted_schema_name - assert_raise(ActiveRecord::StatementInvalid) do + assert_raises(ActiveRecord::StatementInvalid) do with_schema_search_path '$user,public' end end def test_without_schema_search_path - assert_raise(ActiveRecord::StatementInvalid) { columns(TABLE_NAME) } + assert_raises(ActiveRecord::StatementInvalid) { columns(TABLE_NAME) } end def test_ignore_nil_schema_search_path @@ -237,12 +284,12 @@ class SchemaTest < ActiveRecord::TestCase def test_pk_and_sequence_for_with_schema_specified [ %("#{SCHEMA_NAME}"."#{PK_TABLE_NAME}"), - %(#{SCHEMA_NAME}."#{PK_TABLE_NAME}"), - %(#{SCHEMA_NAME}.#{PK_TABLE_NAME}) + %("#{SCHEMA_NAME}"."#{UNMATCHED_PK_TABLE_NAME}") ].each do |given| pk, seq = @connection.pk_and_sequence_for(given) assert_equal 'id', pk, "primary key should be found when table referenced as #{given}" - assert_equal "#{SCHEMA_NAME}.#{PK_TABLE_NAME}_id_seq", seq, "sequence name should be found when table referenced as #{given}" + assert_equal "#{PK_TABLE_NAME}_id_seq", seq, "sequence name should be found when table referenced as #{given}" if given == %("#{SCHEMA_NAME}"."#{PK_TABLE_NAME}") + assert_equal "#{UNMATCHED_SEQUENCE_NAME}", seq, "sequence name should be found when table referenced as #{given}" if given == %("#{SCHEMA_NAME}"."#{UNMATCHED_PK_TABLE_NAME}") end end diff --git a/activerecord/test/cases/adapters/postgresql/utils_test.rb b/activerecord/test/cases/adapters/postgresql/utils_test.rb index 5f08f79171..9e7b08ef34 100644 --- a/activerecord/test/cases/adapters/postgresql/utils_test.rb +++ b/activerecord/test/cases/adapters/postgresql/utils_test.rb @@ -1,3 +1,5 @@ +require 'cases/helper' + class PostgreSQLUtilsTest < ActiveSupport::TestCase include ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::Utils diff --git a/activerecord/test/cases/adapters/postgresql/view_test.rb b/activerecord/test/cases/adapters/postgresql/view_test.rb index 303ba9245a..66e07b71a0 100644 --- a/activerecord/test/cases/adapters/postgresql/view_test.rb +++ b/activerecord/test/cases/adapters/postgresql/view_test.rb @@ -14,7 +14,7 @@ class ViewTest < ActiveRecord::TestCase ] class ThingView < ActiveRecord::Base - set_table_name 'test_schema.view_things' + self.table_name = 'test_schema.view_things' end def setup diff --git a/activerecord/test/cases/adapters/sqlite3/explain_test.rb b/activerecord/test/cases/adapters/sqlite3/explain_test.rb index e18892821d..b227bce680 100644 --- a/activerecord/test/cases/adapters/sqlite3/explain_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/explain_test.rb @@ -9,12 +9,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(/(SEARCH )?TABLE developers USING (INTEGER )?PRIMARY KEY/, 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(/(SEARCH )?TABLE developers USING (INTEGER )?PRIMARY KEY/, explain) + assert_match %(EXPLAIN for: SELECT "audit_logs".* FROM "audit_logs" WHERE "audit_logs"."developer_id" IN (1)), explain assert_match(/(SCAN )?TABLE audit_logs/, explain) end end diff --git a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb index e0152e7ccf..2ba9143cd5 100644 --- a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb @@ -1,10 +1,11 @@ require "cases/helper" require 'bigdecimal' require 'yaml' +require 'securerandom' module ActiveRecord module ConnectionAdapters - class SQLiteAdapter + class SQLite3Adapter class QuotingTest < ActiveRecord::TestCase def setup @conn = Base.sqlite3_connection :database => ':memory:', @@ -12,6 +13,14 @@ module ActiveRecord :timeout => 100 end + def test_type_cast_binary_encoding_without_logger + @conn.extend(Module.new { def logger; end }) + column = Struct.new(:type, :name).new(:string, "foo") + binary = SecureRandom.hex + expected = binary.dup.encode!('utf-8') + assert_equal expected, @conn.type_cast(binary, column) + end + def test_type_cast_symbol assert_equal 'foo', @conn.type_cast(:foo, nil) end @@ -70,9 +79,9 @@ module ActiveRecord assert_equal bd.to_f, @conn.type_cast(bd, nil) end - def test_type_cast_unknown + def test_type_cast_unknown_should_raise_error obj = Class.new.new - assert_equal YAML.dump(obj), @conn.type_cast(obj, nil) + assert_raise(TypeError) { @conn.type_cast(obj, nil) } end def test_quoted_id diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb index 97b56d38d7..17bde6cb62 100644 --- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb @@ -23,8 +23,6 @@ module ActiveRecord end def test_column_types - return skip('only test encoding on 1.9') unless "<3".encoding_aware? - owner = Owner.create!(:name => "hello".encode('ascii-8bit')) owner.reload select = Owner.columns.map { |c| "typeof(#{c.name})" }.join ', ' @@ -144,8 +142,6 @@ module ActiveRecord end def test_quote_binary_column_escapes_it - return unless "<3".respond_to?(:encode) - DualEncoding.connection.execute(<<-eosql) CREATE TABLE dual_encodings ( id integer PRIMARY KEY AUTOINCREMENT, @@ -159,9 +155,7 @@ module ActiveRecord assert_equal str, binary.data ensure - if "<3".respond_to?(:encode) - DualEncoding.connection.drop_table('dual_encodings') - end + DualEncoding.connection.drop_table('dual_encodings') end def test_execute diff --git a/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb b/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb index ae272e2c4b..2f04c60a9a 100644 --- a/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb @@ -1,7 +1,7 @@ require 'cases/helper' module ActiveRecord::ConnectionAdapters - class SQLiteAdapter + class SQLite3Adapter class StatementPoolTest < ActiveRecord::TestCase def test_cache_is_per_pid return skip('must support fork') unless Process.respond_to?(:fork) diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb index 1160d236c9..b351196fbd 100644 --- a/activerecord/test/cases/associations/belongs_to_associations_test.rb +++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb @@ -73,14 +73,14 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase def test_eager_loading_with_primary_key Firm.create("name" => "Apple") Client.create("name" => "Citibank", :firm_name => "Apple") - citibank_result = Client.find(:first, :conditions => {:name => "Citibank"}, :include => :firm_with_primary_key) + citibank_result = Client.scoped(:where => {:name => "Citibank"}, :includes => :firm_with_primary_key).first assert citibank_result.association_cache.key?(:firm_with_primary_key) end def test_eager_loading_with_primary_key_as_symbol Firm.create("name" => "Apple") Client.create("name" => "Citibank", :firm_name => "Apple") - citibank_result = Client.find(:first, :conditions => {:name => "Citibank"}, :include => :firm_with_primary_key_symbols) + citibank_result = Client.scoped(:where => {:name => "Citibank"}, :includes => :firm_with_primary_key_symbols).first assert citibank_result.association_cache.key?(:firm_with_primary_key_symbols) end @@ -168,6 +168,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase sponsor.sponsorable = Member.new :name => "Bert" assert_equal Member, sponsor.association(:sponsorable).send(:klass) + assert_equal "members", sponsor.association(:sponsorable).aliased_table_name end def test_with_polymorphic_and_condition @@ -181,7 +182,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase def test_with_select assert_equal Company.find(2).firm_with_select.attributes.size, 1 - assert_equal Company.find(2, :include => :firm_with_select ).firm_with_select.attributes.size, 1 + assert_equal Company.scoped(:includes => :firm_with_select ).find(2).firm_with_select.attributes.size, 1 end def test_belongs_to_counter @@ -333,7 +334,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase def test_new_record_with_foreign_key_but_no_object c = Client.new("firm_id" => 1) # sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first - assert_equal Firm.find(:first, :order => "id"), c.firm_with_basic_id + assert_equal Firm.scoped(:order => "id").first, c.firm_with_basic_id end def test_setting_foreign_key_after_nil_target_loaded @@ -393,9 +394,9 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase end def test_association_assignment_sticks - post = Post.find(:first) + post = Post.first - author1, author2 = Author.find(:all, :limit => 2) + author1, author2 = Author.scoped(:limit => 2).all assert_not_nil author1 assert_not_nil author2 @@ -497,14 +498,14 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase assert_nothing_raised do Account.find(@account.id).save! - Account.find(@account.id, :include => :firm).save! + Account.scoped(:includes => :firm).find(@account.id).save! end @account.firm.delete assert_nothing_raised do Account.find(@account.id).save! - Account.find(@account.id, :include => :firm).save! + Account.scoped(:includes => :firm).find(@account.id).save! end end @@ -704,4 +705,27 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase assert_equal toy, sponsor.reload.sponsorable end + + test "stale tracking doesn't care about the type" do + apple = Firm.create("name" => "Apple") + citibank = Account.create("credit_limit" => 10) + + citibank.firm_id = apple.id + citibank.firm # load it + + citibank.firm_id = apple.id.to_s + + assert !citibank.association(:firm).stale_target? + end + + def test_reflect_the_most_recent_change + author1, author2 = Author.limit(2) + post = Post.new(:title => "foo", :body=> "bar") + + post.author = author1 + post.author_id = author2.id + + assert post.save + assert_equal post.author_id, author2.id + end end diff --git a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb index ff376a68d8..01f7f18397 100644 --- a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb +++ b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb @@ -16,7 +16,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase :categorizations, :people, :categories, :edges, :vertices def test_eager_association_loading_with_cascaded_two_levels - authors = Author.find(:all, :include=>{:posts=>:comments}, :order=>"authors.id") + authors = Author.scoped(:includes=>{:posts=>:comments}, :order=>"authors.id").all assert_equal 3, authors.size assert_equal 5, authors[0].posts.size assert_equal 3, authors[1].posts.size @@ -24,7 +24,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase end def test_eager_association_loading_with_cascaded_two_levels_and_one_level - authors = Author.find(:all, :include=>[{:posts=>:comments}, :categorizations], :order=>"authors.id") + authors = Author.scoped(:includes=>[{:posts=>:comments}, :categorizations], :order=>"authors.id").all assert_equal 3, authors.size assert_equal 5, authors[0].posts.size assert_equal 3, authors[1].posts.size @@ -61,7 +61,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase end def test_cascaded_eager_association_loading_with_duplicated_includes - categories = Category.includes(:categorizations).includes(:categorizations => :author).where("categorizations.id is not null") + categories = Category.includes(:categorizations).includes(:categorizations => :author).where("categorizations.id is not null").references(:categorizations) assert_nothing_raised do assert_equal 3, categories.count assert_equal 3, categories.all.size @@ -69,7 +69,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase end def test_cascaded_eager_association_loading_with_twice_includes_edge_cases - categories = Category.includes(:categorizations => :author).includes(:categorizations => :post).where("posts.id is not null") + categories = Category.includes(:categorizations => :author).includes(:categorizations => :post).where("posts.id is not null").references(:posts) assert_nothing_raised do assert_equal 3, categories.count assert_equal 3, categories.all.size @@ -84,7 +84,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase end def test_eager_association_loading_with_cascaded_two_levels_with_two_has_many_associations - authors = Author.find(:all, :include=>{:posts=>[:comments, :categorizations]}, :order=>"authors.id") + authors = Author.scoped(:includes=>{:posts=>[:comments, :categorizations]}, :order=>"authors.id").all assert_equal 3, authors.size assert_equal 5, authors[0].posts.size assert_equal 3, authors[1].posts.size @@ -92,7 +92,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase end def test_eager_association_loading_with_cascaded_two_levels_and_self_table_reference - authors = Author.find(:all, :include=>{:posts=>[:comments, :author]}, :order=>"authors.id") + authors = Author.scoped(:includes=>{:posts=>[:comments, :author]}, :order=>"authors.id").all assert_equal 3, authors.size assert_equal 5, authors[0].posts.size assert_equal authors(:david).name, authors[0].name @@ -100,13 +100,13 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase end def test_eager_association_loading_with_cascaded_two_levels_with_condition - authors = Author.find(:all, :include=>{:posts=>:comments}, :conditions=>"authors.id=1", :order=>"authors.id") + authors = Author.scoped(:includes=>{:posts=>:comments}, :where=>"authors.id=1", :order=>"authors.id").all assert_equal 1, authors.size assert_equal 5, authors[0].posts.size end def test_eager_association_loading_with_cascaded_three_levels_by_ping_pong - firms = Firm.find(:all, :include=>{:account=>{:firm=>:account}}, :order=>"companies.id") + firms = Firm.scoped(:includes=>{:account=>{:firm=>:account}}, :order=>"companies.id").all assert_equal 2, firms.size assert_equal firms.first.account, firms.first.account.firm.account assert_equal companies(:first_firm).account, assert_no_queries { firms.first.account.firm.account } @@ -114,7 +114,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase end def test_eager_association_loading_with_has_many_sti - topics = Topic.find(:all, :include => :replies, :order => 'topics.id') + topics = Topic.scoped(:includes => :replies, :order => 'topics.id').all first, second, = topics(:first).replies.size, topics(:second).replies.size assert_no_queries do assert_equal first, topics[0].replies.size @@ -127,7 +127,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase silly.parent_id = 1 assert silly.save - topics = Topic.find(:all, :include => :replies, :order => 'topics.id, replies_topics.id') + topics = Topic.scoped(:includes => :replies, :order => ['topics.id', 'replies_topics.id']).all assert_no_queries do assert_equal 2, topics[0].replies.size assert_equal 0, topics[1].replies.size @@ -135,14 +135,14 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase end def test_eager_association_loading_with_belongs_to_sti - replies = Reply.find(:all, :include => :topic, :order => 'topics.id') + replies = Reply.scoped(:includes => :topic, :order => 'topics.id').all assert replies.include?(topics(:second)) assert !replies.include?(topics(:first)) assert_equal topics(:first), assert_no_queries { replies.first.topic } end def test_eager_association_loading_with_multiple_stis_and_order - author = Author.find(:first, :include => { :posts => [ :special_comments , :very_special_comment ] }, :order => ['authors.name', 'comments.body', 'very_special_comments_posts.body'], :conditions => 'posts.id = 4') + author = Author.scoped(:includes => { :posts => [ :special_comments , :very_special_comment ] }, :order => ['authors.name', 'comments.body', 'very_special_comments_posts.body'], :where => 'posts.id = 4').first assert_equal authors(:david), author assert_no_queries do author.posts.first.special_comments @@ -151,7 +151,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase end def test_eager_association_loading_of_stis_with_multiple_references - authors = Author.find(:all, :include => { :posts => { :special_comments => { :post => [ :special_comments, :very_special_comment ] } } }, :order => 'comments.body, very_special_comments_posts.body', :conditions => 'posts.id = 4') + authors = Author.scoped(:includes => { :posts => { :special_comments => { :post => [ :special_comments, :very_special_comment ] } } }, :order => 'comments.body, very_special_comments_posts.body', :where => 'posts.id = 4').all assert_equal [authors(:david)], authors assert_no_queries do authors.first.posts.first.special_comments.first.post.special_comments @@ -160,7 +160,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase end def test_eager_association_loading_where_first_level_returns_nil - authors = Author.find(:all, :include => {:post_about_thinking => :comments}, :order => 'authors.id DESC') + authors = Author.scoped(:includes => {:post_about_thinking => :comments}, :order => 'authors.id DESC').all assert_equal [authors(:bob), authors(:mary), authors(:david)], authors assert_no_queries do authors[2].post_about_thinking.comments.first @@ -168,12 +168,12 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase end def test_eager_association_loading_with_recursive_cascading_four_levels_has_many_through - source = Vertex.find(:first, :include=>{:sinks=>{:sinks=>{:sinks=>:sinks}}}, :order => 'vertices.id') + source = Vertex.scoped(:includes=>{:sinks=>{:sinks=>{:sinks=>:sinks}}}, :order => 'vertices.id').first assert_equal vertices(:vertex_4), assert_no_queries { source.sinks.first.sinks.first.sinks.first } end def test_eager_association_loading_with_recursive_cascading_four_levels_has_and_belongs_to_many - sink = Vertex.find(:first, :include=>{:sources=>{:sources=>{:sources=>:sources}}}, :order => 'vertices.id DESC') + sink = Vertex.scoped(: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 end diff --git a/activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb b/activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb index d75791cab9..75a6295350 100644 --- a/activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb +++ b/activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb @@ -4,7 +4,7 @@ require 'models/tagging' module Namespaced class Post < ActiveRecord::Base - set_table_name 'posts' + self.table_name = 'posts' has_one :tagging, :as => :taggable, :class_name => 'Tagging' end end @@ -24,12 +24,11 @@ class EagerLoadIncludeFullStiClassNamesTest < ActiveRecord::TestCase old = ActiveRecord::Base.store_full_sti_class ActiveRecord::Base.store_full_sti_class = false - post = Namespaced::Post.find_by_title( 'Great stuff', :include => :tagging ) + post = Namespaced::Post.includes(:tagging).find_by_title('Great stuff') assert_nil post.tagging - ActiveRecord::IdentityMap.clear ActiveRecord::Base.store_full_sti_class = true - post = Namespaced::Post.find_by_title( 'Great stuff', :include => :tagging ) + post = Namespaced::Post.includes(:tagging).find_by_title('Great stuff') assert_instance_of Tagging, post.tagging ensure ActiveRecord::Base.store_full_sti_class = old diff --git a/activerecord/test/cases/associations/eager_load_nested_include_test.rb b/activerecord/test/cases/associations/eager_load_nested_include_test.rb index 2cf9f89c3c..bb0d6bc70b 100644 --- a/activerecord/test/cases/associations/eager_load_nested_include_test.rb +++ b/activerecord/test/cases/associations/eager_load_nested_include_test.rb @@ -6,7 +6,6 @@ require 'models/comment' require 'models/category' require 'models/categorization' require 'models/tagging' -require 'active_support/core_ext/array/random_access' module Remembered extend ActiveSupport::Concern @@ -93,8 +92,7 @@ class EagerLoadPolyAssocsTest < ActiveRecord::TestCase end def test_include_query - res = 0 - res = ShapeExpression.find :all, :include => [ :shape, { :paint => :non_poly } ] + res = ShapeExpression.scoped(:includes => [ :shape, { :paint => :non_poly } ]).all assert_equal NUM_SHAPE_EXPRESSIONS, res.size assert_queries(0) do res.each do |se| @@ -124,7 +122,7 @@ class EagerLoadNestedIncludeWithMissingDataTest < ActiveRecord::TestCase assert_nothing_raised do # @davey_mcdave doesn't have any author_favorites includes = {:posts => :comments, :categorizations => :category, :author_favorites => :favorite_author } - Author.all :include => includes, :conditions => {:authors => {:name => @davey_mcdave.name}}, :order => 'categories.name' + Author.scoped(:includes => includes, :where => {:authors => {:name => @davey_mcdave.name}}, :order => 'categories.name').to_a end end end diff --git a/activerecord/test/cases/associations/eager_singularization_test.rb b/activerecord/test/cases/associations/eager_singularization_test.rb index 07d0b24613..5805e71249 100644 --- a/activerecord/test/cases/associations/eager_singularization_test.rb +++ b/activerecord/test/cases/associations/eager_singularization_test.rb @@ -103,43 +103,43 @@ class EagerSingularizationTest < ActiveRecord::TestCase def test_eager_no_extra_singularization_belongs_to return unless @have_tables assert_nothing_raised do - Virus.find(:all, :include => :octopus) + Virus.scoped(:includes => :octopus).all end end def test_eager_no_extra_singularization_has_one return unless @have_tables assert_nothing_raised do - Octopus.find(:all, :include => :virus) + Octopus.scoped(:includes => :virus).all end end def test_eager_no_extra_singularization_has_many return unless @have_tables assert_nothing_raised do - Bus.find(:all, :include => :passes) + Bus.scoped(:includes => :passes).all end end def test_eager_no_extra_singularization_has_and_belongs_to_many return unless @have_tables assert_nothing_raised do - Crisis.find(:all, :include => :messes) - Mess.find(:all, :include => :crises) + Crisis.scoped(:includes => :messes).all + Mess.scoped(:includes => :crises).all end end def test_eager_no_extra_singularization_has_many_through_belongs_to return unless @have_tables assert_nothing_raised do - Crisis.find(:all, :include => :successes) + Crisis.scoped(:includes => :successes).all end end def test_eager_no_extra_singularization_has_many_through_has_many return unless @have_tables assert_nothing_raised do - Crisis.find(:all, :include => :compresses) + Crisis.scoped(:includes => :compresses).all end end end diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb index c6e451fc57..2e44005847 100644 --- a/activerecord/test/cases/associations/eager_test.rb +++ b/activerecord/test/cases/associations/eager_test.rb @@ -35,39 +35,42 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_eager_with_has_one_through_join_model_with_conditions_on_the_through - member = Member.find(members(:some_other_guy).id, :include => :favourite_club) + member = Member.scoped(:includes => :favourite_club).find(members(:some_other_guy).id) assert_nil member.favourite_club end def test_loading_with_one_association - posts = Post.find(:all, :include => :comments) + posts = Post.scoped(:includes => :comments).all post = posts.find { |p| p.id == 1 } assert_equal 2, post.comments.size assert post.comments.include?(comments(:greetings)) - post = Post.find(:first, :include => :comments, :conditions => "posts.title = 'Welcome to the weblog'") + post = Post.scoped(:includes => :comments, :where => "posts.title = 'Welcome to the weblog'").first assert_equal 2, post.comments.size assert post.comments.include?(comments(:greetings)) - posts = Post.find(:all, :include => :last_comment) + posts = Post.scoped(:includes => :last_comment).all post = posts.find { |p| p.id == 1 } assert_equal Post.find(1).last_comment, post.last_comment end def test_loading_with_one_association_with_non_preload - posts = Post.find(:all, :include => :last_comment, :order => 'comments.id DESC') + posts = Post.scoped(:includes => :last_comment, :order => 'comments.id DESC').all post = posts.find { |p| p.id == 1 } assert_equal Post.find(1).last_comment, post.last_comment end def test_loading_conditions_with_or - posts = authors(:david).posts.find(:all, :include => :comments, :conditions => "comments.body like 'Normal%' OR comments.#{QUOTED_TYPE} = 'SpecialComment'") + posts = authors(:david).posts.references(:comments).scoped( + :includes => :comments, + :where => "comments.body like 'Normal%' OR comments.#{QUOTED_TYPE} = 'SpecialComment'" + ).all assert_nil posts.detect { |p| p.author_id != authors(:david).id }, "expected to find only david's posts" end def test_with_ordering - list = Post.find(:all, :include => :comments, :order => "posts.id DESC") + list = Post.scoped(:includes => :comments, :order => "posts.id DESC").all [:other_by_mary, :other_by_bob, :misc_by_mary, :misc_by_bob, :eager_other, :sti_habtm, :sti_post_and_comments, :sti_comments, :authorless, :thinking, :welcome ].each_with_index do |post, index| @@ -81,14 +84,14 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_loading_with_multiple_associations - posts = Post.find(:all, :include => [ :comments, :author, :categories ], :order => "posts.id") + posts = Post.scoped(:includes => [ :comments, :author, :categories ], :order => "posts.id").all assert_equal 2, posts.first.comments.size assert_equal 2, posts.first.categories.size assert posts.first.comments.include?(comments(:greetings)) end def test_duplicate_middle_objects - comments = Comment.find :all, :conditions => 'post_id = 1', :include => [:post => :author] + comments = Comment.scoped(:where => 'post_id = 1', :includes => [:post => :author]).all assert_no_queries do comments.each {|comment| comment.post.author.name} end @@ -96,25 +99,25 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_preloading_has_many_in_multiple_queries_with_more_ids_than_database_can_handle Post.connection.expects(:in_clause_length).at_least_once.returns(5) - posts = Post.find(:all, :include=>:comments) + posts = Post.scoped(:includes=>:comments).all assert_equal 11, posts.size end def test_preloading_has_many_in_one_queries_when_database_has_no_limit_on_ids_it_can_handle Post.connection.expects(:in_clause_length).at_least_once.returns(nil) - posts = Post.find(:all, :include=>:comments) + posts = Post.scoped(:includes=>:comments).all assert_equal 11, posts.size end def test_preloading_habtm_in_multiple_queries_with_more_ids_than_database_can_handle Post.connection.expects(:in_clause_length).at_least_once.returns(5) - posts = Post.find(:all, :include=>:categories) + posts = Post.scoped(:includes=>:categories).all assert_equal 11, posts.size end def test_preloading_habtm_in_one_queries_when_database_has_no_limit_on_ids_it_can_handle Post.connection.expects(:in_clause_length).at_least_once.returns(nil) - posts = Post.find(:all, :include=>:categories) + posts = Post.scoped(:includes=>:categories).all assert_equal 11, posts.size end @@ -151,8 +154,8 @@ class EagerAssociationTest < ActiveRecord::TestCase popular_post.readers.create!(:person => people(:michael)) popular_post.readers.create!(:person => people(:david)) - readers = Reader.find(:all, :conditions => ["post_id = ?", popular_post.id], - :include => {:post => :comments}) + readers = Reader.scoped(:where => ["post_id = ?", popular_post.id], + :includes => {:post => :comments}).all readers.each do |reader| assert_equal [comment], reader.post.comments end @@ -164,8 +167,8 @@ class EagerAssociationTest < ActiveRecord::TestCase car_post.categories << categories(:technology) comment = car_post.comments.create!(:body => "hmm") - categories = Category.find(:all, :conditions => ["posts.id=?", car_post.id], - :include => {:posts => :comments}) + categories = Category.scoped(:where => { 'posts.id' => car_post.id }, + :includes => {:posts => :comments}).all categories.each do |category| assert_equal [comment], category.posts[0].comments end @@ -183,7 +186,7 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_finding_with_includes_on_has_many_association_with_same_include_includes_only_once author_id = authors(:david).id - author = assert_queries(3) { Author.find(author_id, :include => {:posts_with_comments => :comments}) } # find the author, then find the posts, then find the comments + author = assert_queries(3) { Author.scoped(:includes => {:posts_with_comments => :comments}).find(author_id) } # find the author, then find the posts, then find the comments author.posts_with_comments.each do |post_with_comments| assert_equal post_with_comments.comments.length, post_with_comments.comments.count assert_nil post_with_comments.comments.uniq! @@ -194,7 +197,7 @@ class EagerAssociationTest < ActiveRecord::TestCase author = authors(:david) post = author.post_about_thinking_with_last_comment last_comment = post.last_comment - author = assert_queries(ActiveRecord::IdentityMap.enabled? ? 2 : 3) { Author.find(author.id, :include => {:post_about_thinking_with_last_comment => :last_comment})} # find the author, then find the posts, then find the comments + author = assert_queries(3) { Author.scoped(:includes => {:post_about_thinking_with_last_comment => :last_comment}).find(author.id)} # find the author, then find the posts, then find the comments assert_no_queries do assert_equal post, author.post_about_thinking_with_last_comment assert_equal last_comment, author.post_about_thinking_with_last_comment.last_comment @@ -205,7 +208,7 @@ class EagerAssociationTest < ActiveRecord::TestCase post = posts(:welcome) author = post.author author_address = author.author_address - post = assert_queries(ActiveRecord::IdentityMap.enabled? ? 2 : 3) { Post.find(post.id, :include => {:author_with_address => :author_address}) } # find the post, then find the author, then find the address + post = assert_queries(3) { Post.scoped(:includes => {:author_with_address => :author_address}).find(post.id) } # find the post, then find the author, then find the address assert_no_queries do assert_equal author, post.author_with_address assert_equal author_address, post.author_with_address.author_address @@ -215,7 +218,7 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_finding_with_includes_on_null_belongs_to_association_with_same_include_includes_only_once post = posts(:welcome) post.update_attributes!(:author => nil) - post = assert_queries(1) { Post.find(post.id, :include => {:author_with_address => :author_address}) } # find the post, then find the author which is null so no query for the author or address + post = assert_queries(1) { Post.scoped(:includes => {:author_with_address => :author_address}).find(post.id) } # find the post, then find the author which is null so no query for the author or address assert_no_queries do assert_equal nil, post.author_with_address end @@ -224,41 +227,85 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_finding_with_includes_on_null_belongs_to_polymorphic_association sponsor = sponsors(:moustache_club_sponsor_for_groucho) sponsor.update_attributes!(:sponsorable => nil) - sponsor = assert_queries(1) { Sponsor.find(sponsor.id, :include => :sponsorable) } + sponsor = assert_queries(1) { Sponsor.scoped(:includes => :sponsorable).find(sponsor.id) } assert_no_queries do assert_equal nil, sponsor.sponsorable end end def test_loading_from_an_association - posts = authors(:david).posts.find(:all, :include => :comments, :order => "posts.id") + posts = authors(:david).posts.scoped(:includes => :comments, :order => "posts.id").all assert_equal 2, posts.first.comments.size end def test_loading_from_an_association_that_has_a_hash_of_conditions assert_nothing_raised do - Author.find(:all, :include => :hello_posts_with_hash_conditions) + Author.scoped(:includes => :hello_posts_with_hash_conditions).all end - assert !Author.find(authors(:david).id, :include => :hello_posts_with_hash_conditions).hello_posts.empty? + assert !Author.scoped(:includes => :hello_posts_with_hash_conditions).find(authors(:david).id).hello_posts.empty? end def test_loading_with_no_associations - assert_nil Post.find(posts(:authorless).id, :include => :author).author + assert_nil Post.scoped(:includes => :author).find(posts(:authorless).id).author end def test_nested_loading_with_no_associations assert_nothing_raised do - Post.find(posts(:authorless).id, :include => {:author => :author_addresss}) + Post.scoped(:includes => {:author => :author_addresss}).find(posts(:authorless).id) end end + def test_nested_loading_through_has_one_association + aa = AuthorAddress.scoped(:includes => {:author => :posts}).find(author_addresses(:david_address).id) + assert_equal aa.author.posts.count, aa.author.posts.length + end + + def test_nested_loading_through_has_one_association_with_order + aa = AuthorAddress.scoped(:includes => {:author => :posts}, :order => 'author_addresses.id').find(author_addresses(:david_address).id) + assert_equal aa.author.posts.count, aa.author.posts.length + end + + def test_nested_loading_through_has_one_association_with_order_on_association + aa = AuthorAddress.scoped(:includes => {:author => :posts}, :order => 'authors.id').find(author_addresses(:david_address).id) + assert_equal aa.author.posts.count, aa.author.posts.length + end + + def test_nested_loading_through_has_one_association_with_order_on_nested_association + aa = AuthorAddress.scoped(:includes => {:author => :posts}, :order => 'posts.id').find(author_addresses(:david_address).id) + assert_equal aa.author.posts.count, aa.author.posts.length + end + + def test_nested_loading_through_has_one_association_with_conditions + aa = AuthorAddress.references(:author_addresses).scoped( + :includes => {:author => :posts}, + :where => "author_addresses.id > 0" + ).find author_addresses(:david_address).id + assert_equal aa.author.posts.count, aa.author.posts.length + end + + def test_nested_loading_through_has_one_association_with_conditions_on_association + aa = AuthorAddress.references(:authors).scoped( + :includes => {:author => :posts}, + :where => "authors.id > 0" + ).find author_addresses(:david_address).id + assert_equal aa.author.posts.count, aa.author.posts.length + end + + def test_nested_loading_through_has_one_association_with_conditions_on_nested_association + aa = AuthorAddress.references(:posts).scoped( + :includes => {:author => :posts}, + :where => "posts.id > 0" + ).find author_addresses(:david_address).id + assert_equal aa.author.posts.count, aa.author.posts.length + end + def test_eager_association_loading_with_belongs_to_and_foreign_keys - pets = Pet.find(:all, :include => :owner) + pets = Pet.scoped(:includes => :owner).all assert_equal 3, pets.length end def test_eager_association_loading_with_belongs_to - comments = Comment.find(:all, :include => :post) + comments = Comment.scoped(:includes => :post).all assert_equal 11, comments.length titles = comments.map { |c| c.post.title } assert titles.include?(posts(:welcome).title) @@ -266,45 +313,47 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_eager_association_loading_with_belongs_to_and_limit - comments = Comment.find(:all, :include => :post, :limit => 5, :order => 'comments.id') + comments = Comment.scoped(:includes => :post, :limit => 5, :order => 'comments.id').all assert_equal 5, comments.length assert_equal [1,2,3,5,6], comments.collect { |c| c.id } end def test_eager_association_loading_with_belongs_to_and_limit_and_conditions - comments = Comment.find(:all, :include => :post, :conditions => 'post_id = 4', :limit => 3, :order => 'comments.id') + comments = Comment.scoped(:includes => :post, :where => 'post_id = 4', :limit => 3, :order => 'comments.id').all assert_equal 3, comments.length assert_equal [5,6,7], comments.collect { |c| c.id } end def test_eager_association_loading_with_belongs_to_and_limit_and_offset - comments = Comment.find(:all, :include => :post, :limit => 3, :offset => 2, :order => 'comments.id') + comments = Comment.scoped(:includes => :post, :limit => 3, :offset => 2, :order => 'comments.id').all assert_equal 3, comments.length assert_equal [3,5,6], comments.collect { |c| c.id } end def test_eager_association_loading_with_belongs_to_and_limit_and_offset_and_conditions - comments = Comment.find(:all, :include => :post, :conditions => 'post_id = 4', :limit => 3, :offset => 1, :order => 'comments.id') + comments = Comment.scoped(:includes => :post, :where => 'post_id = 4', :limit => 3, :offset => 1, :order => 'comments.id').all assert_equal 3, comments.length assert_equal [6,7,8], comments.collect { |c| c.id } end def test_eager_association_loading_with_belongs_to_and_limit_and_offset_and_conditions_array - comments = Comment.find(:all, :include => :post, :conditions => ['post_id = ?',4], :limit => 3, :offset => 1, :order => 'comments.id') + comments = Comment.scoped(:includes => :post, :where => ['post_id = ?',4], :limit => 3, :offset => 1, :order => 'comments.id').all assert_equal 3, comments.length assert_equal [6,7,8], comments.collect { |c| c.id } end def test_eager_association_loading_with_belongs_to_and_conditions_string_with_unquoted_table_name assert_nothing_raised do - Comment.find(:all, :include => :post, :conditions => ['posts.id = ?',4]) + ActiveSupport::Deprecation.silence do + Comment.scoped(:includes => :post, :where => ['posts.id = ?',4]).all + end end end def test_eager_association_loading_with_belongs_to_and_conditions_hash comments = [] assert_nothing_raised do - comments = Comment.find(:all, :include => :post, :conditions => {:posts => {:id => 4}}, :limit => 3, :order => 'comments.id') + comments = Comment.scoped(:includes => :post, :where => {:posts => {:id => 4}}, :limit => 3, :order => 'comments.id').all end assert_equal 3, comments.length assert_equal [5,6,7], comments.collect { |c| c.id } @@ -316,67 +365,71 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_eager_association_loading_with_belongs_to_and_conditions_string_with_quoted_table_name quoted_posts_id= Comment.connection.quote_table_name('posts') + '.' + Comment.connection.quote_column_name('id') assert_nothing_raised do - Comment.find(:all, :include => :post, :conditions => ["#{quoted_posts_id} = ?",4]) + ActiveSupport::Deprecation.silence do + Comment.scoped(:includes => :post, :where => ["#{quoted_posts_id} = ?",4]).all + end end end def test_eager_association_loading_with_belongs_to_and_order_string_with_unquoted_table_name assert_nothing_raised do - Comment.find(:all, :include => :post, :order => 'posts.id') + Comment.scoped(:includes => :post, :order => 'posts.id').all end end def test_eager_association_loading_with_belongs_to_and_order_string_with_quoted_table_name quoted_posts_id= Comment.connection.quote_table_name('posts') + '.' + Comment.connection.quote_column_name('id') assert_nothing_raised do - Comment.find(:all, :include => :post, :order => quoted_posts_id) + ActiveSupport::Deprecation.silence do + Comment.scoped(:includes => :post, :order => quoted_posts_id).all + end end end def test_eager_association_loading_with_belongs_to_and_limit_and_multiple_associations - posts = Post.find(:all, :include => [:author, :very_special_comment], :limit => 1, :order => 'posts.id') + posts = Post.scoped(:includes => [:author, :very_special_comment], :limit => 1, :order => 'posts.id').all assert_equal 1, posts.length assert_equal [1], posts.collect { |p| p.id } end def test_eager_association_loading_with_belongs_to_and_limit_and_offset_and_multiple_associations - posts = Post.find(:all, :include => [:author, :very_special_comment], :limit => 1, :offset => 1, :order => 'posts.id') + posts = Post.scoped(:includes => [:author, :very_special_comment], :limit => 1, :offset => 1, :order => 'posts.id').all assert_equal 1, posts.length assert_equal [2], posts.collect { |p| p.id } end def test_eager_association_loading_with_belongs_to_inferred_foreign_key_from_association_name - author_favorite = AuthorFavorite.find(:first, :include => :favorite_author) + author_favorite = AuthorFavorite.scoped(:includes => :favorite_author).first assert_equal authors(:mary), assert_no_queries { author_favorite.favorite_author } end def test_eager_load_belongs_to_quotes_table_and_column_names - job = Job.find jobs(:unicyclist).id, :include => :ideal_reference + job = Job.includes(:ideal_reference).find jobs(:unicyclist).id references(:michael_unicyclist) assert_no_queries{ assert_equal references(:michael_unicyclist), job.ideal_reference} end def test_eager_load_has_one_quotes_table_and_column_names - michael = Person.find(people(:michael), :include => :favourite_reference) + michael = Person.scoped(:includes => :favourite_reference).find(people(:michael)) references(:michael_unicyclist) assert_no_queries{ assert_equal references(:michael_unicyclist), michael.favourite_reference} end def test_eager_load_has_many_quotes_table_and_column_names - michael = Person.find(people(:michael), :include => :references) + michael = Person.scoped(:includes => :references).find(people(:michael)) references(:michael_magician,:michael_unicyclist) assert_no_queries{ assert_equal references(:michael_magician,:michael_unicyclist), michael.references.sort_by(&:id) } end def test_eager_load_has_many_through_quotes_table_and_column_names - michael = Person.find(people(:michael), :include => :jobs) + michael = Person.scoped(:includes => :jobs).find(people(:michael)) jobs(:magician, :unicyclist) assert_no_queries{ assert_equal jobs(:unicyclist, :magician), michael.jobs.sort_by(&:id) } end def test_eager_load_has_many_with_string_keys subscriptions = subscriptions(:webster_awdr, :webster_rfr) - subscriber =Subscriber.find(subscribers(:second).id, :include => :subscriptions) + subscriber =Subscriber.scoped(:includes => :subscriptions).find(subscribers(:second).id) assert_equal subscriptions, subscriber.subscriptions.sort_by(&:id) end @@ -394,25 +447,25 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_eager_load_has_many_through_with_string_keys books = books(:awdr, :rfr) - subscriber = Subscriber.find(subscribers(:second).id, :include => :books) + subscriber = Subscriber.scoped(:includes => :books).find(subscribers(:second).id) assert_equal books, subscriber.books.sort_by(&:id) end def test_eager_load_belongs_to_with_string_keys subscriber = subscribers(:second) - subscription = Subscription.find(subscriptions(:webster_awdr).id, :include => :subscriber) + subscription = Subscription.scoped(:includes => :subscriber).find(subscriptions(:webster_awdr).id) assert_equal subscriber, subscription.subscriber end def test_eager_association_loading_with_explicit_join - posts = Post.find(:all, :include => :comments, :joins => "INNER JOIN authors ON posts.author_id = authors.id AND authors.name = 'Mary'", :limit => 1, :order => 'author_id') + posts = Post.scoped(:includes => :comments, :joins => "INNER JOIN authors ON posts.author_id = authors.id AND authors.name = 'Mary'", :limit => 1, :order => 'author_id').all assert_equal 1, posts.length end def test_eager_with_has_many_through - posts_with_comments = people(:michael).posts.find(:all, :include => :comments, :order => 'posts.id') - posts_with_author = people(:michael).posts.find(:all, :include => :author, :order => 'posts.id') - posts_with_comments_and_author = people(:michael).posts.find(:all, :include => [ :comments, :author ], :order => 'posts.id') + posts_with_comments = people(:michael).posts.scoped(:includes => :comments, :order => 'posts.id').all + posts_with_author = people(:michael).posts.scoped(:includes => :author, :order => 'posts.id').all + posts_with_comments_and_author = people(:michael).posts.scoped(:includes => [ :comments, :author ], :order => 'posts.id').all assert_equal 2, posts_with_comments.inject(0) { |sum, post| sum += post.comments.size } assert_equal authors(:david), assert_no_queries { posts_with_author.first.author } assert_equal authors(:david), assert_no_queries { posts_with_comments_and_author.first.author } @@ -423,32 +476,32 @@ class EagerAssociationTest < ActiveRecord::TestCase Post.create!(:author => author, :title => "TITLE", :body => "BODY") author.author_favorites.create(:favorite_author_id => 1) author.author_favorites.create(:favorite_author_id => 2) - posts_with_author_favorites = author.posts.find(:all, :include => :author_favorites) + posts_with_author_favorites = author.posts.scoped(:includes => :author_favorites).all assert_no_queries { posts_with_author_favorites.first.author_favorites.first.author_id } end def test_eager_with_has_many_through_an_sti_join_model - author = Author.find(:first, :include => :special_post_comments, :order => 'authors.id') + author = Author.scoped(:includes => :special_post_comments, :order => 'authors.id').first assert_equal [comments(:does_it_hurt)], assert_no_queries { author.special_post_comments } end def test_eager_with_has_many_through_an_sti_join_model_with_conditions_on_both - author = Author.find(:first, :include => :special_nonexistant_post_comments, :order => 'authors.id') + author = Author.scoped(:includes => :special_nonexistant_post_comments, :order => 'authors.id').first assert_equal [], author.special_nonexistant_post_comments end def test_eager_with_has_many_through_join_model_with_conditions - assert_equal Author.find(:first, :include => :hello_post_comments, - :order => 'authors.id').hello_post_comments.sort_by(&:id), - Author.find(:first, :order => 'authors.id').hello_post_comments.sort_by(&:id) + assert_equal Author.scoped(:includes => :hello_post_comments, + :order => 'authors.id').first.hello_post_comments.sort_by(&:id), + Author.scoped(:order => 'authors.id').first.hello_post_comments.sort_by(&:id) end def test_eager_with_has_many_through_join_model_with_conditions_on_top_level - assert_equal comments(:more_greetings), Author.find(authors(:david).id, :include => :comments_with_order_and_conditions).comments_with_order_and_conditions.first + assert_equal comments(:more_greetings), Author.scoped(:includes => :comments_with_order_and_conditions).find(authors(:david).id).comments_with_order_and_conditions.first end def test_eager_with_has_many_through_join_model_with_include - author_comments = Author.find(authors(:david).id, :include => :comments_with_include).comments_with_include.to_a + author_comments = Author.scoped(:includes => :comments_with_include).find(authors(:david).id).comments_with_include.to_a assert_no_queries do author_comments.first.post.title end @@ -456,7 +509,7 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_eager_with_has_many_through_with_conditions_join_model_with_include post_tags = Post.find(posts(:welcome).id).misc_tags - eager_post_tags = Post.find(1, :include => :misc_tags).misc_tags + eager_post_tags = Post.scoped(:includes => :misc_tags).find(1).misc_tags assert_equal post_tags, eager_post_tags end @@ -467,16 +520,16 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_eager_with_has_many_and_limit - posts = Post.find(:all, :order => 'posts.id asc', :include => [ :author, :comments ], :limit => 2) + posts = Post.scoped(:order => 'posts.id asc', :includes => [ :author, :comments ], :limit => 2).all assert_equal 2, posts.size assert_equal 3, posts.inject(0) { |sum, post| sum += post.comments.size } end def test_eager_with_has_many_and_limit_and_conditions if current_adapter?(:OpenBaseAdapter) - posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :conditions => "FETCHBLOB(posts.body) = 'hello'", :order => "posts.id") + posts = Post.scoped(:includes => [ :author, :comments ], :limit => 2, :where => "FETCHBLOB(posts.body) = 'hello'", :order => "posts.id").all else - posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :conditions => "posts.body = 'hello'", :order => "posts.id") + posts = Post.scoped(:includes => [ :author, :comments ], :limit => 2, :where => "posts.body = 'hello'", :order => "posts.id").all end assert_equal 2, posts.size assert_equal [4,5], posts.collect { |p| p.id } @@ -484,57 +537,62 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_eager_with_has_many_and_limit_and_conditions_array if current_adapter?(:OpenBaseAdapter) - posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :conditions => [ "FETCHBLOB(posts.body) = ?", 'hello' ], :order => "posts.id") + posts = Post.scoped(:includes => [ :author, :comments ], :limit => 2, :where => [ "FETCHBLOB(posts.body) = ?", 'hello' ], :order => "posts.id").all else - posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :conditions => [ "posts.body = ?", 'hello' ], :order => "posts.id") + posts = Post.scoped(:includes => [ :author, :comments ], :limit => 2, :where => [ "posts.body = ?", 'hello' ], :order => "posts.id").all end assert_equal 2, posts.size assert_equal [4,5], posts.collect { |p| p.id } end def test_eager_with_has_many_and_limit_and_conditions_array_on_the_eagers - posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :conditions => [ "authors.name = ?", 'David' ]) + posts = ActiveSupport::Deprecation.silence do + Post.scoped(:includes => [ :author, :comments ], :limit => 2, :where => [ "authors.name = ?", 'David' ]).all + end assert_equal 2, posts.size - count = Post.count(:include => [ :author, :comments ], :limit => 2, :conditions => [ "authors.name = ?", 'David' ]) + count = ActiveSupport::Deprecation.silence do + Post.count(:include => [ :author, :comments ], :limit => 2, :conditions => [ "authors.name = ?", 'David' ]) + end assert_equal count, posts.size end def test_eager_with_has_many_and_limit_and_high_offset - posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :offset => 10, :conditions => [ "authors.name = ?", 'David' ]) + posts = Post.scoped(:includes => [ :author, :comments ], :limit => 2, :offset => 10, :where => { 'authors.name' => 'David' }).all assert_equal 0, posts.size end def test_eager_with_has_many_and_limit_and_high_offset_and_multiple_array_conditions assert_queries(1) do - posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :offset => 10, - :conditions => [ "authors.name = ? and comments.body = ?", 'David', 'go crazy' ]) + posts = Post.references(:authors, :comments). + scoped(:includes => [ :author, :comments ], :limit => 2, :offset => 10, + :where => [ "authors.name = ? and comments.body = ?", 'David', 'go crazy' ]).all assert_equal 0, posts.size end end def test_eager_with_has_many_and_limit_and_high_offset_and_multiple_hash_conditions assert_queries(1) do - posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :offset => 10, - :conditions => { 'authors.name' => 'David', 'comments.body' => 'go crazy' }) + posts = Post.scoped(:includes => [ :author, :comments ], :limit => 2, :offset => 10, + :where => { 'authors.name' => 'David', 'comments.body' => 'go crazy' }).all assert_equal 0, posts.size end end def test_count_eager_with_has_many_and_limit_and_high_offset - posts = Post.count(:all, :include => [ :author, :comments ], :limit => 2, :offset => 10, :conditions => [ "authors.name = ?", 'David' ]) + posts = Post.scoped(:includes => [ :author, :comments ], :limit => 2, :offset => 10, :where => { 'authors.name' => 'David' }).count(:all) assert_equal 0, posts end def test_eager_with_has_many_and_limit_with_no_results - posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :conditions => "posts.title = 'magic forest'") + posts = Post.scoped(:includes => [ :author, :comments ], :limit => 2, :where => "posts.title = 'magic forest'").all assert_equal 0, posts.size end def test_eager_count_performed_on_a_has_many_association_with_multi_table_conditional author = authors(:david) author_posts_without_comments = author.posts.select { |post| post.comments.blank? } - assert_equal author_posts_without_comments.size, author.posts.count(:all, :include => :comments, :conditions => 'comments.id is null') + assert_equal author_posts_without_comments.size, author.posts.includes(:comments).where('comments.id is null').references(:comments).count end def test_eager_count_performed_on_a_has_many_through_association_with_multi_table_conditional @@ -544,7 +602,7 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_eager_with_has_and_belongs_to_many_and_limit - posts = Post.find(:all, :include => :categories, :order => "posts.id", :limit => 3) + posts = Post.scoped(:includes => :categories, :order => "posts.id", :limit => 3).all assert_equal 3, posts.size assert_equal 2, posts[0].categories.size assert_equal 1, posts[1].categories.size @@ -553,9 +611,9 @@ class EagerAssociationTest < ActiveRecord::TestCase assert posts[1].categories.include?(categories(:general)) end - # This is only really relevant when the identity map is off. Since the preloader for habtm - # gets raw row hashes from the database and then instantiates them, this test ensures that - # it only instantiates one actual object per record from the database. + # Since the preloader for habtm gets raw row hashes from the database and then + # instantiates them, this test ensures that it only instantiates one actual + # object per record from the database. def test_has_and_belongs_to_many_should_not_instantiate_same_records_multiple_times welcome = posts(:welcome) categories = Category.includes(:posts) @@ -570,68 +628,47 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_eager_with_has_many_and_limit_and_conditions_on_the_eagers - posts = authors(:david).posts.find(:all, - :include => :comments, - :conditions => "comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment'", - :limit => 2 - ) + posts = + authors(:david).posts + .includes(:comments) + .where("comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment'") + .references(:comments) + .limit(2) + .to_a assert_equal 2, posts.size - count = Post.count( - :include => [ :comments, :author ], - :conditions => "authors.name = 'David' AND (comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment')", - :limit => 2 - ) + count = + Post.includes(:comments, :author) + .where("authors.name = 'David' AND (comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment')") + .references(:authors, :comments) + .limit(2) + .count assert_equal count, posts.size end def test_eager_with_has_many_and_limit_and_scoped_conditions_on_the_eagers posts = nil - Post.send(:with_scope, :find => { - :include => :comments, - :conditions => "comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment'" - }) do - posts = authors(:david).posts.find(:all, :limit => 2) - assert_equal 2, posts.size - end + Post.includes(:comments) + .where("comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment'") + .references(:comments) + .scoping do - Post.send(:with_scope, :find => { - :include => [ :comments, :author ], - :conditions => "authors.name = 'David' AND (comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment')" - }) do - count = Post.count(:limit => 2) - assert_equal count, posts.size + posts = authors(:david).posts.limit(2).to_a + assert_equal 2, posts.size end - end - def test_eager_with_has_many_and_limit_and_scoped_and_explicit_conditions_on_the_eagers - Post.send(:with_scope, :find => { :conditions => "1=1" }) do - posts = authors(:david).posts.find(:all, - :include => :comments, - :conditions => "comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment'", - :limit => 2 - ) - assert_equal 2, posts.size + Post.includes(:comments, :author) + .where("authors.name = 'David' AND (comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment')") + .references(:authors, :comments) + .scoping do - count = Post.count( - :include => [ :comments, :author ], - :conditions => "authors.name = 'David' AND (comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment')", - :limit => 2 - ) + count = Post.limit(2).count assert_equal count, posts.size end end - def test_eager_with_scoped_order_using_association_limiting_without_explicit_scope - posts_with_explicit_order = Post.find(:all, :conditions => 'comments.id is not null', :include => :comments, :order => 'posts.id DESC', :limit => 2) - posts_with_scoped_order = Post.send(:with_scope, :find => {:order => 'posts.id DESC'}) do - Post.find(:all, :conditions => 'comments.id is not null', :include => :comments, :limit => 2) - end - assert_equal posts_with_explicit_order, posts_with_scoped_order - end - def test_eager_association_loading_with_habtm - posts = Post.find(:all, :include => :categories, :order => "posts.id") + posts = Post.scoped(:includes => :categories, :order => "posts.id").all assert_equal 2, posts[0].categories.size assert_equal 1, posts[1].categories.size assert_equal 0, posts[2].categories.size @@ -640,23 +677,23 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_eager_with_inheritance - SpecialPost.find(:all, :include => [ :comments ]) + SpecialPost.scoped(:includes => [ :comments ]).all end def test_eager_has_one_with_association_inheritance - post = Post.find(4, :include => [ :very_special_comment ]) + post = Post.scoped(:includes => [ :very_special_comment ]).find(4) assert_equal "VerySpecialComment", post.very_special_comment.class.to_s end def test_eager_has_many_with_association_inheritance - post = Post.find(4, :include => [ :special_comments ]) + post = Post.scoped(:includes => [ :special_comments ]).find(4) post.special_comments.each do |special_comment| assert special_comment.is_a?(SpecialComment) end end def test_eager_habtm_with_association_inheritance - post = Post.find(6, :include => [ :special_categories ]) + post = Post.scoped(:includes => [ :special_categories ]).find(6) assert_equal 1, post.special_categories.size post.special_categories.each do |special_category| assert_equal "SpecialCategory", special_category.class.to_s @@ -665,8 +702,8 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_eager_with_has_one_dependent_does_not_destroy_dependent assert_not_nil companies(:first_firm).account - f = Firm.find(:first, :include => :account, - :conditions => ["companies.name = ?", "37signals"]) + f = Firm.scoped(:includes => :account, + :where => ["companies.name = ?", "37signals"]).first assert_not_nil f.account assert_equal companies(:first_firm, :reload).account, f.account end @@ -680,16 +717,16 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_eager_with_invalid_association_reference assert_raise(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") { - Post.find(6, :include=> :monkeys ) + Post.scoped(:includes=> :monkeys ).find(6) } assert_raise(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") { - Post.find(6, :include=>[ :monkeys ]) + Post.scoped(:includes=>[ :monkeys ]).find(6) } assert_raise(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") { - Post.find(6, :include=>[ 'monkeys' ]) + Post.scoped(:includes=>[ 'monkeys' ]).find(6) } assert_raise(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys, :elephants") { - Post.find(6, :include=>[ :monkeys, :elephants ]) + Post.scoped(:includes=>[ :monkeys, :elephants ]).find(6) } end @@ -734,21 +771,52 @@ class EagerAssociationTest < ActiveRecord::TestCase end def find_all_ordered(className, include=nil) - className.find(:all, :order=>"#{className.table_name}.#{className.primary_key}", :include=>include) + className.scoped(:order=>"#{className.table_name}.#{className.primary_key}", :includes=>include).all end def test_limited_eager_with_order - assert_equal posts(:thinking, :sti_comments), Post.find(:all, :include => [:author, :comments], :conditions => "authors.name = 'David'", :order => 'UPPER(posts.title)', :limit => 2, :offset => 1) - assert_equal posts(:sti_post_and_comments, :sti_comments), Post.find(:all, :include => [:author, :comments], :conditions => "authors.name = 'David'", :order => 'UPPER(posts.title) DESC', :limit => 2, :offset => 1) + assert_equal( + posts(:thinking, :sti_comments), + Post.scoped( + :includes => [:author, :comments], :where => { 'authors.name' => 'David' }, + :order => 'UPPER(posts.title)', :limit => 2, :offset => 1 + ).all + ) + assert_equal( + posts(:sti_post_and_comments, :sti_comments), + Post.scoped( + :includes => [:author, :comments], :where => { 'authors.name' => 'David' }, + :order => 'UPPER(posts.title) DESC', :limit => 2, :offset => 1 + ).all + ) end def test_limited_eager_with_multiple_order_columns - assert_equal posts(:thinking, :sti_comments), Post.find(:all, :include => [:author, :comments], :conditions => "authors.name = 'David'", :order => ['UPPER(posts.title)', 'posts.id'], :limit => 2, :offset => 1) - assert_equal posts(:sti_post_and_comments, :sti_comments), Post.find(:all, :include => [:author, :comments], :conditions => "authors.name = 'David'", :order => ['UPPER(posts.title) DESC', 'posts.id'], :limit => 2, :offset => 1) + assert_equal( + posts(:thinking, :sti_comments), + Post.scoped( + :includes => [:author, :comments], :where => { 'authors.name' => 'David' }, + :order => ['UPPER(posts.title)', 'posts.id'], :limit => 2, :offset => 1 + ).all + ) + assert_equal( + posts(:sti_post_and_comments, :sti_comments), + Post.scoped( + :includes => [:author, :comments], :where => { 'authors.name' => 'David' }, + :order => ['UPPER(posts.title) DESC', 'posts.id'], :limit => 2, :offset => 1 + ).all + ) end def test_limited_eager_with_numeric_in_association - assert_equal people(:david, :susan), Person.find(:all, :include => [:readers, :primary_contact, :number1_fan], :conditions => "number1_fans_people.first_name like 'M%'", :order => 'people.id', :limit => 2, :offset => 0) + assert_equal( + people(:david, :susan), + Person.references(:number1_fans_people).scoped( + :includes => [:readers, :primary_contact, :number1_fan], + :where => "number1_fans_people.first_name like 'M%'", + :order => 'people.id', :limit => 2, :offset => 0 + ).all + ) end def test_preload_with_interpolation @@ -760,9 +828,9 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_polymorphic_type_condition - post = Post.find(posts(:thinking).id, :include => :taggings) + post = Post.scoped(:includes => :taggings).find(posts(:thinking).id) assert post.taggings.include?(taggings(:thinking_general)) - post = SpecialPost.find(posts(:thinking).id, :include => :taggings) + post = SpecialPost.scoped(:includes => :taggings).find(posts(:thinking).id) assert post.taggings.include?(taggings(:thinking_general)) end @@ -813,13 +881,13 @@ class EagerAssociationTest < ActiveRecord::TestCase end end def test_eager_with_valid_association_as_string_not_symbol - assert_nothing_raised { Post.find(:all, :include => 'comments') } + assert_nothing_raised { Post.scoped(:includes => 'comments').all } end def test_eager_with_floating_point_numbers assert_queries(2) do # Before changes, the floating point numbers will be interpreted as table names and will cause this to run in one query - Comment.find :all, :conditions => "123.456 = 123.456", :include => :post + Comment.scoped(:where => "123.456 = 123.456", :includes => :post).all end end @@ -863,31 +931,31 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_count_with_include if current_adapter?(:SybaseAdapter) - assert_equal 3, authors(:david).posts_with_comments.count(:conditions => "len(comments.body) > 15") + assert_equal 3, authors(:david).posts_with_comments.where("len(comments.body) > 15").references(:comments).count elsif current_adapter?(:OpenBaseAdapter) - assert_equal 3, authors(:david).posts_with_comments.count(:conditions => "length(FETCHBLOB(comments.body)) > 15") + assert_equal 3, authors(:david).posts_with_comments.where("length(FETCHBLOB(comments.body)) > 15").references(:comments).count else - assert_equal 3, authors(:david).posts_with_comments.count(:conditions => "length(comments.body) > 15") + assert_equal 3, authors(:david).posts_with_comments.where("length(comments.body) > 15").references(:comments).count end end def test_load_with_sti_sharing_association assert_queries(2) do #should not do 1 query per subclass - Comment.find :all, :include => :post + Comment.includes(:post).all end end def test_conditions_on_join_table_with_include_and_limit - assert_equal 3, Developer.find(:all, :include => 'projects', :conditions => 'developers_projects.access_level = 1', :limit => 5).size + assert_equal 3, Developer.scoped(:includes => 'projects', :where => { 'developers_projects.access_level' => 1 }, :limit => 5).all.size end def test_order_on_join_table_with_include_and_limit - assert_equal 5, Developer.find(:all, :include => 'projects', :order => 'developers_projects.joined_on DESC', :limit => 5).size + assert_equal 5, Developer.scoped(:includes => 'projects', :order => 'developers_projects.joined_on DESC', :limit => 5).all.size end def test_eager_loading_with_order_on_joined_table_preloads posts = assert_queries(2) do - Post.find(:all, :joins => :comments, :include => :author, :order => 'comments.id DESC') + Post.scoped(:joins => :comments, :includes => :author, :order => 'comments.id DESC').all end assert_equal posts(:eager_other), posts[1] assert_equal authors(:mary), assert_no_queries { posts[1].author} @@ -895,24 +963,24 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_eager_loading_with_conditions_on_joined_table_preloads posts = assert_queries(2) do - Post.find(:all, :select => 'distinct posts.*', :include => :author, :joins => [:comments], :conditions => "comments.body like 'Thank you%'", :order => 'posts.id') + Post.scoped(:select => 'distinct posts.*', :includes => :author, :joins => [:comments], :where => "comments.body like 'Thank you%'", :order => 'posts.id').all end assert_equal [posts(:welcome)], posts assert_equal authors(:david), assert_no_queries { posts[0].author} - posts = assert_queries(ActiveRecord::IdentityMap.enabled? ? 1 : 2) do - Post.find(:all, :select => 'distinct posts.*', :include => :author, :joins => [:comments], :conditions => "comments.body like 'Thank you%'", :order => 'posts.id') + posts = assert_queries(2) do + Post.scoped(:select => 'distinct posts.*', :includes => :author, :joins => [:comments], :where => "comments.body like 'Thank you%'", :order => 'posts.id').all end assert_equal [posts(:welcome)], posts assert_equal authors(:david), assert_no_queries { posts[0].author} - posts = assert_queries(ActiveRecord::IdentityMap.enabled? ? 1 : 2) do - Post.find(:all, :include => :author, :joins => {:taggings => :tag}, :conditions => "tags.name = 'General'", :order => 'posts.id') + posts = assert_queries(2) do + Post.scoped(:includes => :author, :joins => {:taggings => :tag}, :where => "tags.name = 'General'", :order => 'posts.id').all end assert_equal posts(:welcome, :thinking), posts - posts = assert_queries(ActiveRecord::IdentityMap.enabled? ? 1 : 2) do - Post.find(:all, :include => :author, :joins => {:taggings => {:tag => :taggings}}, :conditions => "taggings_tags.super_tag_id=2", :order => 'posts.id') + posts = assert_queries(2) do + Post.scoped(:includes => :author, :joins => {:taggings => {:tag => :taggings}}, :where => "taggings_tags.super_tag_id=2", :order => 'posts.id').all end assert_equal posts(:welcome, :thinking), posts @@ -920,13 +988,13 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_eager_loading_with_conditions_on_string_joined_table_preloads posts = assert_queries(2) do - Post.find(:all, :select => 'distinct posts.*', :include => :author, :joins => "INNER JOIN comments on comments.post_id = posts.id", :conditions => "comments.body like 'Thank you%'", :order => 'posts.id') + Post.scoped(:select => 'distinct posts.*', :includes => :author, :joins => "INNER JOIN comments on comments.post_id = posts.id", :where => "comments.body like 'Thank you%'", :order => 'posts.id').all end assert_equal [posts(:welcome)], posts assert_equal authors(:david), assert_no_queries { posts[0].author} - posts = assert_queries(ActiveRecord::IdentityMap.enabled? ? 1 : 2) do - Post.find(:all, :select => 'distinct posts.*', :include => :author, :joins => ["INNER JOIN comments on comments.post_id = posts.id"], :conditions => "comments.body like 'Thank you%'", :order => 'posts.id') + posts = assert_queries(2) do + Post.scoped(:select => 'distinct posts.*', :includes => :author, :joins => ["INNER JOIN comments on comments.post_id = posts.id"], :where => "comments.body like 'Thank you%'", :order => 'posts.id').all end assert_equal [posts(:welcome)], posts assert_equal authors(:david), assert_no_queries { posts[0].author} @@ -935,7 +1003,7 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_eager_loading_with_select_on_joined_table_preloads posts = assert_queries(2) do - Post.find(:all, :select => 'posts.*, authors.name as author_name', :include => :comments, :joins => :author, :order => 'posts.id') + Post.scoped(:select => 'posts.*, authors.name as author_name', :includes => :comments, :joins => :author, :order => 'posts.id').all end assert_equal 'David', posts[0].author_name assert_equal posts(:welcome).comments, assert_no_queries { posts[0].comments} @@ -945,14 +1013,14 @@ class EagerAssociationTest < ActiveRecord::TestCase Author.columns authors = assert_queries(2) do - Author.find(:all, :include => :author_address, :joins => :comments, :conditions => "posts.title like 'Welcome%'") + Author.scoped(:includes => :author_address, :joins => :comments, :where => "posts.title like 'Welcome%'").all end assert_equal authors(:david), authors[0] assert_equal author_addresses(:david_address), authors[0].author_address end def test_preload_belongs_to_uses_exclusive_scope - people = Person.males.find(:all, :include => :primary_contact) + people = Person.males.scoped(:includes => :primary_contact).all assert_not_equal people.length, 0 people.each do |person| assert_no_queries {assert_not_nil person.primary_contact} @@ -961,15 +1029,15 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_preload_has_many_uses_exclusive_scope - people = Person.males.find :all, :include => :agents + people = Person.males.includes(:agents).all people.each do |person| assert_equal Person.find(person.id).agents, person.agents end end def test_preload_has_many_using_primary_key - expected = Firm.find(:first).clients_using_primary_key.to_a - firm = Firm.find :first, :include => :clients_using_primary_key + expected = Firm.first.clients_using_primary_key.to_a + firm = Firm.includes(:clients_using_primary_key).first assert_no_queries do assert_equal expected, firm.clients_using_primary_key end @@ -979,9 +1047,9 @@ class EagerAssociationTest < ActiveRecord::TestCase expected = Firm.find(1).clients_using_primary_key.sort_by(&:name) # Oracle adapter truncates alias to 30 characters if current_adapter?(:OracleAdapter) - firm = Firm.find 1, :include => :clients_using_primary_key, :order => 'clients_using_primary_keys_companies'[0,30]+'.name' + firm = Firm.scoped(:includes => :clients_using_primary_key, :order => 'clients_using_primary_keys_companies'[0,30]+'.name').find(1) else - firm = Firm.find 1, :include => :clients_using_primary_key, :order => 'clients_using_primary_keys_companies.name' + firm = Firm.scoped(:includes => :clients_using_primary_key, :order => 'clients_using_primary_keys_companies.name').find(1) end assert_no_queries do assert_equal expected, firm.clients_using_primary_key @@ -990,7 +1058,7 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_preload_has_one_using_primary_key expected = accounts(:signals37) - firm = Firm.find :first, :include => :account_using_primary_key, :order => 'companies.id' + firm = Firm.scoped(:includes => :account_using_primary_key, :order => 'companies.id').first assert_no_queries do assert_equal expected, firm.account_using_primary_key end @@ -998,7 +1066,7 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_include_has_one_using_primary_key expected = accounts(:signals37) - firm = Firm.find(:all, :include => :account_using_primary_key, :order => 'accounts.id').detect {|f| f.id == 1} + firm = Firm.scoped(:includes => :account_using_primary_key, :order => 'accounts.id').all.detect {|f| f.id == 1} assert_no_queries do assert_equal expected, firm.account_using_primary_key end @@ -1014,7 +1082,7 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_preloading_empty_belongs_to_polymorphic t = Tagging.create!(:taggable_type => 'Post', :taggable_id => Post.maximum(:id) + 1, :tag => tags(:general)) - tagging = assert_queries(ActiveRecord::IdentityMap.enabled? ? 1 : 2) { Tagging.preload(:taggable).find(t.id) } + tagging = assert_queries(2) { Tagging.preload(:taggable).find(t.id) } assert_no_queries { assert_nil tagging.taggable } end @@ -1060,4 +1128,28 @@ class EagerAssociationTest < ActiveRecord::TestCase Post.includes(:comments).order(nil).where(:comments => {:body => "Thank you for the welcome"}).first end end + + def test_deep_including_through_habtm + posts = Post.scoped(:includes => {:categories => :categorizations}, :order => "posts.id").all + assert_no_queries { assert_equal 2, posts[0].categories[0].categorizations.length } + assert_no_queries { assert_equal 1, posts[0].categories[1].categorizations.length } + assert_no_queries { assert_equal 2, posts[1].categories[0].categorizations.length } + end + + test "scoping with a circular preload" do + assert_equal Comment.find(1), Comment.preload(:post => :comments).scoping { Comment.find(1) } + end + + test "circular preload does not modify unscoped" do + expected = FirstPost.unscoped.find(2) + FirstPost.preload(:comments => :first_post).find(1) + assert_equal expected, FirstPost.unscoped.find(2) + end + + test "preload ignores the scoping" do + assert_equal( + Comment.find(1).post, + Post.where('1 = 0').scoping { Comment.preload(:post).find(1).post } + ) + end end diff --git a/activerecord/test/cases/associations/extension_test.rb b/activerecord/test/cases/associations/extension_test.rb index 8dc1423375..d7c489c2b5 100644 --- a/activerecord/test/cases/associations/extension_test.rb +++ b/activerecord/test/cases/associations/extension_test.rb @@ -36,11 +36,6 @@ class AssociationsExtensionsTest < ActiveRecord::TestCase end def test_marshalling_extensions - if ENV['TRAVIS'] && RUBY_VERSION == "1.8.7" - return skip("Marshalling tests disabled for Ruby 1.8.7 on Travis CI due to what appears " \ - "to be a Ruby bug.") - end - david = developers(:david) assert_equal projects(:action_controller), david.projects.find_most_recent @@ -51,11 +46,6 @@ class AssociationsExtensionsTest < ActiveRecord::TestCase end def test_marshalling_named_extensions - if ENV['TRAVIS'] && RUBY_VERSION == "1.8.7" - return skip("Marshalling tests disabled for Ruby 1.8.7 on Travis CI due to what appears " \ - "to be a Ruby bug.") - end - david = developers(:david) assert_equal projects(:action_controller), david.projects_extended_by_name.find_most_recent @@ -71,6 +61,12 @@ class AssociationsExtensionsTest < ActiveRecord::TestCase assert_equal 'MyApplication::Business::DeveloperAssociationNameAssociationExtension', extension_name(MyApplication::Business::Developer) end + def test_proxy_association_after_scoped + post = posts(:welcome) + assert_equal post.association(:comments), post.comments.the_association + assert_equal post.association(:comments), post.comments.scoped.the_association + end + private def extension_name(model) 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 34d90cc395..22fd80df28 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 @@ -23,7 +23,7 @@ require 'models/treaty' require 'active_support/core_ext/string/conversions' class ProjectWithAfterCreateHook < ActiveRecord::Base - set_table_name 'projects' + self.table_name = 'projects' has_and_belongs_to_many :developers, :class_name => "DeveloperForProjectWithAfterCreateHook", :join_table => "developers_projects", @@ -39,7 +39,7 @@ class ProjectWithAfterCreateHook < ActiveRecord::Base end class DeveloperForProjectWithAfterCreateHook < ActiveRecord::Base - set_table_name 'developers' + self.table_name = 'developers' has_and_belongs_to_many :projects, :class_name => "ProjectWithAfterCreateHook", :join_table => "developers_projects", @@ -48,7 +48,7 @@ class DeveloperForProjectWithAfterCreateHook < ActiveRecord::Base end class ProjectWithSymbolsForKeys < ActiveRecord::Base - set_table_name 'projects' + self.table_name = 'projects' has_and_belongs_to_many :developers, :class_name => "DeveloperWithSymbolsForKeys", :join_table => :developers_projects, @@ -57,7 +57,7 @@ class ProjectWithSymbolsForKeys < ActiveRecord::Base end class DeveloperWithSymbolsForKeys < ActiveRecord::Base - set_table_name 'developers' + self.table_name = 'developers' has_and_belongs_to_many :projects, :class_name => "ProjectWithSymbolsForKeys", :join_table => :developers_projects, @@ -66,7 +66,7 @@ class DeveloperWithSymbolsForKeys < ActiveRecord::Base end class DeveloperWithCounterSQL < ActiveRecord::Base - set_table_name 'developers' + self.table_name = 'developers' has_and_belongs_to_many :projects, :class_name => "DeveloperWithCounterSQL", :join_table => "developers_projects", @@ -77,7 +77,7 @@ end class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase fixtures :accounts, :companies, :categories, :posts, :categories_posts, :developers, :projects, :developers_projects, - :parrots, :pirates, :treasures, :price_estimates, :tags, :taggings + :parrots, :pirates, :parrots_pirates, :treasures, :price_estimates, :tags, :taggings def setup_data_for_habtm_case ActiveRecord::Base.connection.execute('delete from countries_treaties') @@ -355,7 +355,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase def test_deleting_array david = Developer.find(1) david.projects.reload - david.projects.delete(Project.find(:all)) + david.projects.delete(Project.all) assert_equal 0, david.projects.size assert_equal 0, david.projects(true).size end @@ -375,7 +375,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase active_record.developers.reload assert_equal 3, active_record.developers_by_sql.size - active_record.developers_by_sql.delete(Developer.find(:all)) + active_record.developers_by_sql.delete(Developer.all) assert_equal 0, active_record.developers_by_sql(true).size end @@ -445,6 +445,26 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase assert david.projects(true).empty? end + def test_destroy_associations_destroys_multiple_associations + george = parrots(:george) + assert !george.pirates.empty? + assert !george.treasures.empty? + + assert_no_difference "Pirate.count" do + assert_no_difference "Treasure.count" do + george.destroy_associations + end + end + + join_records = Parrot.connection.select_all("SELECT * FROM parrots_pirates WHERE parrot_id = #{george.id}") + assert join_records.empty? + assert george.pirates(true).empty? + + join_records = Parrot.connection.select_all("SELECT * FROM parrots_treasures WHERE parrot_id = #{george.id}") + assert join_records.empty? + assert george.treasures(true).empty? + end + def test_deprecated_push_with_attributes_was_removed jamis = developers(:jamis) assert_raise(NoMethodError) do @@ -528,15 +548,15 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase def test_find_with_merged_options assert_equal 1, projects(:active_record).limited_developers.size - assert_equal 1, projects(:active_record).limited_developers.find(:all).size - assert_equal 3, projects(:active_record).limited_developers.find(:all, :limit => nil).size + assert_equal 1, projects(:active_record).limited_developers.all.size + assert_equal 3, projects(:active_record).limited_developers.limit(nil).all.size end def test_dynamic_find_should_respect_association_order # Developers are ordered 'name DESC, id DESC' high_id_jamis = projects(:active_record).developers.create(:name => 'Jamis') - assert_equal high_id_jamis, projects(:active_record).developers.find(:first, :conditions => "name = 'Jamis'") + assert_equal high_id_jamis, projects(:active_record).developers.scoped(:where => "name = 'Jamis'").first assert_equal high_id_jamis, projects(:active_record).developers.find_by_name('Jamis') end @@ -546,7 +566,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase middle_id_jamis = developers(:poor_jamis) high_id_jamis = projects(:active_record).developers.create(:name => 'Jamis') - assert_equal [high_id_jamis, middle_id_jamis, low_id_jamis], projects(:active_record).developers.find(:all, :conditions => "name = 'Jamis'") + assert_equal [high_id_jamis, middle_id_jamis, low_id_jamis], projects(:active_record).developers.scoped(:where => "name = 'Jamis'").all assert_equal [high_id_jamis, middle_id_jamis, low_id_jamis], projects(:active_record).developers.find_all_by_name('Jamis') end @@ -556,12 +576,12 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase end def test_dynamic_find_all_should_respect_association_limit - assert_equal 1, projects(:active_record).limited_developers.find(:all, :conditions => "name = 'Jamis'").length + assert_equal 1, projects(:active_record).limited_developers.scoped(:where => "name = 'Jamis'").all.length assert_equal 1, projects(:active_record).limited_developers.find_all_by_name('Jamis').length end def test_dynamic_find_all_order_should_override_association_limit - assert_equal 2, projects(:active_record).limited_developers.find(:all, :conditions => "name = 'Jamis'", :limit => 9_000).length + assert_equal 2, projects(:active_record).limited_developers.scoped(:where => "name = 'Jamis'", :limit => 9_000).all.length assert_equal 2, projects(:active_record).limited_developers.find_all_by_name('Jamis', :limit => 9_000).length end @@ -612,7 +632,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase end def test_consider_type - developer = Developer.find(:first) + developer = Developer.first special_project = SpecialProject.create("name" => "Special Project") other_project = developer.projects.first @@ -647,7 +667,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase categories(:technology).select_testing_posts(true).each do |o| assert_respond_to o, :correctness_marker end - assert_respond_to categories(:technology).select_testing_posts.find(:first), :correctness_marker + assert_respond_to categories(:technology).select_testing_posts.first, :correctness_marker end def test_habtm_selects_all_columns_by_default @@ -659,7 +679,13 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase end def test_join_table_alias - assert_equal 3, Developer.find(:all, :include => {:projects => :developers}, :conditions => 'developers_projects_join.joined_on IS NOT NULL').size + assert_equal( + 3, + Developer.references(:developers_projects_join).scoped( + :includes => {:projects => :developers}, + :where => 'developers_projects_join.joined_on IS NOT NULL' + ).to_a.size + ) end def test_join_with_group @@ -669,12 +695,18 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase end Project.columns.each { |c| group << "projects.#{c.name}" } - assert_equal 3, Developer.find(:all, :include => {:projects => :developers}, :conditions => 'developers_projects_join.joined_on IS NOT NULL', :group => group.join(",")).size + assert_equal( + 3, + Developer.references(:developers_projects_join).scoped( + :includes => {:projects => :developers}, :where => 'developers_projects_join.joined_on IS NOT NULL', + :group => group.join(",") + ).to_a.size + ) end def test_find_grouped - all_posts_from_category1 = Post.find(:all, :conditions => "category_id = 1", :joins => :categories) - grouped_posts_of_category1 = Post.find(:all, :conditions => "category_id = 1", :group => "author_id", :select => 'count(posts.id) as posts_count', :joins => :categories) + all_posts_from_category1 = Post.scoped(:where => "category_id = 1", :joins => :categories).all + grouped_posts_of_category1 = Post.scoped(:where => "category_id = 1", :group => "author_id", :select => 'count(posts.id) as posts_count', :joins => :categories).all assert_equal 5, all_posts_from_category1.size assert_equal 2, grouped_posts_of_category1.size end @@ -748,8 +780,8 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase assert_equal 1, project.developers.size assert_equal 1, developer.projects.size - assert_equal developer, project.developers.find(:first) - assert_equal project, developer.projects.find(:first) + assert_equal developer, project.developers.first + assert_equal project, developer.projects.first end def test_self_referential_habtm_without_foreign_key_set_should_raise_exception @@ -766,13 +798,6 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase assert Category.find(1).posts_with_authors_sorted_by_author_id.find_by_title('Welcome to the weblog') end - def test_counting_on_habtm_association_and_not_array - david = Developer.find(1) - # Extra parameter just to make sure we aren't falling back to - # Array#count in Ruby >=1.8.7, which would raise an ArgumentError - assert_nothing_raised { david.projects.count(:all, :conditions => '1=1') } - end - def test_count david = Developer.find(1) assert_equal 2, david.projects.count @@ -795,7 +820,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase def test_association_proxy_transaction_method_starts_transaction_in_association_class Post.expects(:transaction) - Category.find(:first).posts.transaction do + Category.first.posts.transaction do # nothing end end @@ -805,12 +830,11 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase # clear cache possibly created by other tests david.projects.reset_column_information - # One query for columns, one for primary key - assert_queries(2) { david.projects.columns; david.projects.columns } + assert_queries(1) { david.projects.columns; david.projects.columns } ## and again to verify that reset_column_information clears the cache correctly david.projects.reset_column_information - assert_queries(2) { david.projects.columns; david.projects.columns } + assert_queries(1) { david.projects.columns; david.projects.columns } end def test_attributes_are_being_set_when_initialized_from_habm_association_with_where_clause diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index a60af7c046..f74fe42dc2 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -8,6 +8,7 @@ require 'models/reply' require 'models/category' require 'models/post' require 'models/author' +require 'models/essay' require 'models/comment' require 'models/person' require 'models/reader' @@ -56,12 +57,22 @@ class HasManyAssociationsTestForCountDistinctWithFinderSql < ActiveRecord::TestC end end +class HasManyAssociationsTestForReorderWithJoinDependency < ActiveRecord::TestCase + fixtures :authors, :posts, :comments + + def test_should_generate_valid_sql + author = authors(:david) + # this can fail on adapters which require ORDER BY expressions to be included in the SELECT expression + # if the reorder clauses are not correctly handled + assert author.posts_with_comments_sorted_by_comment_id.where('comments.id > 0').reorder('posts.comments_count DESC', 'posts.taggings_count DESC').last + end +end class HasManyAssociationsTest < ActiveRecord::TestCase fixtures :accounts, :categories, :companies, :developers, :projects, :developers_projects, :topics, :authors, :comments, - :people, :posts, :readers, :taggings, :cars + :people, :posts, :readers, :taggings, :cars, :essays def setup Client.destroyed_client_ids.clear @@ -119,6 +130,28 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal car.id, bulb.car_id end + def test_association_protect_foreign_key + invoice = Invoice.create + + line_item = invoice.line_items.new + assert_equal invoice.id, line_item.invoice_id + + line_item = invoice.line_items.new :invoice_id => invoice.id + 1 + assert_equal invoice.id, line_item.invoice_id + + line_item = invoice.line_items.build + assert_equal invoice.id, line_item.invoice_id + + line_item = invoice.line_items.build :invoice_id => invoice.id + 1 + assert_equal invoice.id, line_item.invoice_id + + line_item = invoice.line_items.create + assert_equal invoice.id, line_item.invoice_id + + line_item = invoice.line_items.create :invoice_id => invoice.id + 1 + assert_equal invoice.id, line_item.invoice_id + end + def test_association_conditions_bypass_attribute_protection car = Car.create(:name => 'honda') @@ -209,27 +242,19 @@ 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.find(:first, :order => "id").clients.count + assert_equal 2, Firm.scoped(:order => "id").first.clients.count end def test_counting - assert_equal 2, Firm.find(:first, :order => "id").plain_clients.count - end - - def test_counting_with_empty_hash_conditions - assert_equal 2, Firm.find(:first, :order => "id").plain_clients.count(:conditions => {}) - end - - def test_counting_with_single_conditions - assert_equal 1, Firm.find(:first, :order => "id").plain_clients.count(:conditions => ['name=?', "Microsoft"]) + assert_equal 2, Firm.scoped(:order => "id").first.plain_clients.count end def test_counting_with_single_hash - assert_equal 1, Firm.find(:first, :order => "id").plain_clients.count(:conditions => {:name => "Microsoft"}) + assert_equal 1, Firm.scoped(:order => "id").first.plain_clients.where(:name => "Microsoft").count end def test_counting_with_column_name_and_hash - assert_equal 2, Firm.find(:first, :order => "id").plain_clients.count(:name) + assert_equal 2, Firm.scoped(:order => "id").first.plain_clients.count(:name) end def test_counting_with_association_limit @@ -239,7 +264,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_finding - assert_equal 2, Firm.find(:first, :order => "id").clients.length + assert_equal 2, Firm.scoped(:order => "id").first.clients.length end def test_finding_array_compatibility @@ -248,14 +273,14 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_find_with_blank_conditions [[], {}, nil, ""].each do |blank| - assert_equal 2, Firm.find(:first, :order => "id").clients.find(:all, :conditions => blank).size + assert_equal 2, Firm.scoped(:order => "id").first.clients.where(blank).all.size end 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.find(:all).size - assert_equal 2, companies(:first_firm).limited_clients.find(:all, :limit => nil).size + assert_equal 1, companies(:first_firm).limited_clients.all.size + assert_equal 2, companies(:first_firm).limited_clients.limit(nil).all.size end def test_find_should_append_to_association_order @@ -268,30 +293,25 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_dynamic_find_should_respect_association_order - assert_equal companies(:second_client), companies(:first_firm).clients_sorted_desc.find(:first, :conditions => "type = 'Client'") + assert_equal companies(:second_client), companies(:first_firm).clients_sorted_desc.scoped(:where => "type = 'Client'").first assert_equal companies(:second_client), companies(:first_firm).clients_sorted_desc.find_by_type('Client') end def test_dynamic_find_all_should_respect_association_order - assert_equal [companies(:second_client), companies(:first_client)], companies(:first_firm).clients_sorted_desc.find(:all, :conditions => "type = 'Client'") + assert_equal [companies(:second_client), companies(:first_client)], companies(:first_firm).clients_sorted_desc.scoped(:where => "type = 'Client'").all assert_equal [companies(:second_client), companies(:first_client)], companies(:first_firm).clients_sorted_desc.find_all_by_type('Client') end def test_dynamic_find_all_should_respect_association_limit - assert_equal 1, companies(:first_firm).limited_clients.find(:all, :conditions => "type = 'Client'").length + assert_equal 1, companies(:first_firm).limited_clients.scoped(:where => "type = 'Client'").all.length assert_equal 1, companies(:first_firm).limited_clients.find_all_by_type('Client').length end def test_dynamic_find_all_limit_should_override_association_limit - assert_equal 2, companies(:first_firm).limited_clients.find(:all, :conditions => "type = 'Client'", :limit => 9_000).length + assert_equal 2, companies(:first_firm).limited_clients.scoped(:where => "type = 'Client'", :limit => 9_000).all.length assert_equal 2, companies(:first_firm).limited_clients.find_all_by_type('Client', :limit => 9_000).length end - def test_dynamic_find_all_should_respect_readonly_access - companies(:first_firm).readonly_clients.find(:all).each { |c| assert_raise(ActiveRecord::ReadOnlyRecord) { c.save! } } - companies(:first_firm).readonly_clients.find(:all).each { |c| assert c.readonly? } - end - def test_dynamic_find_or_create_from_two_attributes_using_an_association author = authors(:david) number_of_posts = Post.count @@ -308,52 +328,59 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_triple_equality # sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first - assert !(Array === Firm.find(:first, :order => "id").clients) - assert Firm.find(:first, :order => "id").clients === Array + assert !(Array === Firm.scoped(:order => "id").first.clients) + assert Firm.scoped(:order => "id").first.clients === Array end def test_finding_default_orders - assert_equal "Summit", Firm.find(:first, :order => "id").clients.first.name + assert_equal "Summit", Firm.scoped(:order => "id").first.clients.first.name end def test_finding_with_different_class_name_and_order - assert_equal "Microsoft", Firm.find(:first, :order => "id").clients_sorted_desc.first.name + assert_equal "Microsoft", Firm.scoped(:order => "id").first.clients_sorted_desc.first.name end def test_finding_with_foreign_key - assert_equal "Microsoft", Firm.find(:first, :order => "id").clients_of_firm.first.name + assert_equal "Microsoft", Firm.scoped(:order => "id").first.clients_of_firm.first.name end def test_finding_with_condition - assert_equal "Microsoft", Firm.find(:first, :order => "id").clients_like_ms.first.name + assert_equal "Microsoft", Firm.scoped(:order => "id").first.clients_like_ms.first.name end def test_finding_with_condition_hash - assert_equal "Microsoft", Firm.find(:first, :order => "id").clients_like_ms_with_hash_conditions.first.name + assert_equal "Microsoft", Firm.scoped(:order => "id").first.clients_like_ms_with_hash_conditions.first.name end def test_finding_using_primary_key - assert_equal "Summit", Firm.find(:first, :order => "id").clients_using_primary_key.first.name + assert_equal "Summit", Firm.scoped(:order => "id").first.clients_using_primary_key.first.name end def test_finding_using_sql - firm = Firm.find(:first, :order => "id") + firm = Firm.scoped(:order => "id").first first_client = firm.clients_using_sql.first assert_not_nil first_client assert_equal "Microsoft", first_client.name assert_equal 1, firm.clients_using_sql.size - assert_equal 1, Firm.find(:first, :order => "id").clients_using_sql.size + assert_equal 1, Firm.scoped(:order => "id").first.clients_using_sql.size + end + + def test_finding_using_sql_take_into_account_only_uniq_ids + firm = Firm.scoped(:order => "id").first + client = firm.clients_using_sql.first + assert_equal client, firm.clients_using_sql.find(client.id, client.id) + assert_equal client, firm.clients_using_sql.find(client.id, client.id.to_s) end def test_counting_using_sql - assert_equal 1, Firm.find(:first, :order => "id").clients_using_counter_sql.size - assert Firm.find(:first, :order => "id").clients_using_counter_sql.any? - assert_equal 0, Firm.find(:first, :order => "id").clients_using_zero_counter_sql.size - assert !Firm.find(:first, :order => "id").clients_using_zero_counter_sql.any? + assert_equal 1, Firm.scoped(:order => "id").first.clients_using_counter_sql.size + assert Firm.scoped(:order => "id").first.clients_using_counter_sql.any? + assert_equal 0, Firm.scoped(:order => "id").first.clients_using_zero_counter_sql.size + assert !Firm.scoped(:order => "id").first.clients_using_zero_counter_sql.any? end def test_counting_non_existant_items_using_sql - assert_equal 0, Firm.find(:first, :order => "id").no_clients_using_counter_sql.size + assert_equal 0, Firm.scoped(:order => "id").first.no_clients_using_counter_sql.size end def test_counting_using_finder_sql @@ -368,7 +395,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_find_ids - firm = Firm.find(:first, :order => "id") + firm = Firm.scoped(:order => "id").first assert_raise(ActiveRecord::RecordNotFound) { firm.clients.find } @@ -388,7 +415,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_find_string_ids_when_using_finder_sql - firm = Firm.find(:first, :order => "id") + firm = Firm.scoped(:order => "id").first client = firm.clients_using_finder_sql.find("2") assert_kind_of Client, client @@ -404,9 +431,9 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_find_all - firm = Firm.find(:first, :order => "id") - assert_equal 2, firm.clients.find(:all, :conditions => "#{QUOTED_TYPE} = 'Client'").length - assert_equal 1, firm.clients.find(:all, :conditions => "name = 'Summit'").length + firm = Firm.scoped(:order => "id").first + assert_equal 2, firm.clients.scoped(:where => "#{QUOTED_TYPE} = 'Client'").all.length + assert_equal 1, firm.clients.scoped(:where => "name = 'Summit'").all.length end def test_find_each @@ -425,7 +452,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase firm = companies(:first_firm) assert_queries(2) do - firm.clients.find_each(:batch_size => 1, :conditions => {:name => "Microsoft"}) do |c| + firm.clients.where(name: 'Microsoft').find_each(batch_size: 1) do |c| assert_equal firm.id, c.firm_id assert_equal "Microsoft", c.name end @@ -450,29 +477,29 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_find_all_sanitized # sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first - firm = Firm.find(:first, :order => "id") - summit = firm.clients.find(:all, :conditions => "name = 'Summit'") - assert_equal summit, firm.clients.find(:all, :conditions => ["name = ?", "Summit"]) - assert_equal summit, firm.clients.find(:all, :conditions => ["name = :name", { :name => "Summit" }]) + firm = Firm.scoped(:order => "id").first + summit = firm.clients.scoped(:where => "name = 'Summit'").all + assert_equal summit, firm.clients.scoped(:where => ["name = ?", "Summit"]).all + assert_equal summit, firm.clients.scoped(:where => ["name = :name", { :name => "Summit" }]).all end def test_find_first - firm = Firm.find(:first, :order => "id") + firm = Firm.scoped(:order => "id").first client2 = Client.find(2) - assert_equal firm.clients.first, firm.clients.find(:first, :order => "id") - assert_equal client2, firm.clients.find(:first, :conditions => "#{QUOTED_TYPE} = 'Client'", :order => "id") + assert_equal firm.clients.first, firm.clients.scoped(:order => "id").first + assert_equal client2, firm.clients.scoped(:where => "#{QUOTED_TYPE} = 'Client'", :order => "id").first end def test_find_first_sanitized - firm = Firm.find(:first, :order => "id") + firm = Firm.scoped(:order => "id").first client2 = Client.find(2) - assert_equal client2, firm.clients.find(:first, :conditions => ["#{QUOTED_TYPE} = ?", 'Client'], :order => "id") - assert_equal client2, firm.clients.find(:first, :conditions => ["#{QUOTED_TYPE} = :type", { :type => 'Client' }], :order => "id") + assert_equal client2, firm.clients.scoped(:where => ["#{QUOTED_TYPE} = ?", 'Client'], :order => "id").first + assert_equal client2, firm.clients.scoped(:where => ["#{QUOTED_TYPE} = :type", { :type => 'Client' }], :order => "id").first end def test_find_all_with_include_and_conditions assert_nothing_raised do - Developer.find(:all, :joins => :audit_logs, :conditions => {'audit_logs.message' => nil, :name => 'Smith'}) + Developer.scoped(:joins => :audit_logs, :where => {'audit_logs.message' => nil, :name => 'Smith'}).all end end @@ -482,8 +509,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_find_grouped - all_clients_of_firm1 = Client.find(:all, :conditions => "firm_id = 1") - grouped_clients_of_firm1 = Client.find(:all, :conditions => "firm_id = 1", :group => "firm_id", :select => 'firm_id, count(id) as clients_count') + all_clients_of_firm1 = Client.scoped(:where => "firm_id = 1").all + grouped_clients_of_firm1 = Client.scoped(:where => "firm_id = 1", :group => "firm_id", :select => 'firm_id, count(id) as clients_count').all assert_equal 2, all_clients_of_firm1.size assert_equal 1, grouped_clients_of_firm1.size end @@ -541,7 +568,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_create_with_bang_on_has_many_raises_when_record_not_saved assert_raise(ActiveRecord::RecordInvalid) do - firm = Firm.find(:first, :order => "id") + firm = Firm.scoped(:order => "id").first firm.plain_clients.create! end end @@ -727,6 +754,18 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal number_of_clients + 1, companies(:first_firm).clients_of_firm.size end + def test_find_or_initialize_returns_the_instantiated_object + client = companies(:first_firm).clients_of_firm.find_or_initialize_by_name("name" => "Another Client") + assert_equal client, companies(:first_firm).clients_of_firm[-1] + end + + def test_find_or_initialize_only_instantiates_a_single_object + number_of_clients = Client.count + companies(:first_firm).clients_of_firm.find_or_initialize_by_name("name" => "Another Client").save! + companies(:first_firm).save! + assert_equal number_of_clients+1, Client.count + end + def test_find_or_create_with_hash post = authors(:david).posts.find_or_create_by_title(:title => 'Yet another post', :body => 'somebody') assert_equal post, authors(:david).posts.find_or_create_by_title(:title => 'Yet another post', :body => 'somebody') @@ -761,7 +800,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_deleting_updates_counter_cache - topic = Topic.first(:order => "id ASC") + topic = Topic.order("id ASC").first assert_equal topic.replies.to_a.size, topic.replies_count topic.replies.delete(topic.replies.first) @@ -956,14 +995,14 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_delete_all_association_with_primary_key_deletes_correct_records - firm = Firm.find(:first) + firm = Firm.first # break the vanilla firm_id foreign key assert_equal 2, firm.clients.count firm.clients.first.update_column(:firm_id, nil) assert_equal 1, firm.clients(true).count assert_equal 1, firm.clients_using_primary_key_with_delete_all.count old_record = firm.clients_using_primary_key_with_delete_all.first - firm = Firm.find(:first) + firm = Firm.first firm.destroy assert_nil Client.find_by_id(old_record.id) end @@ -1071,7 +1110,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase firm = companies(:first_firm) assert_equal 2, firm.clients.size firm.destroy - assert Client.find(:all, :conditions => "firm_id=#{firm.id}").empty? + assert Client.scoped(:where => "firm_id=#{firm.id}").all.empty? end def test_dependence_for_associations_with_hash_condition @@ -1081,7 +1120,7 @@ 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.find(:first, :order => "id") + firm = Firm.scoped(:order => "id").first assert_equal 2, firm.clients.size client = firm.clients.first @@ -1109,7 +1148,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase firm.destroy rescue "do nothing" - assert_equal 2, Client.find(:all, :conditions => "firm_id=#{firm.id}").size + assert_equal 2, Client.scoped(:where => "firm_id=#{firm.id}").all.size end def test_dependence_on_account @@ -1129,16 +1168,42 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_nil companies(:leetsoft).reload.client_of assert_nil companies(:jadedpixel).reload.client_of - assert_equal num_accounts, Account.count end def test_restrict - firm = RestrictedFirm.new(:name => 'restrict') - firm.save! + option_before = ActiveRecord::Base.dependent_restrict_raises + ActiveRecord::Base.dependent_restrict_raises = true + + firm = RestrictedFirm.create!(:name => 'restrict') firm.companies.create(:name => 'child') + assert !firm.companies.empty? assert_raise(ActiveRecord::DeleteRestrictionError) { firm.destroy } + assert RestrictedFirm.exists?(:name => 'restrict') + assert firm.companies.exists?(:name => 'child') + ensure + ActiveRecord::Base.dependent_restrict_raises = option_before + end + + def test_restrict_when_dependent_restrict_raises_config_set_to_false + option_before = ActiveRecord::Base.dependent_restrict_raises + ActiveRecord::Base.dependent_restrict_raises = false + + firm = RestrictedFirm.create!(:name => 'restrict') + firm.companies.create(:name => 'child') + + assert !firm.companies.empty? + + firm.destroy + + assert !firm.errors.empty? + + assert_equal "Cannot delete record because dependent companies exist", firm.errors[:base].first + assert RestrictedFirm.exists?(:name => 'restrict') + assert firm.companies.exists?(:name => 'child') + ensure + ActiveRecord::Base.dependent_restrict_raises = option_before end def test_included_in_collection @@ -1146,16 +1211,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_adding_array_and_collection - assert_nothing_raised { Firm.find(:first).clients + Firm.find(:all).last.clients } - end - - def test_find_all_without_conditions - firm = companies(:first_firm) - assert_equal 2, firm.clients.find(:all).length + assert_nothing_raised { Firm.first.clients + Firm.all.last.clients } end def test_replace_with_less - firm = Firm.find(:first, :order => "id") + firm = Firm.scoped(:order => "id").first firm.clients = [companies(:first_client)] assert firm.save, "Could not save firm" firm.reload @@ -1169,7 +1229,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_replace_with_new - firm = Firm.find(:first, :order => "id") + firm = Firm.scoped(:order => "id").first firm.clients = [companies(:second_client), Client.new("name" => "New Client")] firm.save firm.reload @@ -1242,6 +1302,10 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert company.clients_using_sql.loaded? 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 + end + def test_assign_ids_ignoring_blanks firm = Firm.create!(:name => 'Apple') firm.client_ids = [companies(:first_client).id, nil, companies(:second_client).id, ''] @@ -1265,27 +1329,27 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_dynamic_find_should_respect_association_order_for_through - assert_equal Comment.find(10), authors(:david).comments_desc.find(:first, :conditions => "comments.type = 'SpecialComment'") + assert_equal Comment.find(10), authors(:david).comments_desc.scoped(:where => "comments.type = 'SpecialComment'").first assert_equal Comment.find(10), authors(:david).comments_desc.find_by_type('SpecialComment') end def test_dynamic_find_all_should_respect_association_order_for_through - assert_equal [Comment.find(10), Comment.find(7), Comment.find(6), Comment.find(3)], authors(:david).comments_desc.find(:all, :conditions => "comments.type = 'SpecialComment'") + assert_equal [Comment.find(10), Comment.find(7), Comment.find(6), Comment.find(3)], authors(:david).comments_desc.scoped(:where => "comments.type = 'SpecialComment'").all assert_equal [Comment.find(10), Comment.find(7), Comment.find(6), Comment.find(3)], authors(:david).comments_desc.find_all_by_type('SpecialComment') end def test_dynamic_find_all_should_respect_association_limit_for_through - assert_equal 1, authors(:david).limited_comments.find(:all, :conditions => "comments.type = 'SpecialComment'").length + assert_equal 1, authors(:david).limited_comments.scoped(:where => "comments.type = 'SpecialComment'").all.length assert_equal 1, authors(:david).limited_comments.find_all_by_type('SpecialComment').length end def test_dynamic_find_all_order_should_override_association_limit_for_through - assert_equal 4, authors(:david).limited_comments.find(:all, :conditions => "comments.type = 'SpecialComment'", :limit => 9_000).length + assert_equal 4, authors(:david).limited_comments.scoped(:where => "comments.type = 'SpecialComment'", :limit => 9_000).all.length assert_equal 4, authors(:david).limited_comments.find_all_by_type('SpecialComment', :limit => 9_000).length end def test_find_all_include_over_the_same_table_for_through - assert_equal 2, people(:michael).posts.find(:all, :include => :people).length + assert_equal 2, people(:michael).posts.scoped(:includes => :people).all.length end def test_has_many_through_respects_hash_conditions @@ -1391,14 +1455,29 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end end - def test_calling_first_or_last_with_find_options_on_loaded_association_should_fetch_with_query - firm = companies(:first_firm) - firm.clients.class # force load target + def test_custom_primary_key_on_new_record_should_fetch_with_query + author = Author.new(:name => "David") + assert !author.essays.loaded? - assert_queries 2 do - assert firm.clients.loaded? - firm.clients.first(:order => 'name') - firm.clients.last(:order => 'name') + assert_queries 1 do + assert_equal 1, author.essays.size + end + + assert_equal author.essays, Essay.find_all_by_writer_id("David") + + end + + def test_has_many_custom_primary_key + david = authors(:david) + assert_equal david.essays, Essay.find_all_by_writer_id("David") + end + + def test_blank_custom_primary_key_on_new_record_should_not_run_queries + author = Author.new + assert !author.essays.loaded? + + assert_queries 0 do + assert_equal 0, author.essays.size end end @@ -1459,11 +1538,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase firm = Namespaced::Firm.create({ :name => 'Some Company' }) firm.clients.create({ :name => 'Some Client' }) - stats = Namespaced::Firm.find(firm.id, { + stats = Namespaced::Firm.scoped( :select => "#{Namespaced::Firm.table_name}.id, COUNT(#{Namespaced::Client.table_name}.id) AS num_clients", :joins => :clients, :group => "#{Namespaced::Firm.table_name}.id" - }) + ).find firm.id assert_equal 1, stats.num_clients.to_i ensure @@ -1472,7 +1551,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_association_proxy_transaction_method_starts_transaction_in_association_class Comment.expects(:transaction) - Post.find(:first).comments.transaction do + Post.first.comments.transaction do # nothing end end @@ -1489,7 +1568,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_creating_using_primary_key - firm = Firm.find(:first, :order => "id") + firm = Firm.scoped(:order => "id").first client = firm.clients_using_primary_key.create!(:name => 'test') assert_equal firm.name, client.firm_name end @@ -1612,4 +1691,21 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal [bulb2], car.bulbs assert_equal [bulb2], car.reload.bulbs end + + def test_building_has_many_association_with_restrict_dependency + option_before = ActiveRecord::Base.dependent_restrict_raises + ActiveRecord::Base.dependent_restrict_raises = true + + klass = Class.new(ActiveRecord::Base) + + assert_deprecated { klass.has_many :companies, :dependent => :restrict } + assert_not_deprecated { klass.has_many :companies } + ensure + ActiveRecord::Base.dependent_restrict_raises = option_before + end + + def test_collection_association_with_private_kernel_method + firm = companies(:first_firm) + assert_equal [accounts(:signals37)], firm.accounts.open + 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 7a6aba6a6b..1c06007d86 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -44,17 +44,33 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase end def test_associate_existing - posts(:thinking); people(:david) # Warm cache + post = posts(:thinking) + person = people(:david) assert_queries(1) do - posts(:thinking).people << people(:david) + post.people << person end assert_queries(1) do - assert posts(:thinking).people.include?(people(:david)) + assert post.people.include?(person) end - assert posts(:thinking).reload.people(true).include?(people(:david)) + assert post.reload.people(true).include?(person) + end + + def test_associate_existing_with_strict_mass_assignment_sanitizer + SecureReader.mass_assignment_sanitizer = :strict + + SecureReader.new + + post = posts(:thinking) + person = people(:david) + + assert_queries(1) do + post.secure_people << person + end + ensure + SecureReader.mass_assignment_sanitizer = :logger end def test_associate_existing_record_twice_should_add_to_target_twice @@ -517,7 +533,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase end def test_count_with_include_should_alias_join_table - assert_equal 2, people(:michael).posts.count(:include => :readers) + assert_equal 2, people(:michael).posts.includes(:readers).count end def test_inner_join_with_quoted_table_name @@ -528,6 +544,12 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase assert_equal [posts(:welcome).id, posts(:authorless).id].sort, people(:michael).post_ids.sort end + def test_get_ids_for_has_many_through_with_conditions_should_not_preload + Tagging.create!(:taggable_type => 'Post', :taggable_id => posts(:welcome).id, :tag => tags(:misc)) + ActiveRecord::Associations::Preloader.expects(:new).never + posts(:welcome).misc_tag_ids + end + def test_get_ids_for_loaded_associations person = people(:michael) person.posts(true) @@ -546,7 +568,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase def test_association_proxy_transaction_method_starts_transaction_in_association_class Tag.expects(:transaction) - Post.find(:first).tags.transaction do + Post.first.tags.transaction do # nothing end end @@ -629,7 +651,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase def test_collection_singular_ids_setter company = companies(:rails_core) - dev = Developer.find(:first) + dev = Developer.first company.developer_ids = [dev.id] assert_equal [dev], company.developers @@ -649,7 +671,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase def test_collection_singular_ids_setter_raises_exception_when_invalid_ids_set company = companies(:rails_core) - ids = [Developer.find(:first).id, -9999] + ids = [Developer.first.id, -9999] assert_raises(ActiveRecord::RecordNotFound) {company.developer_ids= ids} end @@ -682,6 +704,17 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase assert author.comments.include?(comment) end + def test_through_association_readonly_should_be_false + assert !people(:michael).posts.first.readonly? + assert !people(:michael).posts.all.first.readonly? + end + + def test_can_update_through_association + assert_nothing_raised do + people(:michael).posts.first.update_attributes!(:title => "Can write") + end + end + def test_has_many_through_polymorphic_with_primary_key_option assert_equal [categories(:general)], authors(:david).essay_categories @@ -732,7 +765,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase def test_select_chosen_fields_only author = authors(:david) - assert_equal ['body'], author.comments.select('comments.body').first.attributes.keys + assert_equal ['body', 'id'].sort, author.comments.select('comments.body').first.attributes.keys.sort end def test_get_has_many_through_belongs_to_ids_with_conditions @@ -841,7 +874,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase def test_preloading_empty_through_association_via_joins person = Person.create!(:first_name => "Gaga") - person = Person.where(:id => person.id).where('readers.id = 1 or 1=1').includes(:posts).to_a.first + person = Person.where(:id => person.id).where('readers.id = 1 or 1=1').references(:readers).includes(:posts).to_a.first assert person.posts.loaded?, 'person.posts should be loaded' assert_equal [], person.posts diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb index 26931e3e85..88ec65706c 100644 --- a/activerecord/test/cases/associations/has_one_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_associations_test.rb @@ -25,13 +25,13 @@ class HasOneAssociationsTest < ActiveRecord::TestCase assert_queries(1) { assert_nil firm.account } assert_queries(0) { assert_nil firm.account } - firms = Firm.find(:all, :include => :account) + firms = Firm.scoped(:includes => :account).all assert_queries(0) { firms.each(&:account) } end def test_with_select assert_equal Firm.find(1).account_with_select.attributes.size, 2 - assert_equal Firm.find(1, :include => :account_with_select).account_with_select.attributes.size, 2 + assert_equal Firm.scoped(:includes => :account_with_select).find(1).account_with_select.attributes.size, 2 end def test_finding_using_primary_key @@ -157,11 +157,62 @@ class HasOneAssociationsTest < ActiveRecord::TestCase end def test_dependence_with_restrict - firm = RestrictedFirm.new(:name => 'restrict') - firm.save! + option_before = ActiveRecord::Base.dependent_restrict_raises + ActiveRecord::Base.dependent_restrict_raises = true + + firm = RestrictedFirm.create!(:name => 'restrict') firm.create_account(:credit_limit => 10) + assert_not_nil firm.account + assert_raise(ActiveRecord::DeleteRestrictionError) { firm.destroy } + assert RestrictedFirm.exists?(:name => 'restrict') + assert firm.account.present? + ensure + ActiveRecord::Base.dependent_restrict_raises = option_before + end + + def test_dependence_with_restrict_with_dependent_restrict_raises_config_set_to_false + option_before = ActiveRecord::Base.dependent_restrict_raises + ActiveRecord::Base.dependent_restrict_raises = false + + firm = RestrictedFirm.create!(:name => 'restrict') + firm.create_account(:credit_limit => 10) + + assert_not_nil firm.account + + firm.destroy + + assert !firm.errors.empty? + assert_equal "Cannot delete record because a dependent account exists", firm.errors[:base].first + assert RestrictedFirm.exists?(:name => 'restrict') + assert firm.account.present? + ensure + ActiveRecord::Base.dependent_restrict_raises = option_before + end + + def test_dependence_with_restrict_with_dependent_restrict_raises_config_set_to_false_and_attribute_name + old_backend = I18n.backend + I18n.backend = I18n::Backend::Simple.new + I18n.backend.store_translations 'en', :activerecord => {:attributes => {:restricted_firm => {:account => "account model"}}} + + option_before = ActiveRecord::Base.dependent_restrict_raises + ActiveRecord::Base.dependent_restrict_raises = false + + firm = RestrictedFirm.create!(:name => 'restrict') + firm.create_account(:credit_limit => 10) + + assert_not_nil firm.account + + firm.destroy + + assert !firm.errors.empty? + assert_equal "Cannot delete record because a dependent account model exists", firm.errors[:base].first + assert RestrictedFirm.exists?(:name => 'restrict') + assert firm.account.present? + ensure + ActiveRecord::Base.dependent_restrict_raises = option_before + I18n.backend = old_backend end def test_successful_build_association @@ -243,13 +294,13 @@ class HasOneAssociationsTest < ActiveRecord::TestCase def test_dependence_with_missing_association_and_nullify Account.destroy_all - firm = DependentFirm.find(:first) + firm = DependentFirm.first assert_nil firm.account firm.destroy end def test_finding_with_interpolated_condition - firm = Firm.find(:first) + firm = Firm.first superior = firm.clients.create(:name => 'SuperiorCo') superior.rating = 10 superior.save @@ -295,14 +346,14 @@ class HasOneAssociationsTest < ActiveRecord::TestCase assert_nothing_raised do Firm.find(@firm.id).save! - Firm.find(@firm.id, :include => :account).save! + Firm.scoped(:includes => :account).find(@firm.id).save! end @firm.account.destroy assert_nothing_raised do Firm.find(@firm.id).save! - Firm.find(@firm.id, :include => :account).save! + Firm.scoped(:includes => :account).find(@firm.id).save! end end @@ -397,6 +448,22 @@ class HasOneAssociationsTest < ActiveRecord::TestCase assert_equal car.id, bulb.car_id end + def test_association_protect_foreign_key + pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?") + + ship = pirate.build_ship + assert_equal pirate.id, ship.pirate_id + + ship = pirate.build_ship :pirate_id => pirate.id + 1 + assert_equal pirate.id, ship.pirate_id + + ship = pirate.create_ship + assert_equal pirate.id, ship.pirate_id + + ship = pirate.create_ship :pirate_id => pirate.id + 1 + assert_equal pirate.id, ship.pirate_id + end + def test_association_conditions_bypass_attribute_protection car = Car.create(:name => 'honda') @@ -456,4 +523,16 @@ class HasOneAssociationsTest < ActiveRecord::TestCase assert_equal car.id, bulb.attributes_after_initialize['car_id'] end + + def test_building_has_one_association_with_dependent_restrict + option_before = ActiveRecord::Base.dependent_restrict_raises + ActiveRecord::Base.dependent_restrict_raises = true + + klass = Class.new(ActiveRecord::Base) + + assert_deprecated { klass.has_one :account, :dependent => :restrict } + assert_not_deprecated { klass.has_one :account } + ensure + ActiveRecord::Base.dependent_restrict_raises = option_before + 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 2503349c08..94b9639e57 100644 --- a/activerecord/test/cases/associations/has_one_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_through_associations_test.rb @@ -73,7 +73,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase def test_has_one_through_eager_loading members = assert_queries(3) do #base table, through table, clubs table - Member.find(:all, :include => :club, :conditions => ["name = ?", "Groucho Marx"]) + Member.scoped(:includes => :club, :where => ["name = ?", "Groucho Marx"]).all end assert_equal 1, members.size assert_not_nil assert_no_queries {members[0].club} @@ -81,7 +81,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase def test_has_one_through_eager_loading_through_polymorphic members = assert_queries(3) do #base table, through table, clubs table - Member.find(:all, :include => :sponsor_club, :conditions => ["name = ?", "Groucho Marx"]) + Member.scoped(:includes => :sponsor_club, :where => ["name = ?", "Groucho Marx"]).all end assert_equal 1, members.size assert_not_nil assert_no_queries {members[0].sponsor_club} @@ -89,14 +89,14 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase def test_has_one_through_with_conditions_eager_loading # conditions on the through table - assert_equal clubs(:moustache_club), Member.find(@member.id, :include => :favourite_club).favourite_club + assert_equal clubs(:moustache_club), Member.scoped(:includes => :favourite_club).find(@member.id).favourite_club memberships(:membership_of_favourite_club).update_column(:favourite, false) - assert_equal nil, Member.find(@member.id, :include => :favourite_club).reload.favourite_club + assert_equal nil, Member.scoped(:includes => :favourite_club).find(@member.id).reload.favourite_club # conditions on the source table - assert_equal clubs(:moustache_club), Member.find(@member.id, :include => :hairy_club).hairy_club + assert_equal clubs(:moustache_club), Member.scoped(:includes => :hairy_club).find(@member.id).hairy_club clubs(:moustache_club).update_column(:name, "Association of Clean-Shaven Persons") - assert_equal nil, Member.find(@member.id, :include => :hairy_club).reload.hairy_club + assert_equal nil, Member.scoped(:includes => :hairy_club).find(@member.id).reload.hairy_club end def test_has_one_through_polymorphic_with_source_type @@ -104,14 +104,14 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase end def test_eager_has_one_through_polymorphic_with_source_type - clubs = Club.find(:all, :include => :sponsored_member, :conditions => ["name = ?","Moustache and Eyebrow Fancier Club"]) + clubs = Club.scoped(:includes => :sponsored_member, :where => ["name = ?","Moustache and Eyebrow Fancier Club"]).all # Only the eyebrow fanciers club has a sponsored_member assert_not_nil assert_no_queries {clubs[0].sponsored_member} end def test_has_one_through_nonpreload_eagerloading members = assert_queries(1) do - Member.find(:all, :include => :club, :conditions => ["members.name = ?", "Groucho Marx"], :order => 'clubs.name') #force fallback + Member.scoped(:includes => :club, :where => ["members.name = ?", "Groucho Marx"], :order => 'clubs.name').all #force fallback end assert_equal 1, members.size assert_not_nil assert_no_queries {members[0].club} @@ -119,7 +119,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase def test_has_one_through_nonpreload_eager_loading_through_polymorphic members = assert_queries(1) do - Member.find(:all, :include => :sponsor_club, :conditions => ["members.name = ?", "Groucho Marx"], :order => 'clubs.name') #force fallback + Member.scoped(:includes => :sponsor_club, :where => ["members.name = ?", "Groucho Marx"], :order => 'clubs.name').all #force fallback end assert_equal 1, members.size assert_not_nil assert_no_queries {members[0].sponsor_club} @@ -128,7 +128,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase def test_has_one_through_nonpreload_eager_loading_through_polymorphic_with_more_than_one_through_record Sponsor.new(:sponsor_club => clubs(:crazy_club), :sponsorable => members(:groucho)).save! members = assert_queries(1) do - Member.find(:all, :include => :sponsor_club, :conditions => ["members.name = ?", "Groucho Marx"], :order => 'clubs.name DESC') #force fallback + Member.scoped(:includes => :sponsor_club, :where => ["members.name = ?", "Groucho Marx"], :order => 'clubs.name DESC').all #force fallback end assert_equal 1, members.size assert_not_nil assert_no_queries { members[0].sponsor_club } @@ -197,7 +197,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase @member.member_detail = @member_detail @member.organization = @organization @member_details = assert_queries(3) do - MemberDetail.find(:all, :include => :member_type) + MemberDetail.scoped(:includes => :member_type).all end @new_detail = @member_details[0] assert @new_detail.send(:association, :member_type).loaded? @@ -210,14 +210,14 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase assert_nothing_raised do Club.find(@club.id).save! - Club.find(@club.id, :include => :sponsored_member).save! + Club.scoped(:includes => :sponsored_member).find(@club.id).save! end @club.sponsor.destroy assert_nothing_raised do Club.find(@club.id).save! - Club.find(@club.id, :include => :sponsored_member).save! + Club.scoped(:includes => :sponsored_member).find(@club.id).save! end end diff --git a/activerecord/test/cases/associations/identity_map_test.rb b/activerecord/test/cases/associations/identity_map_test.rb deleted file mode 100644 index 9b8635774c..0000000000 --- a/activerecord/test/cases/associations/identity_map_test.rb +++ /dev/null @@ -1,137 +0,0 @@ -require "cases/helper" -require 'models/author' -require 'models/post' - -if ActiveRecord::IdentityMap.enabled? -class InverseHasManyIdentityMapTest < ActiveRecord::TestCase - fixtures :authors, :posts - - def test_parent_instance_should_be_shared_with_every_child_on_find - m = Author.first - is = m.posts - is.each do |i| - assert_equal m.name, i.author.name, "Name of man should be the same before changes to parent instance" - m.name = 'Bongo' - assert_equal m.name, i.author.name, "Name of man should be the same after changes to parent instance" - i.author.name = 'Mungo' - assert_equal m.name, i.author.name, "Name of man should be the same after changes to child-owned instance" - end - end - - def test_parent_instance_should_be_shared_with_eager_loaded_children - m = Author.find(:first, :include => :posts) - is = m.posts - is.each do |i| - assert_equal m.name, i.author.name, "Name of man should be the same before changes to parent instance" - m.name = 'Bongo' - assert_equal m.name, i.author.name, "Name of man should be the same after changes to parent instance" - i.author.name = 'Mungo' - assert_equal m.name, i.author.name, "Name of man should be the same after changes to child-owned instance" - end - - m = Author.find(:first, :include => :posts, :order => 'posts.id') - is = m.posts - is.each do |i| - assert_equal m.name, i.author.name, "Name of man should be the same before changes to parent instance" - m.name = 'Bongo' - assert_equal m.name, i.author.name, "Name of man should be the same after changes to parent instance" - i.author.name = 'Mungo' - assert_equal m.name, i.author.name, "Name of man should be the same after changes to child-owned instance" - end - end - - def test_parent_instance_should_be_shared_with_newly_built_child - m = Author.first - i = m.posts.build(:title => 'Industrial Revolution Re-enactment', :body => 'Lorem ipsum') - assert_not_nil i.author - assert_equal m.name, i.author.name, "Name of man should be the same before changes to parent instance" - m.name = 'Bongo' - assert_equal m.name, i.author.name, "Name of man should be the same after changes to parent instance" - i.author.name = 'Mungo' - assert_equal m.name, i.author.name, "Name of man should be the same after changes to just-built-child-owned instance" - end - - def test_parent_instance_should_be_shared_with_newly_block_style_built_child - m = Author.first - i = m.posts.build {|ii| ii.title = 'Industrial Revolution Re-enactment'; ii.body = 'Lorem ipsum'} - assert_not_nil i.title, "Child attributes supplied to build via blocks should be populated" - assert_not_nil i.author - assert_equal m.name, i.author.name, "Name of man should be the same before changes to parent instance" - m.name = 'Bongo' - assert_equal m.name, i.author.name, "Name of man should be the same after changes to parent instance" - i.author.name = 'Mungo' - assert_equal m.name, i.author.name, "Name of man should be the same after changes to just-built-child-owned instance" - end - - def test_parent_instance_should_be_shared_with_newly_created_child - m = Author.first - i = m.posts.create(:title => 'Industrial Revolution Re-enactment', :body => 'Lorem ipsum') - assert_not_nil i.author - assert_equal m.name, i.author.name, "Name of man should be the same before changes to parent instance" - m.name = 'Bongo' - assert_equal m.name, i.author.name, "Name of man should be the same after changes to parent instance" - i.author.name = 'Mungo' - assert_equal m.name, i.author.name, "Name of man should be the same after changes to newly-created-child-owned instance" - end - - def test_parent_instance_should_be_shared_with_newly_created_via_bang_method_child - m = Author.first - i = m.posts.create!(:title => 'Industrial Revolution Re-enactment', :body => 'Lorem ipsum') - assert_not_nil i.author - assert_equal m.name, i.author.name, "Name of man should be the same before changes to parent instance" - m.name = 'Bongo' - assert_equal m.name, i.author.name, "Name of man should be the same after changes to parent instance" - i.author.name = 'Mungo' - assert_equal m.name, i.author.name, "Name of man should be the same after changes to newly-created-child-owned instance" - end - - def test_parent_instance_should_be_shared_with_newly_block_style_created_child - m = Author.first - i = m.posts.create {|ii| ii.title = 'Industrial Revolution Re-enactment'; ii.body = 'Lorem ipsum'} - assert_not_nil i.title, "Child attributes supplied to create via blocks should be populated" - assert_not_nil i.author - assert_equal m.name, i.author.name, "Name of man should be the same before changes to parent instance" - m.name = 'Bongo' - assert_equal m.name, i.author.name, "Name of man should be the same after changes to parent instance" - i.author.name = 'Mungo' - assert_equal m.name, i.author.name, "Name of man should be the same after changes to newly-created-child-owned instance" - end - - def test_parent_instance_should_be_shared_with_poked_in_child - m = Author.first - i = Post.create(:title => 'Industrial Revolution Re-enactment', :body => 'Lorem ipsum') - m.posts << i - assert_not_nil i.author - assert_equal m.name, i.author.name, "Name of man should be the same before changes to parent instance" - m.name = 'Bongo' - assert_equal m.name, i.author.name, "Name of man should be the same after changes to parent instance" - i.author.name = 'Mungo' - assert_equal m.name, i.author.name, "Name of man should be the same after changes to newly-created-child-owned instance" - end - - def test_parent_instance_should_be_shared_with_replaced_via_accessor_children - m = Author.first - i = Post.new(:title => 'Industrial Revolution Re-enactment', :body => 'Lorem ipsum') - m.posts = [i] - assert_same m, i.author - assert_not_nil i.author - assert_equal m.name, i.author.name, "Name of man should be the same before changes to parent instance" - m.name = 'Bongo' - assert_equal m.name, i.author.name, "Name of man should be the same after changes to parent instance" - i.author.name = 'Mungo' - assert_equal m.name, i.author.name, "Name of man should be the same after changes to replaced-child-owned instance" - end - - def test_parent_instance_should_be_shared_with_replaced_via_method_children - m = Author.first - i = Post.new(:title => 'Industrial Revolution Re-enactment', :body => 'Lorem ipsum') - m.posts = [i] - assert_not_nil i.author - assert_equal m.name, i.author.name, "Name of man should be the same before changes to parent instance" - m.name = 'Bongo' - assert_equal m.name, i.author.name, "Name of man should be the same after changes to parent instance" - i.author.name = 'Mungo' - assert_equal m.name, i.author.name, "Name of man should be the same after changes to replaced-child-owned instance" - 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 e5e9ca6131..1d61d5c474 100644 --- a/activerecord/test/cases/associations/inner_join_association_test.rb +++ b/activerecord/test/cases/associations/inner_join_association_test.rb @@ -67,22 +67,22 @@ class InnerJoinAssociationTest < ActiveRecord::TestCase def test_find_with_implicit_inner_joins_does_not_set_associations authors = Author.joins(:posts).select('authors.*') assert !authors.empty?, "expected authors to be non-empty" - assert authors.all? {|a| !a.send(:instance_variable_names).include?("@posts")}, "expected no authors to have the @posts association loaded" + assert authors.all? { |a| !a.instance_variable_defined?(:@posts) }, "expected no authors to have the @posts association loaded" end def test_count_honors_implicit_inner_joins real_count = Author.scoped.to_a.sum{|a| a.posts.count } - assert_equal real_count, Author.count(:joins => :posts), "plain inner join count should match the number of referenced posts records" + assert_equal real_count, Author.joins(:posts).count, "plain inner join count should match the number of referenced posts records" end def test_calculate_honors_implicit_inner_joins real_count = Author.scoped.to_a.sum{|a| a.posts.count } - assert_equal real_count, Author.calculate(:count, 'authors.id', :joins => :posts), "plain inner join count should match the number of referenced posts records" + assert_equal real_count, Author.joins(:posts).calculate(:count, 'authors.id'), "plain inner join count should match the number of referenced posts records" end def test_calculate_honors_implicit_inner_joins_and_distinct_and_conditions real_count = Author.scoped.to_a.select {|a| a.posts.any? {|p| p.title =~ /^Welcome/} }.length - authors_with_welcoming_post_titles = Author.calculate(:count, 'authors.id', :joins => :posts, :distinct => true, :conditions => "posts.title like 'Welcome%'") + authors_with_welcoming_post_titles = Author.scoped(:joins => :posts, :where => "posts.title like 'Welcome%'").calculate(:count, 'authors.id', :distinct => true) assert_equal real_count, authors_with_welcoming_post_titles, "inner join and conditions should have only returned authors posting titles starting with 'Welcome'" end diff --git a/activerecord/test/cases/associations/inverse_associations_test.rb b/activerecord/test/cases/associations/inverse_associations_test.rb index 76282213d8..f35ffb2994 100644 --- a/activerecord/test/cases/associations/inverse_associations_test.rb +++ b/activerecord/test/cases/associations/inverse_associations_test.rb @@ -96,7 +96,7 @@ class InverseHasOneTests < ActiveRecord::TestCase def test_parent_instance_should_be_shared_with_eager_loaded_child_on_find - m = Man.find(:first, :conditions => {:name => 'Gordon'}, :include => :face) + m = Man.scoped(:where => {:name => 'Gordon'}, :includes => :face).first f = m.face assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance" m.name = 'Bongo' @@ -104,7 +104,7 @@ class InverseHasOneTests < ActiveRecord::TestCase f.man.name = 'Mungo' assert_equal m.name, f.man.name, "Name of man should be the same after changes to child-owned instance" - m = Man.find(:first, :conditions => {:name => 'Gordon'}, :include => :face, :order => 'faces.id') + m = Man.scoped(:where => {:name => 'Gordon'}, :includes => :face, :order => 'faces.id').first f = m.face assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance" m.name = 'Bongo' @@ -114,7 +114,7 @@ class InverseHasOneTests < ActiveRecord::TestCase end def test_parent_instance_should_be_shared_with_newly_built_child - m = Man.find(:first) + m = Man.first f = m.build_face(:description => 'haunted') assert_not_nil f.man assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance" @@ -125,7 +125,7 @@ class InverseHasOneTests < ActiveRecord::TestCase end def test_parent_instance_should_be_shared_with_newly_created_child - m = Man.find(:first) + m = Man.first f = m.create_face(:description => 'haunted') assert_not_nil f.man assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance" @@ -136,7 +136,7 @@ class InverseHasOneTests < ActiveRecord::TestCase end def test_parent_instance_should_be_shared_with_newly_created_child_via_bang_method - m = Man.find(:first) + m = Man.first f = m.create_face!(:description => 'haunted') assert_not_nil f.man assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance" @@ -147,7 +147,7 @@ class InverseHasOneTests < ActiveRecord::TestCase end def test_parent_instance_should_be_shared_with_replaced_via_accessor_child - m = Man.find(:first) + m = Man.first f = Face.new(:description => 'haunted') m.face = f assert_not_nil f.man @@ -159,7 +159,7 @@ class InverseHasOneTests < ActiveRecord::TestCase end def test_trying_to_use_inverses_that_dont_exist_should_raise_an_error - assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Man.find(:first).dirty_face } + assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Man.first.dirty_face } end end @@ -179,7 +179,7 @@ class InverseHasManyTests < ActiveRecord::TestCase end def test_parent_instance_should_be_shared_with_eager_loaded_children - m = Man.find(:first, :conditions => {:name => 'Gordon'}, :include => :interests) + m = Man.scoped(:where => {:name => 'Gordon'}, :includes => :interests).first is = m.interests is.each do |i| assert_equal m.name, i.man.name, "Name of man should be the same before changes to parent instance" @@ -189,7 +189,7 @@ class InverseHasManyTests < ActiveRecord::TestCase assert_equal m.name, i.man.name, "Name of man should be the same after changes to child-owned instance" end - m = Man.find(:first, :conditions => {:name => 'Gordon'}, :include => :interests, :order => 'interests.id') + m = Man.scoped(:where => {:name => 'Gordon'}, :includes => :interests, :order => 'interests.id').first is = m.interests is.each do |i| assert_equal m.name, i.man.name, "Name of man should be the same before changes to parent instance" @@ -201,7 +201,7 @@ class InverseHasManyTests < ActiveRecord::TestCase end def test_parent_instance_should_be_shared_with_newly_block_style_built_child - m = Man.find(:first) + m = Man.first i = m.interests.build {|ii| ii.topic = 'Industrial Revolution Re-enactment'} assert_not_nil i.topic, "Child attributes supplied to build via blocks should be populated" assert_not_nil i.man @@ -213,7 +213,7 @@ class InverseHasManyTests < ActiveRecord::TestCase end def test_parent_instance_should_be_shared_with_newly_created_via_bang_method_child - m = Man.find(:first) + m = Man.first i = m.interests.create!(:topic => 'Industrial Revolution Re-enactment') assert_not_nil i.man assert_equal m.name, i.man.name, "Name of man should be the same before changes to parent instance" @@ -224,7 +224,7 @@ class InverseHasManyTests < ActiveRecord::TestCase end def test_parent_instance_should_be_shared_with_newly_block_style_created_child - m = Man.find(:first) + m = Man.first i = m.interests.create {|ii| ii.topic = 'Industrial Revolution Re-enactment'} assert_not_nil i.topic, "Child attributes supplied to create via blocks should be populated" assert_not_nil i.man @@ -248,7 +248,7 @@ class InverseHasManyTests < ActiveRecord::TestCase end def test_parent_instance_should_be_shared_with_replaced_via_accessor_children - m = Man.find(:first) + m = Man.first i = Interest.new(:topic => 'Industrial Revolution Re-enactment') m.interests = [i] assert_not_nil i.man @@ -260,7 +260,7 @@ class InverseHasManyTests < ActiveRecord::TestCase end def test_trying_to_use_inverses_that_dont_exist_should_raise_an_error - assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Man.find(:first).secret_interests } + assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Man.first.secret_interests } end end @@ -278,7 +278,7 @@ class InverseBelongsToTests < ActiveRecord::TestCase end def test_eager_loaded_child_instance_should_be_shared_with_parent_on_find - f = Face.find(:first, :include => :man, :conditions => {:description => 'trusting'}) + f = Face.scoped(:includes => :man, :where => {:description => 'trusting'}).first m = f.man assert_equal f.description, m.face.description, "Description of face should be the same before changes to child instance" f.description = 'gormless' @@ -286,7 +286,7 @@ class InverseBelongsToTests < ActiveRecord::TestCase m.face.description = 'pleasing' assert_equal f.description, m.face.description, "Description of face should be the same after changes to parent-owned instance" - f = Face.find(:first, :include => :man, :order => 'men.id', :conditions => {:description => 'trusting'}) + f = Face.scoped(:includes => :man, :order => 'men.id', :where => {:description => 'trusting'}).first m = f.man assert_equal f.description, m.face.description, "Description of face should be the same before changes to child instance" f.description = 'gormless' @@ -331,7 +331,7 @@ class InverseBelongsToTests < ActiveRecord::TestCase end def test_child_instance_should_be_shared_with_replaced_via_accessor_parent - f = Face.find(:first) + f = Face.first m = Man.new(:name => 'Charles') f.man = m assert_not_nil m.face @@ -343,7 +343,7 @@ class InverseBelongsToTests < ActiveRecord::TestCase end def test_trying_to_use_inverses_that_dont_exist_should_raise_an_error - assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Face.find(:first).horrible_man } + assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Face.first.horrible_man } end end @@ -351,7 +351,7 @@ class InversePolymorphicBelongsToTests < ActiveRecord::TestCase fixtures :men, :faces, :interests def test_child_instance_should_be_shared_with_parent_on_find - f = Face.find(:first, :conditions => {:description => 'confused'}) + f = Face.scoped(:where => {:description => 'confused'}).first m = f.polymorphic_man assert_equal f.description, m.polymorphic_face.description, "Description of face should be the same before changes to child instance" f.description = 'gormless' @@ -361,7 +361,7 @@ class InversePolymorphicBelongsToTests < ActiveRecord::TestCase end def test_eager_loaded_child_instance_should_be_shared_with_parent_on_find - f = Face.find(:first, :conditions => {:description => 'confused'}, :include => :man) + f = Face.scoped(:where => {:description => 'confused'}, :includes => :man).first m = f.polymorphic_man assert_equal f.description, m.polymorphic_face.description, "Description of face should be the same before changes to child instance" f.description = 'gormless' @@ -369,7 +369,7 @@ class InversePolymorphicBelongsToTests < ActiveRecord::TestCase m.polymorphic_face.description = 'pleasing' assert_equal f.description, m.polymorphic_face.description, "Description of face should be the same after changes to parent-owned instance" - f = Face.find(:first, :conditions => {:description => 'confused'}, :include => :man, :order => 'men.id') + f = Face.scoped(:where => {:description => 'confused'}, :includes => :man, :order => 'men.id').first m = f.polymorphic_man assert_equal f.description, m.polymorphic_face.description, "Description of face should be the same before changes to child instance" f.description = 'gormless' @@ -421,19 +421,19 @@ class InversePolymorphicBelongsToTests < ActiveRecord::TestCase def test_trying_to_access_inverses_that_dont_exist_shouldnt_raise_an_error # Ideally this would, if only for symmetry's sake with other association types - assert_nothing_raised(ActiveRecord::InverseOfAssociationNotFoundError) { Face.find(:first).horrible_polymorphic_man } + assert_nothing_raised(ActiveRecord::InverseOfAssociationNotFoundError) { Face.first.horrible_polymorphic_man } end def test_trying_to_set_polymorphic_inverses_that_dont_exist_at_all_should_raise_an_error # fails because no class has the correct inverse_of for horrible_polymorphic_man - assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Face.find(:first).horrible_polymorphic_man = Man.first } + assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Face.first.horrible_polymorphic_man = Man.first } end def test_trying_to_set_polymorphic_inverses_that_dont_exist_on_the_instance_being_set_should_raise_an_error # passes because Man does have the correct inverse_of - assert_nothing_raised(ActiveRecord::InverseOfAssociationNotFoundError) { Face.find(:first).polymorphic_man = Man.first } + assert_nothing_raised(ActiveRecord::InverseOfAssociationNotFoundError) { Face.first.polymorphic_man = Man.first } # fails because Interest does have the correct inverse_of - assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Face.find(:first).polymorphic_man = Interest.first } + assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Face.first.polymorphic_man = Interest.first } end end @@ -444,7 +444,7 @@ class InverseMultipleHasManyInversesForSameModel < ActiveRecord::TestCase def test_that_we_can_load_associations_that_have_the_same_reciprocal_name_from_different_models assert_nothing_raised(ActiveRecord::AssociationTypeMismatch) do - i = Interest.find(:first) + i = Interest.first i.zine i.man end @@ -452,7 +452,7 @@ class InverseMultipleHasManyInversesForSameModel < ActiveRecord::TestCase def test_that_we_can_create_associations_that_have_the_same_reciprocal_name_from_different_models assert_nothing_raised(ActiveRecord::AssociationTypeMismatch) do - i = Interest.find(:first) + i = Interest.first i.build_zine(:title => 'Get Some in Winter! 2008') i.build_man(:name => 'Gordon') i.save! diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb index 4ce8b85098..3606ce691c 100644 --- a/activerecord/test/cases/associations/join_model_test.rb +++ b/activerecord/test/cases/associations/join_model_test.rb @@ -46,12 +46,12 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase assert !authors(:mary).unique_categorized_posts.loaded? assert_queries(1) { assert_equal 1, author.unique_categorized_posts.count } assert_queries(1) { assert_equal 1, author.unique_categorized_posts.count(:title) } - assert_queries(1) { assert_equal 0, author.unique_categorized_posts.count(:title, :conditions => "title is NULL") } + assert_queries(1) { assert_equal 0, author.unique_categorized_posts.where(title: nil).count(:title) } assert !authors(:mary).unique_categorized_posts.loaded? end def test_has_many_uniq_through_find - assert_equal 1, authors(:mary).unique_categorized_posts.find(:all).size + assert_equal 1, authors(:mary).unique_categorized_posts.all.size end def test_has_many_uniq_through_dynamic_find @@ -71,7 +71,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_polymorphic_has_many_going_through_join_model_with_find - assert_equal tags(:general), tag = posts(:welcome).tags.find(:first) + assert_equal tags(:general), tag = posts(:welcome).tags.first assert_no_queries do tag.tagging end @@ -85,7 +85,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_polymorphic_has_many_going_through_join_model_with_include_on_source_reflection_with_find - assert_equal tags(:general), tag = posts(:welcome).funky_tags.find(:first) + assert_equal tags(:general), tag = posts(:welcome).funky_tags.first assert_no_queries do tag.tagging end @@ -237,8 +237,8 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_include_has_many_through - posts = Post.find(:all, :order => 'posts.id') - posts_with_authors = Post.find(:all, :include => :authors, :order => 'posts.id') + posts = Post.scoped(:order => 'posts.id').all + posts_with_authors = Post.scoped(:includes => :authors, :order => 'posts.id').all assert_equal posts.length, posts_with_authors.length posts.length.times do |i| assert_equal posts[i].authors.length, assert_no_queries { posts_with_authors[i].authors.length } @@ -246,7 +246,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_include_polymorphic_has_one - post = Post.find_by_id(posts(:welcome).id, :include => :tagging) + post = Post.includes(:tagging).find posts(:welcome).id tagging = taggings(:welcome_general) assert_no_queries do assert_equal tagging, post.tagging @@ -254,7 +254,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_include_polymorphic_has_one_defined_in_abstract_parent - item = Item.find_by_id(items(:dvd).id, :include => :tagging) + item = Item.includes(:tagging).find items(:dvd).id tagging = taggings(:godfather) assert_no_queries do assert_equal tagging, item.tagging @@ -262,8 +262,8 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_include_polymorphic_has_many_through - posts = Post.find(:all, :order => 'posts.id') - posts_with_tags = Post.find(:all, :include => :tags, :order => 'posts.id') + posts = Post.scoped(:order => 'posts.id').all + posts_with_tags = Post.scoped(:includes => :tags, :order => 'posts.id').all assert_equal posts.length, posts_with_tags.length posts.length.times do |i| assert_equal posts[i].tags.length, assert_no_queries { posts_with_tags[i].tags.length } @@ -271,8 +271,8 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_include_polymorphic_has_many - posts = Post.find(:all, :order => 'posts.id') - posts_with_taggings = Post.find(:all, :include => :taggings, :order => 'posts.id') + posts = Post.scoped(:order => 'posts.id').all + posts_with_taggings = Post.scoped(:includes => :taggings, :order => 'posts.id').all assert_equal posts.length, posts_with_taggings.length posts.length.times do |i| assert_equal posts[i].taggings.length, assert_no_queries { posts_with_taggings[i].taggings.length } @@ -280,20 +280,20 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_has_many_find_all - assert_equal [categories(:general)], authors(:david).categories.find(:all) + assert_equal [categories(:general)], authors(:david).categories.all end def test_has_many_find_first - assert_equal categories(:general), authors(:david).categories.find(:first) + assert_equal categories(:general), authors(:david).categories.first end def test_has_many_with_hash_conditions - assert_equal categories(:general), authors(:david).categories_like_general.find(:first) + assert_equal categories(:general), authors(:david).categories_like_general.first end def test_has_many_find_conditions - assert_equal categories(:general), authors(:david).categories.find(:first, :conditions => "categories.name = 'General'") - assert_nil authors(:david).categories.find(:first, :conditions => "categories.name = 'Technology'") + assert_equal categories(:general), authors(:david).categories.scoped(:where => "categories.name = 'General'").first + assert_nil authors(:david).categories.scoped(:where => "categories.name = 'Technology'").first end def test_has_many_class_methods_called_by_method_missing @@ -327,14 +327,6 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase assert_equal [authors(:david), authors(:bob)], posts(:thinking).authors_using_custom_pk.order('authors.id') end - def test_both_scoped_and_explicit_joins_should_be_respected - assert_nothing_raised do - Post.send(:with_scope, :find => {:joins => "left outer join comments on comments.id = posts.id"}) do - Post.find :all, :select => "comments.id, authors.id", :joins => "left outer join authors on authors.id = posts.author_id" - end - end - end - def test_belongs_to_polymorphic_with_counter_cache assert_equal 1, posts(:welcome)[:taggings_count] tagging = posts(:welcome).taggings.create(:tag => tags(:general)) @@ -362,7 +354,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end assert_raise ActiveRecord::EagerLoadPolymorphicError do - tags(:general).taggings.find(:all, :include => :taggable, :conditions => 'bogus_table.column = 1') + tags(:general).taggings.includes(:taggable).where('bogus_table.column = 1').references(:bogus_table).to_a end end @@ -372,7 +364,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_eager_has_many_polymorphic_with_source_type - tag_with_include = Tag.find(tags(:general).id, :include => :tagged_posts) + tag_with_include = Tag.scoped(:includes => :tagged_posts).find(tags(:general).id) desired = posts(:welcome, :thinking) assert_no_queries do # added sort by ID as otherwise test using JRuby was failing as array elements were in different order @@ -382,20 +374,20 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_has_many_through_has_many_find_all - assert_equal comments(:greetings), authors(:david).comments.find(:all, :order => 'comments.id').first + assert_equal comments(:greetings), authors(:david).comments.scoped(:order => 'comments.id').all.first end def test_has_many_through_has_many_find_all_with_custom_class - assert_equal comments(:greetings), authors(:david).funky_comments.find(:all, :order => 'comments.id').first + assert_equal comments(:greetings), authors(:david).funky_comments.scoped(:order => 'comments.id').all.first end def test_has_many_through_has_many_find_first - assert_equal comments(:greetings), authors(:david).comments.find(:first, :order => 'comments.id') + assert_equal comments(:greetings), authors(:david).comments.scoped(:order => 'comments.id').first end def test_has_many_through_has_many_find_conditions - options = { :conditions => "comments.#{QUOTED_TYPE}='SpecialComment'", :order => 'comments.id' } - assert_equal comments(:does_it_hurt), authors(:david).comments.find(:first, options) + options = { :where => "comments.#{QUOTED_TYPE}='SpecialComment'", :order => 'comments.id' } + assert_equal comments(:does_it_hurt), authors(:david).comments.scoped(options).first end def test_has_many_through_has_many_find_by_id @@ -411,7 +403,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_include_has_many_through_polymorphic_has_many - author = Author.find_by_id(authors(:david).id, :include => :taggings) + author = Author.includes(:taggings).find authors(:david).id expected_taggings = taggings(:welcome_general, :thinking_general) assert_no_queries do assert_equal expected_taggings, author.taggings.uniq.sort_by { |t| t.id } @@ -419,7 +411,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_eager_load_has_many_through_has_many - author = Author.find :first, :conditions => ['name = ?', 'David'], :include => :comments, :order => 'comments.id' + author = Author.scoped(:where => ['name = ?', 'David'], :includes => :comments, :order => 'comments.id').first SpecialComment.new; VerySpecialComment.new assert_no_queries do assert_equal [1,2,3,5,6,7,8,9,10,12], author.comments.collect(&:id) @@ -427,7 +419,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_eager_load_has_many_through_has_many_with_conditions - post = Post.find(:first, :include => :invalid_tags) + post = Post.scoped(:includes => :invalid_tags).first assert_no_queries do post.invalid_tags end @@ -435,8 +427,8 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase def test_eager_belongs_to_and_has_one_not_singularized assert_nothing_raised do - Author.find(:first, :include => :author_address) - AuthorAddress.find(:first, :include => :author) + Author.scoped(:includes => :author_address).first + AuthorAddress.scoped(:includes => :author).first end end @@ -452,7 +444,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_has_many_through_uses_conditions_specified_on_the_has_many_association - author = Author.find(:first) + author = Author.first assert_present author.comments assert_blank author.nonexistant_comments end @@ -622,7 +614,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase def test_polymorphic_has_many expected = taggings(:welcome_general) - p = Post.find(posts(:welcome).id, :include => :taggings) + p = Post.scoped(:includes => :taggings).find(posts(:welcome).id) assert_no_queries {assert p.taggings.include?(expected)} assert posts(:welcome).taggings.include?(taggings(:welcome_general)) end @@ -630,18 +622,18 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase def test_polymorphic_has_one expected = posts(:welcome) - tagging = Tagging.find(taggings(:welcome_general).id, :include => :taggable) + tagging = Tagging.scoped(:includes => :taggable).find(taggings(:welcome_general).id) assert_no_queries { assert_equal expected, tagging.taggable} end def test_polymorphic_belongs_to - p = Post.find(posts(:welcome).id, :include => {:taggings => :taggable}) + p = Post.scoped(:includes => {:taggings => :taggable}).find(posts(:welcome).id) assert_no_queries {assert_equal posts(:welcome), p.taggings.first.taggable} end def test_preload_polymorphic_has_many_through - posts = Post.find(:all, :order => 'posts.id') - posts_with_tags = Post.find(:all, :include => :tags, :order => 'posts.id') + posts = Post.scoped(:order => 'posts.id').all + posts_with_tags = Post.scoped(:includes => :tags, :order => 'posts.id').all assert_equal posts.length, posts_with_tags.length posts.length.times do |i| assert_equal posts[i].tags.length, assert_no_queries { posts_with_tags[i].tags.length } @@ -649,7 +641,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_preload_polymorph_many_types - taggings = Tagging.find :all, :include => :taggable, :conditions => ['taggable_type != ?', 'FakeModel'] + taggings = Tagging.scoped(:includes => :taggable, :where => ['taggable_type != ?', 'FakeModel']).all assert_no_queries do taggings.first.taggable.id taggings[1].taggable.id @@ -662,13 +654,13 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase def test_preload_nil_polymorphic_belongs_to assert_nothing_raised do - Tagging.find(:all, :include => :taggable, :conditions => ['taggable_type IS NULL']) + Tagging.scoped(:includes => :taggable, :where => ['taggable_type IS NULL']).all end end def test_preload_polymorphic_has_many - posts = Post.find(:all, :order => 'posts.id') - posts_with_taggings = Post.find(:all, :include => :taggings, :order => 'posts.id') + posts = Post.scoped(:order => 'posts.id').all + posts_with_taggings = Post.scoped(:includes => :taggings, :order => 'posts.id').all assert_equal posts.length, posts_with_taggings.length posts.length.times do |i| assert_equal posts[i].taggings.length, assert_no_queries { posts_with_taggings[i].taggings.length } @@ -676,7 +668,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_belongs_to_shared_parent - comments = Comment.find(:all, :include => :post, :conditions => 'post_id = 1') + comments = Comment.scoped(:includes => :post, :where => 'post_id = 1').all assert_no_queries do assert_equal comments.first.post, comments[1].post end @@ -733,7 +725,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase class_name = "PostWith#{association.to_s.classify}#{dependency.to_s.classify}" Post.find(post_id).update_column :type, class_name klass = Object.const_set(class_name, Class.new(ActiveRecord::Base)) - klass.set_table_name 'posts' + klass.table_name = 'posts' klass.send(association, association_name, :as => :taggable, :dependent => dependency) klass.find(post_id) end diff --git a/activerecord/test/cases/associations/nested_through_associations_test.rb b/activerecord/test/cases/associations/nested_through_associations_test.rb index 530f5212a2..03d99d19f6 100644 --- a/activerecord/test/cases/associations/nested_through_associations_test.rb +++ b/activerecord/test/cases/associations/nested_through_associations_test.rb @@ -505,7 +505,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase def test_nested_has_many_through_with_conditions_on_through_associations_preload_via_joins # Pointless condition to force single-query loading assert_includes_and_joins_equal( - Author.where('tags.id = tags.id'), + Author.where('tags.id = tags.id').references(:tags), [authors(:bob)], :misc_post_first_blue_tags ) end @@ -526,7 +526,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase def test_nested_has_many_through_with_conditions_on_source_associations_preload_via_joins # Pointless condition to force single-query loading assert_includes_and_joins_equal( - Author.where('tags.id = tags.id'), + Author.where('tags.id = tags.id').references(:tags), [authors(:bob)], :misc_post_first_blue_tags_2 ) end @@ -545,6 +545,15 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase assert_equal [organizations(:nsa)], organizations end + def test_nested_has_many_through_should_not_be_autosaved + c = Categorization.new + c.author = authors(:david) + c.post_taggings.to_a + assert !c.post_taggings.empty? + c.save + assert !c.post_taggings.empty? + end + private def assert_includes_and_joins_equal(query, expected, association) diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb index ffe2993e0f..1d0550afaf 100644 --- a/activerecord/test/cases/associations_test.rb +++ b/activerecord/test/cases/associations_test.rb @@ -1,4 +1,5 @@ require "cases/helper" +require 'models/computer' require 'models/developer' require 'models/project' require 'models/company' @@ -28,7 +29,7 @@ class AssociationsTest < ActiveRecord::TestCase molecule.electrons.create(:name => 'electron_1') molecule.electrons.create(:name => 'electron_2') - liquids = Liquid.includes(:molecules => :electrons).where('molecules.id is not null') + liquids = Liquid.includes(:molecules => :electrons).references(:molecules).where('molecules.id is not null') assert_equal 1, liquids[0].molecules.length end @@ -73,8 +74,8 @@ class AssociationsTest < ActiveRecord::TestCase def test_include_with_order_works - assert_nothing_raised {Account.find(:first, :order => 'id', :include => :firm)} - assert_nothing_raised {Account.find(:first, :order => :id, :include => :firm)} + assert_nothing_raised {Account.scoped(:order => 'id', :includes => :firm).first} + assert_nothing_raised {Account.scoped(:order => :id, :includes => :firm).first} end def test_bad_collection_keys @@ -128,6 +129,11 @@ class AssociationsTest < ActiveRecord::TestCase end end + def test_association_with_references + firm = companies(:first_firm) + assert_equal ['foo'], firm.association_with_references.scoped.references_values + end + end class AssociationProxyTest < ActiveRecord::TestCase @@ -208,6 +214,10 @@ class AssociationProxyTest < ActiveRecord::TestCase david = developers(:david) assert_equal david.association(:projects), david.projects.proxy_association end + + def test_scoped_allows_conditions + assert developers(:david).projects.scoped(where: 'foo').where_values.include?('foo') + end end class OverridingAssociationsTest < ActiveRecord::TestCase @@ -273,3 +283,18 @@ class OverridingAssociationsTest < ActiveRecord::TestCase ) end end + +class GeneratedMethodsTest < ActiveRecord::TestCase + fixtures :developers, :computers, :posts, :comments + def test_association_methods_override_attribute_methods_of_same_name + assert_equal(developers(:david), computers(:workstation).developer) + # this next line will fail if the attribute methods module is generated lazily + # after the association methods module is generated + assert_equal(developers(:david), computers(:workstation).developer) + assert_equal(developers(:david).id, computers(:workstation)[:developer]) + end + + def test_model_method_overrides_association_method + assert_equal(comments(:greetings).body, posts(:welcome).first_comment) + end +end diff --git a/activerecord/test/cases/attribute_methods/read_test.rb b/activerecord/test/cases/attribute_methods/read_test.rb index e03ed33591..98c38535a6 100644 --- a/activerecord/test/cases/attribute_methods/read_test.rb +++ b/activerecord/test/cases/attribute_methods/read_test.rb @@ -1,21 +1,28 @@ require "cases/helper" require 'active_support/core_ext/object/inclusion' +require 'thread' module ActiveRecord module AttributeMethods class ReadTest < ActiveRecord::TestCase class FakeColumn < Struct.new(:name) - def type_cast_code(var) - var - end - def type; :integer; end end def setup @klass = Class.new do + def self.superclass; Base; end + def self.active_record_super; Base; end + def self.base_class; self; end + include ActiveRecord::AttributeMethods - include ActiveRecord::AttributeMethods::Read + + def self.define_attribute_methods + # Created in the inherited/included hook for "proper" ARs + @attribute_methods_mutex ||= Mutex.new + + super + end def self.column_names %w{ one two three } @@ -33,9 +40,6 @@ module ActiveRecord [name, FakeColumn.new(name)] }] end - - def self.serialized_attributes; {}; end - def self.base_class; self; end end end diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index 9300c57819..1093fedea1 100644 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -30,9 +30,12 @@ class AttributeMethodsTest < ActiveRecord::TestCase t = Topic.new t.title = "hello there!" t.written_on = Time.now + t.author_name = "" assert t.attribute_present?("title") assert t.attribute_present?("written_on") assert !t.attribute_present?("content") + assert !t.attribute_present?("author_name") + end def test_attribute_present_with_booleans @@ -53,6 +56,12 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert Boolean.find(b4.id).attribute_present?(:value) end + def test_caching_nil_primary_key + klass = Class.new(Minimalistic) + klass.expects(:reset_primary_key).returns(nil).once + 2.times { klass.primary_key } + end + def test_attribute_keys_on_new_instance t = Topic.new assert_equal nil, t.title, "The topics table has a title column, so it should be nil" @@ -96,7 +105,6 @@ class AttributeMethodsTest < ActiveRecord::TestCase def test_respond_to? topic = Topic.find(1) assert_respond_to topic, "title" - assert_respond_to topic, "_title" assert_respond_to topic, "title?" assert_respond_to topic, "title=" assert_respond_to topic, :title @@ -113,9 +121,15 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert_not_nil keyboard.key_number assert_equal keyboard.key_number, keyboard.id assert keyboard.respond_to?('key_number') - assert keyboard.respond_to?('_key_number') assert keyboard.respond_to?('id') - assert keyboard.respond_to?('_id') + end + + def test_id_before_type_cast_with_custom_primary_key + keyboard = Keyboard.create + keyboard.key_number = '10' + assert_equal '10', keyboard.id_before_type_cast + assert_equal nil, keyboard.read_attribute_before_type_cast('id') + assert_equal '10', keyboard.read_attribute_before_type_cast('key_number') end # Syck calls respond_to? before actually calling initialize @@ -230,7 +244,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase # DB2 is not case-sensitive return true if current_adapter?(:DB2Adapter) - assert_equal @loaded_fixtures['computers']['workstation'].to_hash, Computer.find(:first).attributes + assert_equal @loaded_fixtures['computers']['workstation'].to_hash, Computer.first.attributes end def test_hashes_not_mangled @@ -255,8 +269,14 @@ class AttributeMethodsTest < ActiveRecord::TestCase topic.send(:write_attribute, :title, "Still another topic") assert_equal "Still another topic", topic.title - topic.send(:write_attribute, "title", "Still another topic: part 2") + topic[:title] = "Still another topic: part 2" assert_equal "Still another topic: part 2", topic.title + + topic.send(:write_attribute, "title", "Still another topic: part 3") + assert_equal "Still another topic: part 3", topic.title + + topic["title"] = "Still another topic: part 4" + assert_equal "Still another topic: part 4", topic.title end def test_read_attribute @@ -309,6 +329,39 @@ class AttributeMethodsTest < ActiveRecord::TestCase # puts "" end + def test_overridden_write_attribute + topic = Topic.new + def topic.write_attribute(attr_name, value) + super(attr_name, value.downcase) + end + + topic.send(:write_attribute, :title, "Yet another topic") + assert_equal "yet another topic", topic.title + + topic[:title] = "Yet another topic: part 2" + assert_equal "yet another topic: part 2", topic.title + + topic.send(:write_attribute, "title", "Yet another topic: part 3") + assert_equal "yet another topic: part 3", topic.title + + topic["title"] = "Yet another topic: part 4" + assert_equal "yet another topic: part 4", topic.title + end + + def test_overridden_read_attribute + topic = Topic.new + topic.title = "Stop changing the topic" + def topic.read_attribute(attr_name) + super(attr_name).upcase + end + + assert_equal "STOP CHANGING THE TOPIC", topic.send(:read_attribute, "title") + assert_equal "STOP CHANGING THE TOPIC", topic["title"] + + assert_equal "STOP CHANGING THE TOPIC", topic.send(:read_attribute, :title) + assert_equal "STOP CHANGING THE TOPIC", topic[:title] + end + def test_read_overridden_attribute topic = Topic.new(:title => 'a') def topic.title() 'b' end @@ -428,23 +481,23 @@ class AttributeMethodsTest < ActiveRecord::TestCase end def test_typecast_attribute_from_select_to_false - topic = Topic.create(:title => 'Budget') + Topic.create(:title => 'Budget') # Oracle does not support boolean expressions in SELECT if current_adapter?(:OracleAdapter) - topic = Topic.find(:first, :select => "topics.*, 0 as is_test") + topic = Topic.scoped(:select => "topics.*, 0 as is_test").first else - topic = Topic.find(:first, :select => "topics.*, 1=2 as is_test") + topic = Topic.scoped(:select => "topics.*, 1=2 as is_test").first end assert !topic.is_test? end def test_typecast_attribute_from_select_to_true - topic = Topic.create(:title => 'Budget') + Topic.create(:title => 'Budget') # Oracle does not support boolean expressions in SELECT if current_adapter?(:OracleAdapter) - topic = Topic.find(:first, :select => "topics.*, 1 as is_test") + topic = Topic.scoped(:select => "topics.*, 1 as is_test").first else - topic = Topic.find(:first, :select => "topics.*, 2=2 as is_test") + topic = Topic.scoped(:select => "topics.*, 2=2 as is_test").first end assert topic.is_test? end @@ -507,6 +560,14 @@ class AttributeMethodsTest < ActiveRecord::TestCase end end + def test_write_time_to_date_attributes + in_time_zone "Pacific Time (US & Canada)" do + record = @target.new + record.last_read = Time.utc(2010, 1, 1, 10) + assert_equal Date.civil(2010, 1, 1), record.last_read + end + end + def test_time_attributes_are_retrieved_in_current_time_zone in_time_zone "Pacific Time (US & Canada)" do utc_time = Time.utc(2008, 1, 1) @@ -542,6 +603,17 @@ class AttributeMethodsTest < ActiveRecord::TestCase end end + def test_setting_time_zone_aware_read_attribute + utc_time = Time.utc(2008, 1, 1) + cst_time = utc_time.in_time_zone("Central Time (US & Canada)") + in_time_zone "Pacific Time (US & Canada)" do + record = @target.create(:written_on => cst_time).reload + assert_equal utc_time, record[:written_on] + assert_equal ActiveSupport::TimeZone["Pacific Time (US & Canada)"], record[:written_on].time_zone + assert_equal Time.utc(2007, 12, 31, 16), record[:written_on].time + end + end + def test_setting_time_zone_aware_attribute_with_string utc_time = Time.utc(2008, 1, 1) (-11..13).each do |timezone_offset| @@ -556,11 +628,22 @@ class AttributeMethodsTest < ActiveRecord::TestCase end end + def test_time_zone_aware_attribute_saved + in_time_zone 1 do + record = @target.create(:written_on => '2012-02-20 10:00') + + record.written_on = '2012-02-20 09:00' + record.save + assert_equal Time.zone.local(2012, 02, 20, 9), record.reload.written_on + end + end + def test_setting_time_zone_aware_attribute_to_blank_string_returns_nil in_time_zone "Pacific Time (US & Canada)" do record = @target.new record.written_on = ' ' assert_nil record.written_on + assert_nil record[:written_on] end end @@ -675,7 +758,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase topic = Topic.new(:id => 5) topic.id = 5 - topic.method(:id).owner.send(:remove_method, :id) + topic.method(:id).owner.send(:undef_method, :id) assert_deprecated do assert_equal 5, topic.id @@ -684,17 +767,39 @@ class AttributeMethodsTest < ActiveRecord::TestCase Topic.undefine_attribute_methods end + def test_read_attribute_with_nil_should_not_asplode + assert_equal nil, Topic.new.read_attribute(nil) + end + + # If B < A, and A defines an accessor for 'foo', we don't want to override + # 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" + self.abstract_class = true + def title; "omg"; end + def title=(val); self.author_name = val; end + end + subklass = Class.new(klass) + [klass, subklass].each(&:define_attribute_methods) + + topic = subklass.find(1) + assert_equal "omg", topic.title + + topic.title = "lol" + assert_equal "lol", topic.author_name + end + private def cached_columns - @cached_columns ||= (time_related_columns_on_topic + serialized_columns_on_topic).map(&:name) + Topic.columns.find_all { |column| + !Topic.serialized_attributes.include? column.name + }.map(&:name) end def time_related_columns_on_topic - Topic.columns.select { |c| c.type.in?([:time, :date, :datetime, :timestamp]) } - end - - def serialized_columns_on_topic - Topic.columns.select { |c| Topic.serialized_attributes.include?(c.name) } + Topic.columns.select { |c| [:time, :date, :datetime, :timestamp].include?(c.type) } end def in_time_zone(zone) diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb index 4ad2cdfc7e..c93c91803e 100644 --- a/activerecord/test/cases/autosave_association_test.rb +++ b/activerecord/test/cases/autosave_association_test.rb @@ -87,7 +87,7 @@ class TestDefaultAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCas end def test_save_fails_for_invalid_has_one - firm = Firm.find(:first) + firm = Firm.first assert firm.valid? firm.build_account @@ -99,7 +99,7 @@ class TestDefaultAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCas end def test_save_succeeds_for_invalid_has_one_with_validate_false - firm = Firm.find(:first) + firm = Firm.first assert firm.valid? firm.build_unvalidated_account @@ -155,20 +155,20 @@ class TestDefaultAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCas end def test_not_resaved_when_unchanged - firm = Firm.find(:first, :include => :account) + firm = Firm.scoped(:includes => :account).first firm.name += '-changed' assert_queries(1) { firm.save! } - firm = Firm.find(:first) - firm.account = Account.find(:first) + firm = Firm.first + firm.account = Account.first assert_queries(Firm.partial_updates? ? 0 : 1) { firm.save! } - firm = Firm.find(:first).dup - firm.account = Account.find(:first) + firm = Firm.first.dup + firm.account = Account.first assert_queries(2) { firm.save! } - firm = Firm.find(:first).dup - firm.account = Account.find(:first).dup + firm = Firm.first.dup + firm.account = Account.first.dup assert_queries(2) { firm.save! } end @@ -228,7 +228,7 @@ class TestDefaultAutosaveAssociationOnABelongsToAssociation < ActiveRecord::Test end def test_assignment_before_parent_saved - client = Client.find(:first) + client = Client.first apple = Firm.new("name" => "Apple") client.firm = apple assert_equal apple, client.firm @@ -342,11 +342,22 @@ class TestDefaultAutosaveAssociationOnABelongsToAssociation < ActiveRecord::Test end def test_build_and_then_save_parent_should_not_reload_target - client = Client.find(:first) + client = Client.first apple = client.build_firm(:name => "Apple") client.save! assert_no_queries { assert_equal apple, client.firm } end + + def test_validation_does_not_validate_stale_association_target + valid_developer = Developer.create!(:name => "Dude", :salary => 50_000) + invalid_developer = Developer.new() + + auditlog = AuditLog.new(:message => "foo") + auditlog.developer = invalid_developer + auditlog.developer_id = valid_developer.id + + assert auditlog.valid? + end end class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCase @@ -373,7 +384,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa end def test_invalid_adding_with_validate_false - firm = Firm.find(:first) + firm = Firm.first client = Client.new firm.unvalidated_clients_of_firm << client @@ -386,7 +397,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa def test_valid_adding_with_validate_false no_of_clients = Client.count - firm = Firm.find(:first) + firm = Firm.first client = Client.new("name" => "Apple") assert firm.valid? @@ -575,6 +586,7 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase self.use_transactional_fixtures = false unless supports_savepoints? def setup + super @pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?") @ship = @pirate.create_ship(:name => 'Nights Dirty Lightning') end @@ -615,7 +627,9 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase def test_a_child_marked_for_destruction_should_not_be_destroyed_twice @pirate.ship.mark_for_destruction assert @pirate.save - @pirate.ship.expects(:destroy).never + class << @pirate.ship + def destroy; raise "Should not be called" end + end assert @pirate.save end @@ -660,7 +674,9 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase def test_a_parent_marked_for_destruction_should_not_be_destroyed_twice @ship.pirate.mark_for_destruction assert @ship.save - @ship.pirate.expects(:destroy).never + class << @ship.pirate + def destroy; raise "Should not be called" end + end assert @ship.save end @@ -755,6 +771,16 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase assert_equal before, @pirate.reload.birds end + def test_when_new_record_a_child_marked_for_destruction_should_not_affect_other_records_from_saving + @pirate = @ship.build_pirate(:catchphrase => "Arr' now I shall keep me eye on you matey!") # new record + + 3.times { |i| @pirate.birds.build(:name => "birds_#{i}") } + @pirate.birds[1].mark_for_destruction + @pirate.save! + + assert_equal 2, @pirate.birds.reload.length + end + # Add and remove callbacks tests for association collections. %w{ method proc }.each do |callback_type| define_method("test_should_run_add_callback_#{callback_type}s_for_has_many") do @@ -897,6 +923,7 @@ class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase self.use_transactional_fixtures = false unless supports_savepoints? def setup + super @pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?") @ship = @pirate.create_ship(:name => 'Nights Dirty Lightning') end @@ -965,10 +992,7 @@ class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase values = [@pirate.reload.catchphrase, @pirate.ship.name, *@pirate.ship.parts.map(&:name)] # Oracle saves empty string as NULL if current_adapter?(:OracleAdapter) - expected = ActiveRecord::IdentityMap.enabled? ? - [nil, nil, '', ''] : - [nil, nil, nil, nil] - assert_equal expected, values + assert_equal [nil, nil, nil, nil], values else assert_equal ['', '', '', ''], values end @@ -1020,6 +1044,7 @@ class TestAutosaveAssociationOnABelongsToAssociation < ActiveRecord::TestCase self.use_transactional_fixtures = false unless supports_savepoints? def setup + super @ship = Ship.create(:name => 'Nights Dirty Lightning') @pirate = @ship.create_pirate(:catchphrase => "Don' botharrr talkin' like one, savvy?") end @@ -1063,8 +1088,7 @@ class TestAutosaveAssociationOnABelongsToAssociation < ActiveRecord::TestCase @ship.save(:validate => false) # Oracle saves empty string as NULL if current_adapter?(:OracleAdapter) - expected = ActiveRecord::IdentityMap.enabled? ? [nil, ''] : [nil, nil] - assert_equal expected, [@ship.reload.name, @ship.pirate.catchphrase] + assert_equal [nil, nil], [@ship.reload.name, @ship.pirate.catchphrase] else assert_equal ['', ''], [@ship.reload.name, @ship.pirate.catchphrase] end @@ -1268,6 +1292,7 @@ class TestAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCase self.use_transactional_fixtures = false unless supports_savepoints? def setup + super @association_name = :birds @pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?") @@ -1282,6 +1307,7 @@ class TestAutosaveAssociationOnAHasAndBelongsToManyAssociation < ActiveRecord::T self.use_transactional_fixtures = false unless supports_savepoints? def setup + super @association_name = :parrots @habtm = true @@ -1297,6 +1323,7 @@ class TestAutosaveAssociationValidationsOnAHasManyAssociation < ActiveRecord::Te self.use_transactional_fixtures = false unless supports_savepoints? def setup + super @pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?") @pirate.birds.create(:name => 'cookoo') end @@ -1313,6 +1340,7 @@ class TestAutosaveAssociationValidationsOnAHasOneAssociation < ActiveRecord::Tes self.use_transactional_fixtures = false unless supports_savepoints? def setup + super @pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?") @pirate.create_ship(:name => 'titanic') super @@ -1335,6 +1363,7 @@ class TestAutosaveAssociationValidationsOnABelongsToAssociation < ActiveRecord:: self.use_transactional_fixtures = false unless supports_savepoints? def setup + super @pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?") end @@ -1355,6 +1384,7 @@ class TestAutosaveAssociationValidationsOnAHABTMAssociation < ActiveRecord::Test self.use_transactional_fixtures = false unless supports_savepoints? def setup + super @pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?") end @@ -1377,6 +1407,7 @@ class TestAutosaveAssociationValidationMethodsGeneration < ActiveRecord::TestCas self.use_transactional_fixtures = false unless supports_savepoints? def setup + super @pirate = Pirate.new end diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index 997c9e7e9d..35e0045e4c 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -23,10 +23,18 @@ require 'models/edge' require 'models/joke' require 'models/bulb' require 'models/bird' +require 'models/teapot' require 'rexml/document' require 'active_support/core_ext/exception' require 'bcrypt' +class FirstAbstractClass < ActiveRecord::Base + self.abstract_class = true +end +class SecondAbstractClass < FirstAbstractClass + self.abstract_class = true +end +class Photo < SecondAbstractClass; end class Category < ActiveRecord::Base; end class Categorization < ActiveRecord::Base; end class Smarts < ActiveRecord::Base; end @@ -54,7 +62,11 @@ end class Weird < ActiveRecord::Base; end -class Boolean < ActiveRecord::Base; end +class Boolean < ActiveRecord::Base + def has_fun + super + end +end class LintTest < ActiveRecord::TestCase include ActiveModel::Lint::Tests @@ -69,6 +81,16 @@ end class BasicsTest < ActiveRecord::TestCase fixtures :topics, :companies, :developers, :projects, :computers, :accounts, :minimalistics, 'warehouse-things', :authors, :categorizations, :categories, :posts + def test_generated_methods_modules + modules = Computer.ancestors + assert modules.include?(Computer::GeneratedFeatureMethods) + assert_equal(Computer::GeneratedFeatureMethods, Computer.generated_feature_methods) + assert(modules.index(Computer.generated_attribute_methods) > modules.index(Computer.generated_feature_methods), + "generated_attribute_methods must be higher in inheritance hierarchy than generated_feature_methods") + assert_not_equal Computer.generated_feature_methods, Post.generated_feature_methods + assert(modules.index(Computer.generated_attribute_methods) < modules.index(ActiveRecord::Base.ancestors[1])) + end + def test_column_names_are_escaped conn = ActiveRecord::Base.connection classname = conn.class.name[/[^:]*$/] @@ -103,20 +125,13 @@ class BasicsTest < ActiveRecord::TestCase unless current_adapter?(:PostgreSQLAdapter,:OracleAdapter,:SQLServerAdapter) def test_limit_with_comma - assert_nothing_raised do - Topic.limit("1,2").all - end + assert Topic.limit("1,2").all end end def test_limit_without_comma - assert_nothing_raised do - assert_equal 1, Topic.limit("1").all.length - end - - assert_nothing_raised do - assert_equal 1, Topic.limit(1).all.length - end + assert_equal 1, Topic.limit("1").all.length + assert_equal 1, Topic.limit(1).all.length end def test_invalid_limit @@ -145,7 +160,7 @@ class BasicsTest < ActiveRecord::TestCase def test_select_symbol topic_ids = Topic.select(:id).map(&:id).sort - assert_equal Topic.all.map(&:id).sort, topic_ids + assert_equal Topic.pluck(:id).sort, topic_ids end def test_table_exists @@ -153,6 +168,24 @@ class BasicsTest < ActiveRecord::TestCase assert Topic.table_exists? end + def test_finder_block + t = Topic.first + found = nil + Topic.find_by_id(t.id) { |f| found = f } + assert_equal t, found + end + + def test_finder_block_nothing_found + bad_id = Topic.maximum(:id) + 1 + assert_nil Topic.find_by_id(bad_id) { |f| raise } + end + + def test_find_returns_block_value + t = Topic.first + x = Topic.find_by_id(t.id) { |f| "hi mom!" } + assert_equal "hi mom!", x + end + def test_preserving_date_objects if current_adapter?(:SybaseAdapter) # Sybase ctlib does not (yet?) support the date type; use datetime instead. @@ -169,6 +202,31 @@ class BasicsTest < ActiveRecord::TestCase end end + def test_previously_changed + topic = Topic.first + topic.title = '<3<3<3' + assert_equal({}, topic.previous_changes) + + topic.save! + expected = ["The First Topic", "<3<3<3"] + assert_equal(expected, topic.previous_changes['title']) + end + + def test_previously_changed_dup + topic = Topic.first + topic.title = '<3<3<3' + topic.save! + + t2 = topic.dup + + assert_equal(topic.previous_changes, t2.previous_changes) + + topic.title = "lolwut" + topic.save! + + assert_not_equal(topic.previous_changes, t2.previous_changes) + end + def test_preserving_time_objects assert_kind_of( Time, Topic.find(1).bonus_time, @@ -181,7 +239,7 @@ class BasicsTest < ActiveRecord::TestCase ) # For adapters which support microsecond resolution. - if current_adapter?(:PostgreSQLAdapter) || current_adapter?(:SQLiteAdapter) + if current_adapter?(:PostgreSQLAdapter) || current_adapter?(:SQLite3Adapter) 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 @@ -303,13 +361,13 @@ class BasicsTest < ActiveRecord::TestCase end def test_load - topics = Topic.find(:all, :order => 'id') + topics = Topic.scoped(:order => 'id').all assert_equal(4, topics.size) assert_equal(topics(:first).title, topics.first.title) end def test_load_with_condition - topics = Topic.find(:all, :conditions => "author_name = 'Mary'") + topics = Topic.scoped(:where => "author_name = 'Mary'").all assert_equal(1, topics.size) assert_equal(topics(:second).title, topics.first.title) @@ -431,7 +489,7 @@ class BasicsTest < ActiveRecord::TestCase if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter) def test_update_all_with_order_and_limit - assert_equal 1, Topic.update_all("content = 'bulk updated!'", nil, :limit => 1, :order => 'id DESC') + assert_equal 1, Topic.limit(1).order('id DESC').update_all(:content => 'bulk updated!') end end @@ -561,10 +619,12 @@ class BasicsTest < ActiveRecord::TestCase weird = Weird.create('a$b' => 'value') weird.reload assert_equal 'value', weird.send('a$b') + assert_equal 'value', weird.read_attribute('a$b') weird.update_column('a$b', 'value2') weird.reload assert_equal 'value2', weird.send('a$b') + assert_equal 'value2', weird.read_attribute('a$b') end def test_multiparameter_attributes_on_date @@ -644,7 +704,7 @@ class BasicsTest < ActiveRecord::TestCase } topic = Topic.find(1) topic.attributes = attributes - assert_equal Time.local(2004, 6, 24, 16, 24, 0), topic.written_on + assert_equal Time.utc(2004, 6, 24, 16, 24, 0), topic.written_on end def test_multiparameter_attributes_on_time_with_no_date @@ -893,7 +953,7 @@ class BasicsTest < ActiveRecord::TestCase } topic = Topic.find(1) topic.attributes = attributes - assert_equal Time.local(2000, 1, 1, 5, 42, 0), topic.bonus_time + assert_equal Time.utc(2000, 1, 1, 5, 42, 0), topic.bonus_time end def test_boolean @@ -912,6 +972,16 @@ class BasicsTest < ActiveRecord::TestCase assert b_true.value? end + def test_boolean_without_questionmark + b_true = Boolean.create({ "value" => true }) + true_id = b_true.id + + subclass = Class.new(Boolean).find true_id + superclass = Boolean.find true_id + + assert_equal superclass.read_attribute(:has_fun), subclass.read_attribute(:has_fun) + end + def test_boolean_cast_from_string b_blank = Boolean.create({ "value" => "" }) blank_id = b_blank.id @@ -947,10 +1017,9 @@ class BasicsTest < ActiveRecord::TestCase assert_equal "b", duped_topic.title # test if the attribute values have been duped - topic.title = {"a" => "b"} duped_topic = topic.dup - duped_topic.title["a"] = "c" - assert_equal "b", topic.title["a"] + duped_topic.title.replace "c" + assert_equal "a", topic.title # test if attributes set as part of after_initialize are duped correctly assert_equal topic.author_email_address, duped_topic.author_email_address @@ -961,8 +1030,7 @@ class BasicsTest < ActiveRecord::TestCase assert_not_equal duped_topic.id, topic.id duped_topic.reload - # FIXME: I think this is poor behavior, and will fix it with #5686 - assert_equal({'a' => 'c'}.to_yaml, duped_topic.title) + assert_equal("c", duped_topic.title) end def test_dup_with_aggregate_of_same_name_as_attribute @@ -1061,7 +1129,10 @@ class BasicsTest < ActiveRecord::TestCase # TODO: extend defaults tests to other databases! if current_adapter?(:PostgreSQLAdapter) def test_default + tz = Default.default_timezone + Default.default_timezone = :local default = Default.new + Default.default_timezone = tz # fixed dates / times assert_equal Date.new(2004, 1, 1), default.fixed_date @@ -1093,7 +1164,7 @@ class BasicsTest < ActiveRecord::TestCase assert g.save # Reload and check that we have all the geometric attributes. - h = ActiveRecord::IdentityMap.without { Geometric.find(g.id) } + h = Geometric.find(g.id) assert_equal '(5,6.1)', h.a_point assert_equal '[(2,3),(5.5,7)]', h.a_line_segment @@ -1104,7 +1175,8 @@ class BasicsTest < ActiveRecord::TestCase # use a geometric function to test for an open path objs = Geometric.find_by_sql ["select isopen(a_path) from geometrics where id = ?", g.id] - assert_equal objs[0].isopen, 't' + + assert_equal true, objs[0].isopen # test alternate formats when defining the geometric types @@ -1121,7 +1193,7 @@ class BasicsTest < ActiveRecord::TestCase assert g.save # Reload and check that we have all the geometric attributes. - h = ActiveRecord::IdentityMap.without { Geometric.find(g.id) } + h = Geometric.find(g.id) assert_equal '(5,6.1)', h.a_point assert_equal '[(2,3),(5.5,7)]', h.a_line_segment @@ -1132,7 +1204,8 @@ class BasicsTest < ActiveRecord::TestCase # use a geometric function to test for an closed path objs = Geometric.find_by_sql ["select isclosed(a_path) from geometrics where id = ?", g.id] - assert_equal objs[0].isclosed, 't' + + assert_equal true, objs[0].isclosed end end @@ -1185,19 +1258,6 @@ class BasicsTest < ActiveRecord::TestCase assert(auto.id > 0) end - def quote_column_name(name) - "<#{name}>" - end - - def test_quote_keys - ar = AutoId.new - source = {"foo" => "bar", "baz" => "quux"} - actual = ar.send(:quote_columns, self, source) - inverted = actual.invert - assert_equal("<foo>", inverted["bar"]) - assert_equal("<baz>", inverted["quux"]) - end - def test_sql_injection_via_find assert_raise(ActiveRecord::RecordNotFound, ActiveRecord::StatementInvalid) do Topic.find("123456 OR id > 0") @@ -1215,10 +1275,10 @@ class BasicsTest < ActiveRecord::TestCase end def test_quoting_arrays - replies = Reply.find(:all, :conditions => [ "id IN (?)", topics(:first).replies.collect(&:id) ]) + replies = Reply.scoped(:where => [ "id IN (?)", topics(:first).replies.collect(&:id) ]).all assert_equal topics(:first).replies.size, replies.size - replies = Reply.find(:all, :conditions => [ "id IN (?)", [] ]) + replies = Reply.scoped(:where => [ "id IN (?)", [] ]).all assert_equal 0, replies.size end @@ -1235,6 +1295,42 @@ class BasicsTest < ActiveRecord::TestCase assert_equal(myobj, topic.content) end + def test_serialized_attribute_in_base_class + Topic.serialize("content", Hash) + + hash = { 'content1' => 'value1', 'content2' => 'value2' } + important_topic = ImportantTopic.create("content" => hash) + assert_equal(hash, important_topic.content) + + important_topic.reload + assert_equal(hash, important_topic.content) + end + + # This test was added to fix GH #4004. Obviously the value returned + # is not really the value 'before type cast' so we should maybe think + # about changing that in the future. + def test_serialized_attribute_before_type_cast_returns_unserialized_value + klass = Class.new(ActiveRecord::Base) + klass.table_name = "topics" + klass.serialize :content, Hash + + t = klass.new(:content => { :foo => :bar }) + assert_equal({ :foo => :bar }, t.content_before_type_cast) + t.save! + t.reload + assert_equal({ :foo => :bar }, t.content_before_type_cast) + end + + def test_serialized_attribute_declared_in_subclass + hash = { 'important1' => 'value1', 'important2' => 'value2' } + important_topic = ImportantTopic.create("important" => hash) + assert_equal(hash, important_topic.important) + + important_topic.reload + assert_equal(hash, important_topic.important) + assert_equal(hash, important_topic.read_attribute(:important)) + end + def test_serialized_time_attribute myobj = Time.local(2008,1,1,1,0) topic = Topic.create("content" => myobj).reload @@ -1247,11 +1343,24 @@ class BasicsTest < ActiveRecord::TestCase assert_equal(myobj, topic.content) end - def test_nil_serialized_attribute_with_class_constraint + def test_nil_serialized_attribute_without_class_constraint topic = Topic.new assert_nil topic.content end + def test_nil_not_serialized_without_class_constraint + assert Topic.new(:content => nil).save + assert_equal 1, Topic.where(:content => nil).count + end + + def test_nil_not_serialized_with_class_constraint + Topic.serialize :content, Hash + assert Topic.new(:content => nil).save + assert_equal 1, Topic.where(:content => nil).count + ensure + Topic.serialize(:content) + end + def test_should_raise_exception_on_serialized_attribute_with_type_mismatch myobj = MyObject.new('value1', 'value2') topic = Topic.new(:content => myobj) @@ -1359,22 +1468,6 @@ class BasicsTest < ActiveRecord::TestCase assert_equal author_name, Topic.find(topic.id).author_name end - if RUBY_VERSION < '1.9' - def test_quote_chars - with_kcode('UTF8') do - str = 'The Narrator' - topic = Topic.create(:author_name => str) - assert_equal str, topic.author_name - - assert_kind_of ActiveSupport::Multibyte.proxy_class, str.mb_chars - topic = Topic.find_by_author_name(str.mb_chars) - - assert_kind_of Topic, topic - assert_equal str, topic.author_name, "The right topic should have been found by name even with name passed as Chars" - end - end - end - def test_toggle_attribute assert !topics(:first).approved? topics(:first).toggle!(:approved) @@ -1401,107 +1494,104 @@ class BasicsTest < ActiveRecord::TestCase assert_equal dev, dev.reload end - def test_define_attr_method_with_value - k = Class.new( ActiveRecord::Base ) - k.send(:define_attr_method, :table_name, "foo") - assert_equal "foo", k.table_name + def test_switching_between_table_name + assert_difference("GoodJoke.count") do + Joke.table_name = "cold_jokes" + Joke.create + + Joke.table_name = "funny_jokes" + Joke.create + end end - def test_define_attr_method_with_block - k = Class.new( ActiveRecord::Base ) do - class << self - attr_accessor :foo_key - end + def test_clear_cash_when_setting_table_name + Joke.table_name = "cold_jokes" + before_columns = Joke.columns + before_seq = Joke.sequence_name + + Joke.table_name = "funny_jokes" + after_columns = Joke.columns + after_seq = Joke.sequence_name + + assert_not_equal before_columns, after_columns + unless before_seq.nil? && after_seq.nil? + assert_not_equal before_seq, after_seq + assert_equal "cold_jokes_id_seq", before_seq + assert_equal "funny_jokes_id_seq", after_seq end - k.foo_key = "id" - k.send(:define_attr_method, :foo_key) { "sys_" + original_foo_key } - assert_equal "sys_id", k.foo_key end - def test_set_table_name_with_value - k = Class.new( ActiveRecord::Base ) - k.table_name = "foo" - assert_equal "foo", k.table_name - k.set_table_name "bar" - assert_equal "bar", k.table_name + def test_dont_clear_sequence_name_when_setting_explicitly + Joke.sequence_name = "black_jokes_seq" + Joke.table_name = "cold_jokes" + before_seq = Joke.sequence_name + + Joke.table_name = "funny_jokes" + after_seq = Joke.sequence_name + + assert_equal before_seq, after_seq unless before_seq.nil? && after_seq.nil? end - def test_switching_between_table_name - assert_difference("GoodJoke.count") do - Joke.set_table_name "cold_jokes" - Joke.create + def test_dont_clear_inheritnce_column_when_setting_explicitly + Joke.inheritance_column = "my_type" + before_inherit = Joke.inheritance_column - Joke.set_table_name "funny_jokes" - Joke.create - end + Joke.reset_column_information + after_inherit = Joke.inheritance_column + + assert_equal before_inherit, after_inherit unless before_inherit.blank? && after_inherit.blank? + end + + def test_set_table_name_symbol_converted_to_string + Joke.table_name = :cold_jokes + assert_equal 'cold_jokes', Joke.table_name end def test_quoted_table_name_after_set_table_name klass = Class.new(ActiveRecord::Base) - klass.set_table_name "foo" + klass.table_name = "foo" assert_equal "foo", klass.table_name assert_equal klass.connection.quote_table_name("foo"), klass.quoted_table_name - klass.set_table_name "bar" + klass.table_name = "bar" assert_equal "bar", klass.table_name assert_equal klass.connection.quote_table_name("bar"), klass.quoted_table_name end - def test_set_table_name_with_block + def test_set_table_name_with_inheritance k = Class.new( ActiveRecord::Base ) - k.set_table_name { "ks" } - assert_equal "ks", k.table_name + def k.name; "Foo"; end + def k.table_name; super + "ks"; end + assert_equal "foosks", k.table_name end - def test_set_primary_key_with_value - k = Class.new( ActiveRecord::Base ) - k.primary_key = "foo" - assert_equal "foo", k.primary_key - k.set_primary_key "bar" - assert_equal "bar", k.primary_key - end - - def test_set_primary_key_with_block - k = Class.new( ActiveRecord::Base ) - k.primary_key = 'id' - k.set_primary_key { "sys_" + original_primary_key } - assert_equal "sys_id", k.primary_key - end - - def test_set_inheritance_column_with_value - k = Class.new( ActiveRecord::Base ) - k.inheritance_column = "foo" - assert_equal "foo", k.inheritance_column - k.set_inheritance_column "bar" - assert_equal "bar", k.inheritance_column - end - - def test_set_inheritance_column_with_block - k = Class.new( ActiveRecord::Base ) - k.set_inheritance_column { original_inheritance_column + "_id" } - assert_equal "type_id", k.inheritance_column + def test_sequence_name_with_abstract_class + ak = Class.new(ActiveRecord::Base) + ak.abstract_class = true + k = Class.new(ak) + k.table_name = "projects" + orig_name = k.sequence_name + return skip "sequences not supported by db" unless orig_name + assert_equal k.reset_sequence_name, orig_name end def test_count_with_join res = Post.count_by_sql "SELECT COUNT(*) FROM posts LEFT JOIN comments ON posts.id=comments.post_id WHERE posts.#{QUOTED_TYPE} = 'Post'" - res2 = Post.count(:conditions => "posts.#{QUOTED_TYPE} = 'Post'", :joins => "LEFT JOIN comments ON posts.id=comments.post_id") + res2 = Post.where("posts.#{QUOTED_TYPE} = 'Post'").joins("LEFT JOIN comments ON posts.id=comments.post_id").count assert_equal res, res2 res3 = nil assert_nothing_raised do - res3 = Post.count(:conditions => "posts.#{QUOTED_TYPE} = 'Post'", - :joins => "LEFT JOIN comments ON posts.id=comments.post_id") + res3 = Post.where("posts.#{QUOTED_TYPE} = 'Post'").joins("LEFT JOIN comments ON posts.id=comments.post_id").count end assert_equal res, res3 res4 = Post.count_by_sql "SELECT COUNT(p.id) FROM posts p, comments co WHERE p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id" res5 = nil assert_nothing_raised do - res5 = Post.count(:conditions => "p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id", - :joins => "p, comments co", - :select => "p.id") + res5 = Post.where("p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id").joins("p, comments co").select("p.id").count end assert_equal res4, res5 @@ -1509,145 +1599,64 @@ class BasicsTest < ActiveRecord::TestCase res6 = Post.count_by_sql "SELECT COUNT(DISTINCT p.id) FROM posts p, comments co WHERE p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id" res7 = nil assert_nothing_raised do - res7 = Post.count(:conditions => "p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id", - :joins => "p, comments co", - :select => "p.id", - :distinct => true) + res7 = Post.where("p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id").joins("p, comments co").select("p.id").count(distinct: true) end assert_equal res6, res7 end - def test_scoped_find_conditions - scoped_developers = Developer.send(:with_scope, :find => { :conditions => 'salary > 90000' }) do - Developer.find(:all, :conditions => 'id < 5') - end - assert !scoped_developers.include?(developers(:david)) # David's salary is less than 90,000 - assert_equal 3, scoped_developers.size - end - def test_no_limit_offset assert_nothing_raised do - Developer.find(:all, :offset => 2) + Developer.scoped(:offset => 2).all end end - def test_scoped_find_limit_offset - scoped_developers = Developer.send(:with_scope, :find => { :limit => 3, :offset => 2 }) do - Developer.find(:all, :order => 'id') - end - assert !scoped_developers.include?(developers(:david)) - assert !scoped_developers.include?(developers(:jamis)) - assert_equal 3, scoped_developers.size - - # Test without scoped find conditions to ensure we get the whole thing - developers = Developer.find(:all, :order => 'id') - assert_equal Developer.count, developers.size - end - - def test_scoped_find_order - # Test order in scope - scoped_developers = Developer.send(:with_scope, :find => { :limit => 1, :order => 'salary DESC' }) do - Developer.find(:all) - end - assert_equal 'Jamis', scoped_developers.first.name - assert scoped_developers.include?(developers(:jamis)) - # Test scope without order and order in find - scoped_developers = Developer.send(:with_scope, :find => { :limit => 1 }) do - Developer.find(:all, :order => 'salary DESC') - end - # Test scope order + find order, order has priority - scoped_developers = Developer.send(:with_scope, :find => { :limit => 3, :order => 'id DESC' }) do - Developer.find(:all, :order => 'salary ASC') - end - assert scoped_developers.include?(developers(:poor_jamis)) - assert ! scoped_developers.include?(developers(:david)) - assert ! scoped_developers.include?(developers(:jamis)) - assert_equal 3, scoped_developers.size - - # Test without scoped find conditions to ensure we get the right thing - assert ! scoped_developers.include?(Developer.find(1)) - assert scoped_developers.include?(Developer.find(11)) - end - - def test_scoped_find_limit_offset_including_has_many_association - topics = Topic.send(:with_scope, :find => {:limit => 1, :offset => 1, :include => :replies}) do - Topic.find(:all, :order => "topics.id") - end - assert_equal 1, topics.size - assert_equal 2, topics.first.id - end - - def test_scoped_find_order_including_has_many_association - developers = Developer.send(:with_scope, :find => { :order => 'developers.salary DESC', :include => :projects }) do - Developer.find(:all) - end - assert developers.size >= 2 - for i in 1...developers.size - assert developers[i-1].salary >= developers[i].salary - end - end - - def test_scoped_find_with_group_and_having - developers = Developer.send(:with_scope, :find => { :group => 'developers.salary', :having => "SUM(salary) > 10000", :select => "SUM(salary) as salary" }) do - Developer.find(:all) - end - assert_equal 3, developers.size - end - def test_find_last - last = Developer.find :last - assert_equal last, Developer.find(:first, :order => 'id desc') + last = Developer.last + assert_equal last, Developer.scoped(:order => 'id desc').first end def test_last - assert_equal Developer.find(:first, :order => 'id desc'), Developer.last + assert_equal Developer.scoped(:order => 'id desc').first, Developer.last end def test_all developers = Developer.all assert_kind_of Array, developers - assert_equal Developer.find(:all), developers + assert_equal Developer.all, developers end def test_all_with_conditions - assert_equal Developer.find(:all, :order => 'id desc'), Developer.order('id desc').all + assert_equal Developer.scoped(:order => 'id desc').all, Developer.order('id desc').all end def test_find_ordered_last - last = Developer.find :last, :order => 'developers.salary ASC' - assert_equal last, Developer.find(:all, :order => 'developers.salary ASC').last + last = Developer.scoped(:order => 'developers.salary ASC').last + assert_equal last, Developer.scoped(:order => 'developers.salary ASC').all.last end def test_find_reverse_ordered_last - last = Developer.find :last, :order => 'developers.salary DESC' - assert_equal last, Developer.find(:all, :order => 'developers.salary DESC').last + last = Developer.scoped(:order => 'developers.salary DESC').last + assert_equal last, Developer.scoped(:order => 'developers.salary DESC').all.last end def test_find_multiple_ordered_last - last = Developer.find :last, :order => 'developers.name, developers.salary DESC' - assert_equal last, Developer.find(:all, :order => 'developers.name, developers.salary DESC').last + last = Developer.scoped(:order => 'developers.name, developers.salary DESC').last + assert_equal last, Developer.scoped(:order => 'developers.name, developers.salary DESC').all.last end def test_find_keeps_multiple_order_values - combined = Developer.find(:all, :order => 'developers.name, developers.salary') - assert_equal combined, Developer.find(:all, :order => ['developers.name', 'developers.salary']) + combined = Developer.scoped(:order => 'developers.name, developers.salary').all + assert_equal combined, Developer.scoped(:order => ['developers.name', 'developers.salary']).all end def test_find_keeps_multiple_group_values - combined = Developer.find(:all, :group => 'developers.name, developers.salary, developers.id, developers.created_at, developers.updated_at') - assert_equal combined, Developer.find(:all, :group => ['developers.name', 'developers.salary', 'developers.id', 'developers.created_at', 'developers.updated_at']) + combined = Developer.scoped(:group => 'developers.name, developers.salary, developers.id, developers.created_at, developers.updated_at').all + assert_equal combined, Developer.scoped(:group => ['developers.name', 'developers.salary', 'developers.id', 'developers.created_at', 'developers.updated_at']).all end def test_find_symbol_ordered_last - last = Developer.find :last, :order => :salary - assert_equal last, Developer.find(:all, :order => :salary).last - end - - def test_find_scoped_ordered_last - last_developer = Developer.send(:with_scope, :find => { :order => 'developers.salary ASC' }) do - Developer.find(:last) - end - assert_equal last_developer, Developer.find(:all, :order => 'developers.salary ASC').last + last = Developer.scoped(:order => :salary).last + assert_equal last, Developer.scoped(:order => :salary).all.last end def test_abstract_class @@ -1673,10 +1682,7 @@ class BasicsTest < ActiveRecord::TestCase end def test_descends_from_active_record - # Tries to call Object.abstract_class? - assert_raise(NoMethodError) do - ActiveRecord::Base.descends_from_active_record? - end + assert !ActiveRecord::Base.descends_from_active_record? # Abstract subclass of AR::Base. assert LoosePerson.descends_from_active_record? @@ -1699,6 +1705,10 @@ class BasicsTest < ActiveRecord::TestCase # Concrete subclasses an abstract class which has a type column. assert !SubStiPost.descends_from_active_record? + + assert Teapot.descends_from_active_record? + assert !OtherTeapot.descends_from_active_record? + assert CoolTeapot.descends_from_active_record? end def test_find_on_abstract_base_class_doesnt_use_type_condition @@ -1721,7 +1731,7 @@ class BasicsTest < ActiveRecord::TestCase end def test_to_param_should_return_string - assert_kind_of String, Client.find(:first).to_param + assert_kind_of String, Client.first.to_param end def test_inspect_class @@ -1732,7 +1742,7 @@ class BasicsTest < ActiveRecord::TestCase def test_inspect_instance topic = topics(:first) - assert_equal %(#<Topic id: 1, title: "The First Topic", author_name: "David", author_email_address: "david@loudthinking.com", written_on: "#{topic.written_on.to_s(:db)}", bonus_time: "#{topic.bonus_time.to_s(:db)}", last_read: "#{topic.last_read.to_s(:db)}", content: "Have a nice day", approved: false, replies_count: 1, parent_id: nil, parent_title: nil, type: nil, group: nil, created_at: "#{topic.created_at.to_s(:db)}", updated_at: "#{topic.updated_at.to_s(:db)}">), topic.inspect + assert_equal %(#<Topic id: 1, title: "The First Topic", author_name: "David", author_email_address: "david@loudthinking.com", written_on: "#{topic.written_on.to_s(:db)}", bonus_time: "#{topic.bonus_time.to_s(:db)}", last_read: "#{topic.last_read.to_s(:db)}", content: "Have a nice day", important: nil, approved: false, replies_count: 1, parent_id: nil, parent_title: nil, type: nil, group: nil, created_at: "#{topic.created_at.to_s(:db)}", updated_at: "#{topic.updated_at.to_s(:db)}">), topic.inspect end def test_inspect_new_instance @@ -1740,8 +1750,8 @@ class BasicsTest < ActiveRecord::TestCase end def test_inspect_limited_select_instance - assert_equal %(#<Topic id: 1>), Topic.find(:first, :select => 'id', :conditions => 'id = 1').inspect - assert_equal %(#<Topic id: 1, title: "The First Topic">), Topic.find(:first, :select => 'id, title', :conditions => 'id = 1').inspect + assert_equal %(#<Topic id: 1>), Topic.scoped(:select => 'id', :where => 'id = 1').first.inspect + assert_equal %(#<Topic id: 1, title: "The First Topic">), Topic.scoped(:select => 'id, title', :where => 'id = 1').first.inspect end def test_inspect_class_without_table @@ -1772,7 +1782,7 @@ class BasicsTest < ActiveRecord::TestCase def test_silence_sets_log_level_to_error_in_block original_logger = ActiveRecord::Base.logger log = StringIO.new - ActiveRecord::Base.logger = Logger.new(log) + ActiveRecord::Base.logger = ActiveSupport::Logger.new(log) ActiveRecord::Base.logger.level = Logger::DEBUG ActiveRecord::Base.silence do ActiveRecord::Base.logger.warn "warn" @@ -1786,7 +1796,7 @@ class BasicsTest < ActiveRecord::TestCase def test_silence_sets_log_level_back_to_level_before_yield original_logger = ActiveRecord::Base.logger log = StringIO.new - ActiveRecord::Base.logger = Logger.new(log) + ActiveRecord::Base.logger = ActiveSupport::Logger.new(log) ActiveRecord::Base.logger.level = Logger::WARN ActiveRecord::Base.silence do end @@ -1798,7 +1808,7 @@ class BasicsTest < ActiveRecord::TestCase def test_benchmark_with_log_level original_logger = ActiveRecord::Base.logger log = StringIO.new - ActiveRecord::Base.logger = Logger.new(log) + ActiveRecord::Base.logger = ActiveSupport::Logger.new(log) ActiveRecord::Base.logger.level = Logger::WARN ActiveRecord::Base.benchmark("Debug Topic Count", :level => :debug) { Topic.count } ActiveRecord::Base.benchmark("Warn Topic Count", :level => :warn) { Topic.count } @@ -1813,10 +1823,8 @@ class BasicsTest < ActiveRecord::TestCase def test_benchmark_with_use_silence original_logger = ActiveRecord::Base.logger log = StringIO.new - ActiveRecord::Base.logger = Logger.new(log) - ActiveRecord::Base.benchmark("Logging", :level => :debug, :silence => true) { ActiveRecord::Base.logger.debug "Loud" } + ActiveRecord::Base.logger = ActiveSupport::Logger.new(log) ActiveRecord::Base.benchmark("Logging", :level => :debug, :silence => false) { ActiveRecord::Base.logger.debug "Quiet" } - assert_no_match(/Loud/, log.string) assert_match(/Quiet/, log.string) ensure ActiveRecord::Base.logger = original_logger @@ -1848,9 +1856,9 @@ class BasicsTest < ActiveRecord::TestCase def test_clear_cache! # preheat cache - c1 = Post.columns + c1 = Post.connection.schema_cache.columns['posts'] ActiveRecord::Base.clear_cache! - c2 = Post.columns + c2 = Post.connection.schema_cache.columns['posts'] assert_not_equal c1, c2 end @@ -1867,11 +1875,6 @@ class BasicsTest < ActiveRecord::TestCase end def test_marshal_round_trip - if ENV['TRAVIS'] && RUBY_VERSION == "1.8.7" - return skip("Marshalling tests disabled for Ruby 1.8.7 on Travis CI due to what appears " \ - "to be a Ruby bug.") - end - expected = posts(:welcome) marshalled = Marshal.dump(expected) actual = Marshal.load(marshalled) @@ -1880,11 +1883,6 @@ class BasicsTest < ActiveRecord::TestCase end def test_marshal_new_record_round_trip - if ENV['TRAVIS'] && RUBY_VERSION == "1.8.7" - return skip("Marshalling tests disabled for Ruby 1.8.7 on Travis CI due to what appears " \ - "to be a Ruby bug.") - end - marshalled = Marshal.dump(Post.new) post = Marshal.load(marshalled) @@ -1892,11 +1890,6 @@ class BasicsTest < ActiveRecord::TestCase end def test_marshalling_with_associations - if ENV['TRAVIS'] && RUBY_VERSION == "1.8.7" - return skip("Marshalling tests disabled for Ruby 1.8.7 on Travis CI due to what appears " \ - "to be a Ruby bug.") - end - post = Post.new post.comments.build @@ -1906,6 +1899,15 @@ class BasicsTest < ActiveRecord::TestCase assert_equal 1, post.comments.length end + def test_marshalling_new_record_round_trip_with_associations + post = Post.new + post.comments.build + + post = Marshal.load(Marshal.dump(post)) + + assert post.new_record?, "should be a new record" + end + def test_attribute_names assert_equal ["id", "type", "ruby_type", "firm_id", "firm_name", "name", "client_of", "rating", "account_id"], Company.attribute_names @@ -1915,7 +1917,7 @@ class BasicsTest < ActiveRecord::TestCase assert_equal [], NonExistentTable.attribute_names end - def test_attribtue_names_on_abstract_class + def test_attribute_names_on_abstract_class assert_equal [], AbstractCompany.attribute_names end @@ -1949,4 +1951,72 @@ class BasicsTest < ActiveRecord::TestCase Bird.stubs(:scoped).returns(mock(:uniq => scope)) assert_equal scope, Bird.uniq end + + def test_active_record_super + assert_equal ActiveRecord::Model, ActiveRecord::Base.active_record_super + assert_equal ActiveRecord::Base, Topic.active_record_super + assert_equal Topic, ImportantTopic.active_record_super + assert_equal ActiveRecord::Model, Teapot.active_record_super + assert_equal Teapot, OtherTeapot.active_record_super + assert_equal ActiveRecord::Model, CoolTeapot.active_record_super + end + + def test_table_name_with_2_abstract_subclasses + assert_equal "photos", Photo.table_name + end + + def test_column_types_typecast + topic = Topic.first + refute_equal 't.lo', topic.author_name + + attrs = topic.attributes.dup + attrs.delete 'id' + + typecast = Class.new { + def type_cast value + "t.lo" + end + } + + types = { 'author_name' => typecast.new } + topic = Topic.allocate.init_with 'attributes' => attrs, + 'column_types' => types + + assert_equal 't.lo', topic.author_name + end + + def test_typecasting_aliases + assert_equal 10, Topic.select('10 as tenderlove').first.tenderlove + end + + def test_slice + company = Company.new(:rating => 1, :name => "37signals", :firm_name => "37signals") + hash = company.slice(:name, :rating, "arbitrary_method") + assert_equal hash[:name], company.name + assert_equal hash['name'], company.name + assert_equal hash[:rating], company.rating + assert_equal hash['arbitrary_method'], company.arbitrary_method + assert_equal hash[:arbitrary_method], company.arbitrary_method + assert_nil hash[:firm_name] + assert_nil hash['firm_name'] + end + + ["find_by", "find_by!"].each do |meth| + test "#{meth} delegates to scoped" do + record = stub + + scope = mock + scope.expects(meth).with(:foo, :bar).returns(record) + + klass = Class.new(ActiveRecord::Base) + klass.stubs(:scoped => scope) + + assert_equal record, klass.public_send(meth, :foo, :bar) + end + end + + test "scoped can take a values hash" do + klass = Class.new(ActiveRecord::Base) + assert_equal ['foo'], klass.scoped(select: 'foo').select_values + end end diff --git a/activerecord/test/cases/batches_test.rb b/activerecord/test/cases/batches_test.rb index 660098b9ad..cdd4b49042 100644 --- a/activerecord/test/cases/batches_test.rb +++ b/activerecord/test/cases/batches_test.rb @@ -27,30 +27,18 @@ class EachTest < ActiveRecord::TestCase def test_each_should_raise_if_select_is_set_without_id assert_raise(RuntimeError) do - Post.find_each(:select => :title, :batch_size => 1) { |post| post } + Post.select(:title).find_each(:batch_size => 1) { |post| post } end end def test_each_should_execute_if_id_is_in_select assert_queries(6) do - Post.find_each(:select => "id, title, type", :batch_size => 2) do |post| + Post.select("id, title, type").find_each(:batch_size => 2) do |post| assert_kind_of Post, post end end end - def test_each_should_raise_if_the_order_is_set - assert_raise(RuntimeError) do - Post.find_each(:order => "title") { |post| post } - end - end - - def test_each_should_raise_if_the_limit_is_set - assert_raise(RuntimeError) do - Post.find_each(:limit => 1) { |post| post } - end - end - def test_warn_if_limit_scope_is_set ActiveRecord::Base.logger.expects(:warn) Post.limit(1).find_each { |post| post } diff --git a/activerecord/test/cases/binary_test.rb b/activerecord/test/cases/binary_test.rb index 06c14cb108..25d2896ab0 100644 --- a/activerecord/test/cases/binary_test.rb +++ b/activerecord/test/cases/binary_test.rb @@ -8,11 +8,11 @@ unless current_adapter?(:SybaseAdapter, :DB2Adapter, :FirebirdAdapter) require 'models/binary' class BinaryTest < ActiveRecord::TestCase - FIXTURES = %w(flowers.jpg example.log) + FIXTURES = %w(flowers.jpg example.log test.txt) def test_mixed_encoding str = "\x80" - str.force_encoding('ASCII-8BIT') if str.respond_to?(:force_encoding) + str.force_encoding('ASCII-8BIT') binary = Binary.new :name => 'いただきます!', :data => str binary.save! @@ -23,7 +23,7 @@ unless current_adapter?(:SybaseAdapter, :DB2Adapter, :FirebirdAdapter) # Mysql adapter doesn't properly encode things, so we have to do it if current_adapter?(:MysqlAdapter) - name.force_encoding('UTF-8') if name.respond_to?(:force_encoding) + name.force_encoding('UTF-8') end assert_equal 'いただきます!', name end @@ -33,7 +33,7 @@ unless current_adapter?(:SybaseAdapter, :DB2Adapter, :FirebirdAdapter) FIXTURES.each do |filename| data = File.read(ASSETS_ROOT + "/#{filename}") - data.force_encoding('ASCII-8BIT') if data.respond_to?(:force_encoding) + data.force_encoding('ASCII-8BIT') data.freeze bin = Binary.new(:data => data) diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb index c38814713a..e096585f62 100644 --- a/activerecord/test/cases/calculations_test.rb +++ b/activerecord/test/cases/calculations_test.rb @@ -1,5 +1,6 @@ require "cases/helper" require 'models/company' +require "models/contract" require 'models/topic' require 'models/edge' require 'models/club' @@ -48,13 +49,7 @@ class CalculationsTest < ActiveRecord::TestCase end def test_should_get_maximum_of_field_with_include - assert_equal 55, Account.maximum(:credit_limit, :include => :firm, :conditions => "companies.name != 'Summit'") - end - - def test_should_get_maximum_of_field_with_scoped_include - Account.send :with_scope, :find => { :include => :firm, :conditions => "companies.name != 'Summit'" } do - assert_equal 55, Account.maximum(:credit_limit) - end + assert_equal 55, Account.where("companies.name != 'Summit'").references(:companies).includes(:firm).maximum(:credit_limit) end def test_should_get_minimum_of_field @@ -62,12 +57,12 @@ class CalculationsTest < ActiveRecord::TestCase end def test_should_group_by_field - c = Account.sum(:credit_limit, :group => :firm_id) + c = Account.group(:firm_id).sum(:credit_limit) [1,6,2].each { |firm_id| assert c.keys.include?(firm_id) } end def test_should_group_by_multiple_fields - c = Account.count(:all, :group => ['firm_id', :credit_limit]) + c = Account.group('firm_id', :credit_limit).count(:all) [ [nil, 50], [1, 50], [6, 50], [6, 55], [9, 53], [2, 60] ].each { |firm_and_limit| assert c.keys.include?(firm_and_limit) } end @@ -80,32 +75,32 @@ class CalculationsTest < ActiveRecord::TestCase end def test_should_group_by_summed_field - c = Account.sum(:credit_limit, :group => :firm_id) + c = Account.group(:firm_id).sum(:credit_limit) assert_equal 50, c[1] assert_equal 105, c[6] assert_equal 60, c[2] end def test_should_order_by_grouped_field - c = Account.sum(:credit_limit, :group => :firm_id, :order => "firm_id") + c = Account.scoped(:group => :firm_id, :order => "firm_id").sum(:credit_limit) assert_equal [1, 2, 6, 9], c.keys.compact end def test_should_order_by_calculation - c = Account.sum(:credit_limit, :group => :firm_id, :order => "sum_credit_limit desc, firm_id") + c = Account.scoped(:group => :firm_id, :order => "sum_credit_limit desc, firm_id").sum(:credit_limit) assert_equal [105, 60, 53, 50, 50], c.keys.collect { |k| c[k] } assert_equal [6, 2, 9, 1], c.keys.compact end def test_should_limit_calculation - c = Account.sum(:credit_limit, :conditions => "firm_id IS NOT NULL", - :group => :firm_id, :order => "firm_id", :limit => 2) + c = Account.scoped(:where => "firm_id IS NOT NULL", + :group => :firm_id, :order => "firm_id", :limit => 2).sum(:credit_limit) assert_equal [1, 2], c.keys.compact end def test_should_limit_calculation_with_offset - c = Account.sum(:credit_limit, :conditions => "firm_id IS NOT NULL", - :group => :firm_id, :order => "firm_id", :limit => 2, :offset => 1) + c = Account.scoped(:where => "firm_id IS NOT NULL", :group => :firm_id, + :order => "firm_id", :limit => 2, :offset => 1).sum(:credit_limit) assert_equal [2, 6], c.keys.compact end @@ -155,16 +150,8 @@ class CalculationsTest < ActiveRecord::TestCase end def test_should_group_by_summed_field_having_condition - c = Account.sum(:credit_limit, :group => :firm_id, - :having => 'sum(credit_limit) > 50') - assert_nil c[1] - assert_equal 105, c[6] - assert_equal 60, c[2] - end - - def test_should_group_by_summed_field_having_sanitized_condition - c = Account.sum(:credit_limit, :group => :firm_id, - :having => ['sum(credit_limit) > ?', 50]) + c = Account.scoped(:group => :firm_id, + :having => 'sum(credit_limit) > 50').sum(:credit_limit) assert_nil c[1] assert_equal 105, c[6] assert_equal 60, c[2] @@ -178,19 +165,19 @@ class CalculationsTest < ActiveRecord::TestCase end def test_should_group_by_summed_association - c = Account.sum(:credit_limit, :group => :firm) + c = Account.group(:firm).sum(:credit_limit) assert_equal 50, c[companies(:first_firm)] assert_equal 105, c[companies(:rails_core)] assert_equal 60, c[companies(:first_client)] end def test_should_sum_field_with_conditions - assert_equal 105, Account.sum(:credit_limit, :conditions => 'firm_id = 6') + assert_equal 105, Account.where('firm_id = 6').sum(:credit_limit) end def test_should_return_zero_if_sum_conditions_return_nothing - assert_equal 0, Account.sum(:credit_limit, :conditions => '1 = 2') - assert_equal 0, companies(:rails_core).companies.sum(:id, :conditions => '1 = 2') + assert_equal 0, Account.where('1 = 2').sum(:credit_limit) + assert_equal 0, companies(:rails_core).companies.where('1 = 2').sum(:id) end def test_sum_should_return_valid_values_for_decimals @@ -199,24 +186,24 @@ class CalculationsTest < ActiveRecord::TestCase end def test_should_group_by_summed_field_with_conditions - c = Account.sum(:credit_limit, :conditions => 'firm_id > 1', - :group => :firm_id) + c = Account.scoped(:where => 'firm_id > 1', + :group => :firm_id).sum(:credit_limit) assert_nil c[1] assert_equal 105, c[6] assert_equal 60, c[2] end def test_should_group_by_summed_field_with_conditions_and_having - c = Account.sum(:credit_limit, :conditions => 'firm_id > 1', - :group => :firm_id, - :having => 'sum(credit_limit) > 60') + c = Account.scoped(:where => 'firm_id > 1', + :group => :firm_id, + :having => 'sum(credit_limit) > 60').sum(:credit_limit) assert_nil c[1] assert_equal 105, c[6] assert_nil c[2] end def test_should_group_by_fields_with_table_alias - c = Account.sum(:credit_limit, :group => 'accounts.firm_id') + c = Account.group('accounts.firm_id').sum(:credit_limit) assert_equal 50, c[1] assert_equal 105, c[6] assert_equal 60, c[2] @@ -228,14 +215,14 @@ class CalculationsTest < ActiveRecord::TestCase end def test_should_calculate_grouped_with_invalid_field - c = Account.count(:all, :group => 'accounts.firm_id') + c = Account.group('accounts.firm_id').count(:all) assert_equal 1, c[1] assert_equal 2, c[6] assert_equal 1, c[2] end def test_should_calculate_grouped_association_with_invalid_field - c = Account.count(:all, :group => :firm) + c = Account.group(:firm).count(:all) assert_equal 1, c[companies(:first_firm)] assert_equal 2, c[companies(:rails_core)] assert_equal 1, c[companies(:first_client)] @@ -254,7 +241,7 @@ class CalculationsTest < ActiveRecord::TestCase column.expects(:type_cast).with("ABC").returns("ABC") Account.expects(:columns).at_least_once.returns([column]) - c = Account.count(:all, :group => :firm) + c = Account.group(:firm).count(:all) first_key = c.keys.first assert_equal Firm, first_key.class assert_equal 1, c[first_key] @@ -262,22 +249,14 @@ class CalculationsTest < ActiveRecord::TestCase def test_should_calculate_grouped_association_with_foreign_key_option Account.belongs_to :another_firm, :class_name => 'Firm', :foreign_key => 'firm_id' - c = Account.count(:all, :group => :another_firm) + c = Account.group(:another_firm).count(:all) assert_equal 1, c[companies(:first_firm)] assert_equal 2, c[companies(:rails_core)] assert_equal 1, c[companies(:first_client)] end - def test_should_not_modify_options_when_using_includes - options = {:conditions => 'companies.id > 1', :include => :firm} - options_copy = options.dup - - Account.count(:all, options) - assert_equal options_copy, options - end - def test_should_calculate_grouped_by_function - c = Company.count(:all, :group => "UPPER(#{QUOTED_TYPE})") + c = Company.group("UPPER(#{QUOTED_TYPE})").count(:all) assert_equal 2, c[nil] assert_equal 1, c['DEPENDENTFIRM'] assert_equal 4, c['CLIENT'] @@ -285,7 +264,7 @@ class CalculationsTest < ActiveRecord::TestCase end def test_should_calculate_grouped_by_function_with_table_alias - c = Company.count(:all, :group => "UPPER(companies.#{QUOTED_TYPE})") + c = Company.group("UPPER(companies.#{QUOTED_TYPE})").count(:all) assert_equal 2, c[nil] assert_equal 1, c['DEPENDENTFIRM'] assert_equal 4, c['CLIENT'] @@ -305,25 +284,24 @@ class CalculationsTest < ActiveRecord::TestCase end def test_should_sum_scoped_field_with_conditions - assert_equal 8, companies(:rails_core).companies.sum(:id, :conditions => 'id > 7') + assert_equal 8, companies(:rails_core).companies.where('id > 7').sum(:id) end def test_should_group_by_scoped_field - c = companies(:rails_core).companies.sum(:id, :group => :name) + c = companies(:rails_core).companies.group(:name).sum(:id) assert_equal 7, c['Leetsoft'] assert_equal 8, c['Jadedpixel'] end def test_should_group_by_summed_field_through_association_and_having - c = companies(:rails_core).companies.sum(:id, :group => :name, - :having => 'sum(id) > 7') + c = companies(:rails_core).companies.group(:name).having('sum(id) > 7').sum(:id) assert_nil c['Leetsoft'] assert_equal 8, c['Jadedpixel'] end def test_should_count_selected_field_with_include - assert_equal 6, Account.count(:distinct => true, :include => :firm) - assert_equal 4, Account.count(:distinct => true, :include => :firm, :select => :credit_limit) + assert_equal 6, Account.includes(:firm).count(:distinct => true) + assert_equal 4, Account.includes(:firm).select(:credit_limit).count(:distinct => true) end def test_should_not_perform_joined_include_by_default @@ -347,11 +325,11 @@ class CalculationsTest < ActiveRecord::TestCase Account.last.update_column('credit_limit', 49) Account.first.update_column('credit_limit', 51) - assert_equal 1, Account.scoped(:select => "credit_limit").count(:conditions => ['credit_limit >= 50']) + assert_equal 1, Account.scoped(:select => "credit_limit").where('credit_limit >= 50').count end def test_should_count_manual_select_with_include - assert_equal 6, Account.count(:select => "DISTINCT accounts.id", :include => :firm) + assert_equal 6, Account.scoped(:select => "DISTINCT accounts.id", :includes => :firm).count end def test_count_with_column_parameter @@ -359,16 +337,16 @@ class CalculationsTest < ActiveRecord::TestCase end def test_count_with_column_and_options_parameter - assert_equal 2, Account.count(:firm_id, :conditions => "credit_limit = 50 AND firm_id IS NOT NULL") + assert_equal 2, Account.where("credit_limit = 50 AND firm_id IS NOT NULL").count(:firm_id) end def test_should_count_field_in_joined_table - assert_equal 5, Account.count('companies.id', :joins => :firm) - assert_equal 4, Account.count('companies.id', :joins => :firm, :distinct => true) + assert_equal 5, Account.joins(:firm).count('companies.id') + assert_equal 4, Account.joins(:firm).count('companies.id', :distinct => true) end def test_should_count_field_in_joined_table_with_group_by - c = Account.count('companies.id', :group => 'accounts.firm_id', :joins => :firm) + c = Account.scoped(:group => 'accounts.firm_id', :joins => :firm).count('companies.id') [1,6,2,9].each { |firm_id| assert c.keys.include?(firm_id) } end @@ -391,17 +369,17 @@ class CalculationsTest < ActiveRecord::TestCase end def test_count_with_from_option - assert_equal Company.count(:all), Company.count(:all, :from => 'companies') - assert_equal Account.count(:all, :conditions => "credit_limit = 50"), - Account.count(:all, :from => 'accounts', :conditions => "credit_limit = 50") - assert_equal Company.count(:type, :conditions => {:type => "Firm"}), - Company.count(:type, :conditions => {:type => "Firm"}, :from => 'companies') + assert_equal Company.count(:all), Company.from('companies').count(:all) + assert_equal Account.where("credit_limit = 50").count(:all), + Account.from('accounts').where("credit_limit = 50").count(:all) + assert_equal Company.where(:type => "Firm").count(:type), + Company.where(:type => "Firm").from('companies').count(:type) end def test_sum_with_from_option - assert_equal Account.sum(:credit_limit), Account.sum(:credit_limit, :from => 'accounts') - assert_equal Account.sum(:credit_limit, :conditions => "credit_limit > 50"), - Account.sum(:credit_limit, :from => 'accounts', :conditions => "credit_limit > 50") + assert_equal Account.sum(:credit_limit), Account.from('accounts').sum(:credit_limit) + assert_equal Account.where("credit_limit > 50").sum(:credit_limit), + Account.where("credit_limit > 50").from('accounts').sum(:credit_limit) end def test_sum_array_compatibility @@ -409,33 +387,33 @@ class CalculationsTest < ActiveRecord::TestCase end def test_average_with_from_option - assert_equal Account.average(:credit_limit), Account.average(:credit_limit, :from => 'accounts') - assert_equal Account.average(:credit_limit, :conditions => "credit_limit > 50"), - Account.average(:credit_limit, :from => 'accounts', :conditions => "credit_limit > 50") + assert_equal Account.average(:credit_limit), Account.from('accounts').average(:credit_limit) + assert_equal Account.where("credit_limit > 50").average(:credit_limit), + Account.where("credit_limit > 50").from('accounts').average(:credit_limit) end def test_minimum_with_from_option - assert_equal Account.minimum(:credit_limit), Account.minimum(:credit_limit, :from => 'accounts') - assert_equal Account.minimum(:credit_limit, :conditions => "credit_limit > 50"), - Account.minimum(:credit_limit, :from => 'accounts', :conditions => "credit_limit > 50") + assert_equal Account.minimum(:credit_limit), Account.from('accounts').minimum(:credit_limit) + assert_equal Account.where("credit_limit > 50").minimum(:credit_limit), + Account.where("credit_limit > 50").from('accounts').minimum(:credit_limit) end def test_maximum_with_from_option - assert_equal Account.maximum(:credit_limit), Account.maximum(:credit_limit, :from => 'accounts') - assert_equal Account.maximum(:credit_limit, :conditions => "credit_limit > 50"), - Account.maximum(:credit_limit, :from => 'accounts', :conditions => "credit_limit > 50") + assert_equal Account.maximum(:credit_limit), Account.from('accounts').maximum(:credit_limit) + assert_equal Account.where("credit_limit > 50").maximum(:credit_limit), + Account.where("credit_limit > 50").from('accounts').maximum(:credit_limit) end def test_from_option_with_specified_index if Edge.connection.adapter_name == 'MySQL' or Edge.connection.adapter_name == 'Mysql2' - assert_equal Edge.count(:all), Edge.count(:all, :from => 'edges USE INDEX(unique_edge_index)') - assert_equal Edge.count(:all, :conditions => 'sink_id < 5'), - Edge.count(:all, :from => 'edges USE INDEX(unique_edge_index)', :conditions => 'sink_id < 5') + assert_equal Edge.count(:all), Edge.from('edges USE INDEX(unique_edge_index)').count(:all) + assert_equal Edge.where('sink_id < 5').count(:all), + Edge.from('edges USE INDEX(unique_edge_index)').where('sink_id < 5').count(:all) end end def test_from_option_with_table_different_than_class - assert_equal Account.count(:all), Company.count(:all, :from => 'accounts') + assert_equal Account.count(:all), Company.from('accounts').count(:all) end def test_distinct_is_honored_when_used_with_count_operation_after_group @@ -446,4 +424,49 @@ class CalculationsTest < ActiveRecord::TestCase distinct_authors_for_approved_count = Topic.group(:approved).count(:author_name, :distinct => true)[true] assert_equal distinct_authors_for_approved_count, 2 end + + def test_pluck + assert_equal [1,2,3,4], Topic.order(:id).pluck(:id) + end + + def test_pluck_type_cast + topic = topics(:first) + relation = Topic.where(:id => topic.id) + assert_equal [ topic.approved ], relation.pluck(:approved) + assert_equal [ topic.last_read ], relation.pluck(:last_read) + assert_equal [ topic.written_on ], relation.pluck(:written_on) + end + + def test_pluck_and_uniq + assert_equal [50, 53, 55, 60], Account.order(:credit_limit).uniq.pluck(:credit_limit) + end + + def test_pluck_in_relation + company = Company.first + contract = company.contracts.create! + assert_equal [contract.id], company.contracts.pluck(:id) + end + + def test_pluck_with_serialization + t = Topic.create!(:content => { :foo => :bar }) + assert_equal [{:foo => :bar}], Topic.where(:id => t.id).pluck(:content) + end + + def test_pluck_with_qualified_column_name + assert_equal [1,2,3,4], Topic.order(:id).pluck("topics.id") + end + + def test_pluck_auto_table_name_prefix + c = Company.create!(:name => "test", :contracts => [Contract.new]) + assert_equal [c.id], Company.joins(:contracts).pluck(:id) + end + + def test_pluck_not_auto_table_name_prefix_if_column_joined + Company.create!(:name => "test", :contracts => [Contract.new(:developer_id => 7)]) + assert_equal [7], Company.joins(:contracts).pluck(:developer_id) + end + + def test_plucks_with_ids + assert_equal Company.all.map(&:id).sort, Company.ids.sort + end end diff --git a/activerecord/test/cases/callbacks_test.rb b/activerecord/test/cases/callbacks_test.rb index 7f4d25790b..7690769226 100644 --- a/activerecord/test/cases/callbacks_test.rb +++ b/activerecord/test/cases/callbacks_test.rb @@ -1,7 +1,7 @@ require "cases/helper" class CallbackDeveloper < ActiveRecord::Base - set_table_name 'developers' + self.table_name = 'developers' class << self def callback_string(callback_method) @@ -48,7 +48,7 @@ class CallbackDeveloperWithFalseValidation < CallbackDeveloper end class ParentDeveloper < ActiveRecord::Base - set_table_name 'developers' + self.table_name = 'developers' attr_accessor :after_save_called before_validation {|record| record.after_save_called = true} end @@ -58,7 +58,7 @@ class ChildDeveloper < ParentDeveloper end class RecursiveCallbackDeveloper < ActiveRecord::Base - set_table_name 'developers' + self.table_name = 'developers' before_save :on_before_save after_save :on_after_save @@ -79,7 +79,7 @@ class RecursiveCallbackDeveloper < ActiveRecord::Base end class ImmutableDeveloper < ActiveRecord::Base - set_table_name 'developers' + self.table_name = 'developers' validates_inclusion_of :salary, :in => 50000..200000 @@ -98,7 +98,7 @@ class ImmutableDeveloper < ActiveRecord::Base end class ImmutableMethodDeveloper < ActiveRecord::Base - set_table_name 'developers' + self.table_name = 'developers' validates_inclusion_of :salary, :in => 50000..200000 @@ -118,7 +118,7 @@ class ImmutableMethodDeveloper < ActiveRecord::Base end class OnCallbacksDeveloper < ActiveRecord::Base - set_table_name 'developers' + self.table_name = 'developers' before_validation { history << :before_validation } before_validation(:on => :create){ history << :before_validation_on_create } @@ -138,7 +138,7 @@ class OnCallbacksDeveloper < ActiveRecord::Base end class CallbackCancellationDeveloper < ActiveRecord::Base - set_table_name 'developers' + self.table_name = 'developers' attr_reader :after_save_called, :after_create_called, :after_update_called, :after_destroy_called attr_accessor :cancel_before_save, :cancel_before_create, :cancel_before_update, :cancel_before_destroy diff --git a/activerecord/test/cases/coders/yaml_column_test.rb b/activerecord/test/cases/coders/yaml_column_test.rb index c7dcc21809..b874adc081 100644 --- a/activerecord/test/cases/coders/yaml_column_test.rb +++ b/activerecord/test/cases/coders/yaml_column_test.rb @@ -9,6 +9,13 @@ module ActiveRecord assert_equal Object, coder.object_class end + def test_type_mismatch_on_different_classes_on_dump + coder = YAMLColumn.new(Array) + assert_raises(SerializationTypeMismatch) do + coder.dump("a") + end + end + def test_type_mismatch_on_different_classes coder = YAMLColumn.new(Array) assert_raises(SerializationTypeMismatch) do diff --git a/activerecord/test/cases/column_definition_test.rb b/activerecord/test/cases/column_definition_test.rb index 14884e42af..a44b49466f 100644 --- a/activerecord/test/cases/column_definition_test.rb +++ b/activerecord/test/cases/column_definition_test.rb @@ -126,17 +126,20 @@ module ActiveRecord if current_adapter?(:PostgreSQLAdapter) def test_bigint_column_should_map_to_integer - bigint_column = PostgreSQLColumn.new('number', nil, "bigint") + oid = PostgreSQLAdapter::OID::Identity.new + bigint_column = PostgreSQLColumn.new('number', nil, oid, "bigint") assert_equal :integer, bigint_column.type end def test_smallint_column_should_map_to_integer - smallint_column = PostgreSQLColumn.new('number', nil, "smallint") + oid = PostgreSQLAdapter::OID::Identity.new + smallint_column = PostgreSQLColumn.new('number', nil, oid, "smallint") assert_equal :integer, smallint_column.type end def test_uuid_column_should_map_to_string - uuid_column = PostgreSQLColumn.new('unique_id', nil, "uuid") + oid = PostgreSQLAdapter::OID::Identity.new + uuid_column = PostgreSQLColumn.new('unique_id', nil, oid, "uuid") assert_equal :string, uuid_column.type end end diff --git a/activerecord/test/cases/column_test.rb b/activerecord/test/cases/column_test.rb new file mode 100644 index 0000000000..4fcf8a33a4 --- /dev/null +++ b/activerecord/test/cases/column_test.rb @@ -0,0 +1,53 @@ +require "cases/helper" + +module ActiveRecord + module ConnectionAdapters + class ColumnTest < ActiveRecord::TestCase + def test_type_cast_boolean + column = Column.new("field", nil, "boolean") + assert column.type_cast(true) + assert column.type_cast(1) + assert column.type_cast('1') + assert column.type_cast('t') + assert column.type_cast('T') + assert column.type_cast('true') + assert column.type_cast('TRUE') + assert column.type_cast('on') + assert column.type_cast('ON') + assert !column.type_cast(false) + assert !column.type_cast(0) + assert !column.type_cast('0') + assert !column.type_cast('f') + assert !column.type_cast('F') + assert !column.type_cast('false') + assert !column.type_cast('FALSE') + assert !column.type_cast('off') + assert !column.type_cast('OFF') + end + + def test_type_cast_integer + column = Column.new("field", nil, "integer") + assert_equal 1, column.type_cast(1) + assert_equal 1, column.type_cast('1') + assert_equal 1, column.type_cast('1ignore') + assert_equal 0, column.type_cast('bad1') + assert_equal 0, column.type_cast('bad') + assert_equal 1, column.type_cast(1.7) + assert_nil column.type_cast(nil) + end + + def test_type_cast_non_integer_to_integer + column = Column.new("field", nil, "integer") + assert_raises(NoMethodError) do + column.type_cast([]) + end + assert_raises(NoMethodError) do + column.type_cast(true) + end + assert_raises(NoMethodError) do + column.type_cast(false) + end + end + end + end +end diff --git a/activerecord/test/cases/connection_adapters/abstract_adapter_test.rb b/activerecord/test/cases/connection_adapters/abstract_adapter_test.rb new file mode 100644 index 0000000000..7dc6e8afcb --- /dev/null +++ b/activerecord/test/cases/connection_adapters/abstract_adapter_test.rb @@ -0,0 +1,54 @@ +require "cases/helper" + +module ActiveRecord + module ConnectionAdapters + class AbstractAdapterTest < ActiveRecord::TestCase + attr_reader :adapter + + def setup + @adapter = AbstractAdapter.new nil, nil + end + + def test_in_use? + # FIXME: change to refute in Rails 4.0 / mt + assert !adapter.in_use?, 'adapter is not in use' + assert adapter.lease, 'lease adapter' + assert adapter.in_use?, 'adapter is in use' + end + + def test_lease_twice + assert adapter.lease, 'should lease adapter' + assert !adapter.lease, 'should not lease adapter' + end + + def test_last_use + assert !adapter.last_use + adapter.lease + assert adapter.last_use + end + + def test_expire_mutates_in_use + assert adapter.lease, 'lease adapter' + assert adapter.in_use?, 'adapter is in use' + adapter.expire + assert !adapter.in_use?, 'adapter is in use' + end + + def test_close + pool = ConnectionPool.new(ConnectionSpecification.new({}, nil)) + pool.connections << adapter + adapter.pool = pool + + # Make sure the pool marks the connection in use + assert_equal adapter, pool.connection + assert adapter.in_use? + + # Close should put the adapter back in the pool + adapter.close + assert !adapter.in_use? + + assert_equal adapter, pool.connection + end + end + end +end diff --git a/activerecord/test/cases/connection_adapters/connection_handler_test.rb b/activerecord/test/cases/connection_adapters/connection_handler_test.rb index bd0d161838..dc99ac665c 100644 --- a/activerecord/test/cases/connection_adapters/connection_handler_test.rb +++ b/activerecord/test/cases/connection_adapters/connection_handler_test.rb @@ -8,6 +8,9 @@ module ActiveRecord @handler.establish_connection 'america', Base.connection_pool.spec @klass = Class.new do def self.name; 'america'; end + class << self + alias active_record_super superclass + end end @subklass = Class.new(@klass) do def self.name; 'north america'; end @@ -40,8 +43,6 @@ module ActiveRecord def test_retrieve_connection_pool_uses_superclass_pool_after_subclass_establish_and_remove @handler.establish_connection 'north america', Base.connection_pool.spec - assert_not_same @handler.retrieve_connection_pool(@klass), - @handler.retrieve_connection_pool(@subklass) @handler.remove_connection @subklass assert_same @handler.retrieve_connection_pool(@klass), diff --git a/activerecord/test/cases/connection_adapters/connection_specification_test.rb b/activerecord/test/cases/connection_adapters/connection_specification_test.rb new file mode 100644 index 0000000000..ea2196cda2 --- /dev/null +++ b/activerecord/test/cases/connection_adapters/connection_specification_test.rb @@ -0,0 +1,12 @@ +require "cases/helper" + +module ActiveRecord + module ConnectionAdapters + class ConnectionSpecificationTest < ActiveRecord::TestCase + def test_dup_deep_copy_config + spec = ConnectionSpecification.new({ :a => :b }, "bar") + assert_not_equal(spec.config.object_id, spec.dup.config.object_id) + end + end + end +end diff --git a/activerecord/test/cases/connection_adapters/schema_cache_test.rb b/activerecord/test/cases/connection_adapters/schema_cache_test.rb index 79e842f5e1..541e983758 100644 --- a/activerecord/test/cases/connection_adapters/schema_cache_test.rb +++ b/activerecord/test/cases/connection_adapters/schema_cache_test.rb @@ -6,12 +6,6 @@ module ActiveRecord def setup connection = ActiveRecord::Base.connection @cache = SchemaCache.new connection - - if in_memory_db? - connection.create_table :posts do |t| - t.integer :cololumn - end - end end def test_primary_key @@ -19,16 +13,7 @@ module ActiveRecord end def test_primary_key_for_non_existent_table - assert_equal 'id', @cache.primary_keys['omgponies'] - end - - def test_primary_key_is_set_on_columns - posts_columns = @cache.columns_hash['posts'] - assert posts_columns['id'].primary - - (posts_columns.keys - ['id']).each do |key| - assert !posts_columns[key].primary - end + assert_nil @cache.primary_keys['omgponies'] end def test_caches_columns @@ -41,15 +26,34 @@ module ActiveRecord assert_equal columns_hash, @cache.columns_hash['posts'] end - def test_clearing_column_cache + def test_clearing @cache.columns['posts'] @cache.columns_hash['posts'] + @cache.tables['posts'] + @cache.primary_keys['posts'] @cache.clear! assert_equal 0, @cache.columns.size assert_equal 0, @cache.columns_hash.size + assert_equal 0, @cache.tables.size + assert_equal 0, @cache.primary_keys.size + end + + def test_dump_and_load + @cache.columns['posts'] + @cache.columns_hash['posts'] + @cache.tables['posts'] + @cache.primary_keys['posts'] + + @cache = Marshal.load(Marshal.dump(@cache)) + + assert_equal 12, @cache.columns['posts'].size + assert_equal 12, @cache.columns_hash['posts'].size + assert @cache.tables['posts'] + assert_equal 'id', @cache.primary_keys['posts'] end + end end end diff --git a/activerecord/test/cases/connection_management_test.rb b/activerecord/test/cases/connection_management_test.rb index f554ceef35..fe1b40d884 100644 --- a/activerecord/test/cases/connection_management_test.rb +++ b/activerecord/test/cases/connection_management_test.rb @@ -1,4 +1,5 @@ require "cases/helper" +require "rack" module ActiveRecord module ConnectionAdapters @@ -25,38 +26,25 @@ module ActiveRecord assert ActiveRecord::Base.connection_handler.active_connections? end - class FakeBase < ActiveRecord::Base - def self.establish_connection spec - String === spec ? super : spec - end - end + def test_connection_pool_per_pid + return skip('must support fork') unless Process.respond_to?(:fork) - def test_url_host_no_db - spec = FakeBase.establish_connection 'postgres://foo?encoding=utf8' - assert_equal({ - :adapter => "postgresql", - :database => "", - :host => "foo", - :encoding => "utf8" }, spec) - end + object_id = ActiveRecord::Base.connection.object_id - def test_url_host_db - spec = FakeBase.establish_connection 'postgres://foo/bar?encoding=utf8' - assert_equal({ - :adapter => "postgresql", - :database => "bar", - :host => "foo", - :encoding => "utf8" }, spec) - end + rd, wr = IO.pipe + + pid = fork { + rd.close + wr.write Marshal.dump ActiveRecord::Base.connection.object_id + wr.close + exit! + } + + wr.close - def test_url_port - spec = FakeBase.establish_connection 'postgres://foo:123?encoding=utf8' - assert_equal({ - :adapter => "postgresql", - :database => "", - :port => 123, - :host => "foo", - :encoding => "utf8" }, spec) + Process.waitpid pid + assert_not_equal object_id, Marshal.load(rd.read) + rd.close end def test_app_delegation @@ -114,9 +102,10 @@ module ActiveRecord test "proxy is polite to it's body and responds to it" do body = Class.new(String) { def to_path; "/path"; end }.new - proxy = ConnectionManagement::Proxy.new(body) - assert proxy.respond_to?(:to_path) - assert_equal proxy.to_path, "/path" + app = lambda { |_| [200, {}, body] } + response_body = ConnectionManagement.new(app).call(@env)[2] + assert response_body.respond_to?(:to_path) + assert_equal response_body.to_path, "/path" end end end diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb index 1550fa5530..8dc9f761c2 100644 --- a/activerecord/test/cases/connection_pool_test.rb +++ b/activerecord/test/cases/connection_pool_test.rb @@ -3,7 +3,11 @@ require "cases/helper" module ActiveRecord module ConnectionAdapters class ConnectionPoolTest < ActiveRecord::TestCase + attr_reader :pool + def setup + super + # Keep a duplicate pool so we do not bother others @pool = ConnectionPool.new ActiveRecord::Base.connection_pool.spec @@ -18,36 +22,161 @@ module ActiveRecord end end - def test_active_connection? - assert !@pool.active_connection? - assert @pool.connection - assert @pool.active_connection? - @pool.release_connection - assert !@pool.active_connection? + def teardown + super + @pool.disconnect! end - def test_clear_stale_cached_connections! - pool = ConnectionPool.new ActiveRecord::Base.connection_pool.spec + def active_connections(pool) + pool.connections.find_all(&:in_use?) + end + + def test_checkout_after_close + connection = pool.connection + assert connection.in_use? + + connection.close + assert !connection.in_use? + + assert pool.connection.in_use? + end + + def test_released_connection_moves_between_threads + thread_conn = nil + + Thread.new { + pool.with_connection do |conn| + thread_conn = conn + end + }.join + + assert thread_conn + + Thread.new { + pool.with_connection do |conn| + assert_equal thread_conn, conn + end + }.join + end - threads = [ - Thread.new { pool.connection }, - Thread.new { pool.connection }] + def test_with_connection + assert_equal 0, active_connections(pool).size - threads.map { |t| t.join } + main_thread = pool.connection + assert_equal 1, active_connections(pool).size - pool.extend Module.new { - attr_accessor :checkins - def checkin conn - @checkins << conn - conn.object_id + Thread.new { + pool.with_connection do |conn| + assert conn + assert_equal 2, active_connections(pool).size end - } - pool.checkins = [] + assert_equal 1, active_connections(pool).size + }.join + + main_thread.close + assert_equal 0, active_connections(pool).size + end + + def test_active_connection_in_use + assert !pool.active_connection? + main_thread = pool.connection + + assert pool.active_connection? + + main_thread.close + + assert !pool.active_connection? + end + + def test_full_pool_exception + assert_raises(PoolFullError) do + (@pool.size + 1).times do + @pool.checkout + end + end + end + + def test_full_pool_blocks + cs = @pool.size.times.map { @pool.checkout } + t = Thread.new { @pool.checkout } - cleared_threads = pool.clear_stale_cached_connections! - assert((cleared_threads - threads.map { |x| x.object_id }).empty?, - "threads should have been removed") - assert_equal pool.checkins.length, threads.length + # make sure our thread is in the timeout section + Thread.pass until t.status == "sleep" + + connection = cs.first + connection.close + assert_equal connection, t.join.value + end + + def test_removing_releases_latch + cs = @pool.size.times.map { @pool.checkout } + t = Thread.new { @pool.checkout } + + # make sure our thread is in the timeout section + Thread.pass until t.status == "sleep" + + connection = cs.first + @pool.remove connection + assert_respond_to t.join.value, :execute + end + + def test_reap_and_active + @pool.checkout + @pool.checkout + @pool.checkout + @pool.timeout = 0 + + connections = @pool.connections.dup + + @pool.reap + + assert_equal connections.length, @pool.connections.length + end + + def test_reap_inactive + @pool.checkout + @pool.checkout + @pool.checkout + @pool.timeout = 0 + + connections = @pool.connections.dup + connections.each do |conn| + conn.extend(Module.new { def active?; false; end; }) + end + + @pool.reap + + assert_equal 0, @pool.connections.length + ensure + connections.each(&:close) + end + + def test_remove_connection + conn = @pool.checkout + assert conn.in_use? + + length = @pool.connections.length + @pool.remove conn + assert conn.in_use? + assert_equal(length - 1, @pool.connections.length) + ensure + conn.close + end + + def test_remove_connection_for_thread + conn = @pool.connection + @pool.remove conn + assert_not_equal(conn, @pool.connection) + ensure + conn.close if conn + end + + def test_active_connection? + assert !@pool.active_connection? + assert @pool.connection + assert @pool.active_connection? + @pool.release_connection + assert !@pool.active_connection? end def test_checkout_behaviour @@ -59,24 +188,16 @@ module ActiveRecord threads << Thread.new(i) do |pool_count| connection = pool.connection assert_not_nil connection + connection.close end end - threads.each {|t| t.join} + threads.each(&:join) Thread.new do - threads.each do |t| - thread_ids = pool.instance_variable_get(:@reserved_connections).keys - assert thread_ids.include?(t.object_id) - end - - pool.connection - threads.each do |t| - thread_ids = pool.instance_variable_get(:@reserved_connections).keys - assert !thread_ids.include?(t.object_id) - end - end.join() - + assert pool.connection + pool.connection.close + end.join end def test_automatic_reconnect= diff --git a/activerecord/test/cases/connection_specification/resolver_test.rb b/activerecord/test/cases/connection_specification/resolver_test.rb new file mode 100644 index 0000000000..e6cb1b9521 --- /dev/null +++ b/activerecord/test/cases/connection_specification/resolver_test.rb @@ -0,0 +1,41 @@ +require "cases/helper" + +module ActiveRecord + module ConnectionAdapters + class ConnectionSpecification + class ResolverTest < ActiveRecord::TestCase + def resolve(spec) + Resolver.new(spec, {}).spec.config + end + + def test_url_host_no_db + spec = resolve 'mysql://foo?encoding=utf8' + assert_equal({ + :adapter => "mysql", + :database => "", + :host => "foo", + :encoding => "utf8" }, spec) + end + + def test_url_host_db + spec = resolve 'mysql://foo/bar?encoding=utf8' + assert_equal({ + :adapter => "mysql", + :database => "bar", + :host => "foo", + :encoding => "utf8" }, spec) + end + + def test_url_port + spec = resolve 'mysql://foo:123?encoding=utf8' + assert_equal({ + :adapter => "mysql", + :database => "", + :port => 123, + :host => "foo", + :encoding => "utf8" }, spec) + end + end + end + end +end diff --git a/activerecord/test/cases/counter_cache_test.rb b/activerecord/test/cases/counter_cache_test.rb index 3ed96a3ec8..cd3d19e783 100644 --- a/activerecord/test/cases/counter_cache_test.rb +++ b/activerecord/test/cases/counter_cache_test.rb @@ -6,9 +6,11 @@ require 'models/engine' require 'models/reply' require 'models/category' require 'models/categorization' +require 'models/dog' +require 'models/dog_lover' class CounterCacheTest < ActiveRecord::TestCase - fixtures :topics, :categories, :categorizations, :cars + fixtures :topics, :categories, :categorizations, :cars, :dogs, :dog_lovers class ::SpecialTopic < ::Topic has_many :special_replies, :foreign_key => 'parent_id' @@ -61,7 +63,7 @@ class CounterCacheTest < ActiveRecord::TestCase end end - test "reset counter should with belongs_to which has class_name" do + test "reset counter with belongs_to which has class_name" do car = cars(:honda) assert_nothing_raised do Car.reset_counters(car.id, :engines) @@ -71,6 +73,20 @@ class CounterCacheTest < ActiveRecord::TestCase end end + test "reset the right counter if two have the same class_name" do + david = dog_lovers(:david) + + DogLover.increment_counter(:bred_dogs_count, david.id) + DogLover.increment_counter(:trained_dogs_count, david.id) + + assert_difference 'david.reload.bred_dogs_count', -1 do + DogLover.reset_counters(david.id, :bred_dogs) + end + assert_difference 'david.reload.trained_dogs_count', -1 do + DogLover.reset_counters(david.id, :trained_dogs) + end + end + test "update counter with initial null value" do category = categories(:general) assert_equal 2, category.categorizations.count diff --git a/activerecord/test/cases/custom_locking_test.rb b/activerecord/test/cases/custom_locking_test.rb index d63ecdbcc5..42ef51ef3e 100644 --- a/activerecord/test/cases/custom_locking_test.rb +++ b/activerecord/test/cases/custom_locking_test.rb @@ -9,7 +9,7 @@ module ActiveRecord if current_adapter?(:MysqlAdapter, :Mysql2Adapter) assert_match 'SHARE MODE', Person.lock('LOCK IN SHARE MODE').to_sql assert_sql(/LOCK IN SHARE MODE/) do - Person.find(1, :lock => 'LOCK IN SHARE MODE') + Person.scoped(:lock => 'LOCK IN SHARE MODE').find(1) end end end diff --git a/activerecord/test/cases/deprecated_finder_test.rb b/activerecord/test/cases/deprecated_finder_test.rb deleted file mode 100644 index 2afc91b769..0000000000 --- a/activerecord/test/cases/deprecated_finder_test.rb +++ /dev/null @@ -1,30 +0,0 @@ -require "cases/helper" -require 'models/entrant' - -class DeprecatedFinderTest < ActiveRecord::TestCase - fixtures :entrants - - def test_deprecated_find_all_was_removed - assert_raise(NoMethodError) { Entrant.find_all } - end - - def test_deprecated_find_first_was_removed - assert_raise(NoMethodError) { Entrant.find_first } - end - - def test_deprecated_find_on_conditions_was_removed - assert_raise(NoMethodError) { Entrant.find_on_conditions } - end - - def test_count - assert_equal(0, Entrant.count(:conditions => "id > 3")) - assert_equal(1, Entrant.count(:conditions => ["id > ?", 2])) - assert_equal(2, Entrant.count(:conditions => ["id > ?", 1])) - end - - def test_count_by_sql - assert_equal(0, Entrant.count_by_sql("SELECT COUNT(*) FROM entrants WHERE id > 3")) - assert_equal(1, Entrant.count_by_sql(["SELECT COUNT(*) FROM entrants WHERE id > ?", 2])) - assert_equal(2, Entrant.count_by_sql(["SELECT COUNT(*) FROM entrants WHERE id > ?", 1])) - end -end diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb index b1ce846218..2650040a80 100644 --- a/activerecord/test/cases/dirty_test.rb +++ b/activerecord/test/cases/dirty_test.rb @@ -288,7 +288,7 @@ class DirtyTest < ActiveRecord::TestCase with_partial_updates Pirate, false do assert_queries(2) { 2.times { pirate.save! } } - Pirate.update_all({ :updated_on => old_updated_on }, :id => pirate.id) + Pirate.where(id: pirate.id).update_all(:updated_on => old_updated_on) end with_partial_updates Pirate, true do @@ -306,7 +306,7 @@ class DirtyTest < ActiveRecord::TestCase with_partial_updates Person, false do assert_queries(2) { 2.times { person.save! } } - Person.update_all({ :first_name => 'baz' }, :id => person.id) + Person.where(id: person.id).update_all(:first_name => 'baz') end with_partial_updates Person, true do @@ -497,6 +497,20 @@ class DirtyTest < ActiveRecord::TestCase assert !pirate.previous_changes.key?('created_on') end + if ActiveRecord::Base.connection.supports_migrations? + class Testings < ActiveRecord::Base; end + def test_field_named_field + ActiveRecord::Base.connection.create_table :testings do |t| + t.string :field + end + assert_nothing_raised do + Testings.new.attributes + end + ensure + ActiveRecord::Base.connection.drop_table :testings rescue nil + end + end + private def with_partial_updates(klass, on = true) old = klass.partial_updates? diff --git a/activerecord/test/cases/dup_test.rb b/activerecord/test/cases/dup_test.rb index 0236f9b0a1..9705a11387 100644 --- a/activerecord/test/cases/dup_test.rb +++ b/activerecord/test/cases/dup_test.rb @@ -99,5 +99,13 @@ module ActiveRecord assert_not_nil new_topic.created_at end + def test_dup_after_initialize_callbacks + topic = Topic.new + assert Topic.after_initialize_called + Topic.after_initialize_called = false + topic.dup + assert Topic.after_initialize_called + end + end end diff --git a/activerecord/test/cases/dynamic_finder_match_test.rb b/activerecord/test/cases/dynamic_finder_match_test.rb deleted file mode 100644 index e576870317..0000000000 --- a/activerecord/test/cases/dynamic_finder_match_test.rb +++ /dev/null @@ -1,98 +0,0 @@ -require "cases/helper" - -module ActiveRecord - class DynamicFinderMatchTest < ActiveRecord::TestCase - def test_find_or_create_by - match = DynamicFinderMatch.match("find_or_create_by_age_and_sex_and_location") - assert_not_nil match - assert !match.finder? - assert match.instantiator? - assert_equal :first, match.finder - assert_equal :create, match.instantiator - assert_equal %w(age sex location), match.attribute_names - end - - def test_find_or_initialize_by - match = DynamicFinderMatch.match("find_or_initialize_by_age_and_sex_and_location") - assert_not_nil match - assert !match.finder? - assert match.instantiator? - assert_equal :first, match.finder - assert_equal :new, match.instantiator - assert_equal %w(age sex location), match.attribute_names - end - - def test_find_no_match - assert_nil DynamicFinderMatch.match("not_a_finder") - end - - def find_by_bang - match = DynamicFinderMatch.match("find_by_age_and_sex_and_location!") - assert_not_nil match - assert match.finder? - assert match.bang? - assert_equal :first, match.finder - assert_equal %w(age sex location), match.attribute_names - end - - def test_find_by - match = DynamicFinderMatch.match("find_by_age_and_sex_and_location") - assert_not_nil match - assert match.finder? - assert_equal :first, match.finder - assert_equal %w(age sex location), match.attribute_names - end - - def test_find_by_with_symbol - m = DynamicFinderMatch.match(:find_by_foo) - assert_equal :first, m.finder - assert_equal %w{ foo }, m.attribute_names - end - - def test_find_all_by_with_symbol - m = DynamicFinderMatch.match(:find_all_by_foo) - assert_equal :all, m.finder - assert_equal %w{ foo }, m.attribute_names - end - - def test_find_all_by - match = DynamicFinderMatch.match("find_all_by_age_and_sex_and_location") - assert_not_nil match - assert match.finder? - assert_equal :all, match.finder - assert_equal %w(age sex location), match.attribute_names - end - - def test_find_last_by - m = DynamicFinderMatch.match(:find_last_by_foo) - assert_equal :last, m.finder - assert_equal %w{ foo }, m.attribute_names - end - - def test_find_by! - m = DynamicFinderMatch.match(:find_by_foo!) - assert_equal :first, m.finder - assert m.bang?, 'should be banging' - assert_equal %w{ foo }, m.attribute_names - end - - def test_find_or_create - m = DynamicFinderMatch.match(:find_or_create_by_foo) - assert_equal :first, m.finder - assert_equal %w{ foo }, m.attribute_names - assert_equal :create, m.instantiator - end - - def test_find_or_initialize - m = DynamicFinderMatch.match(:find_or_initialize_by_foo) - assert_equal :first, m.finder - assert_equal %w{ foo }, m.attribute_names - assert_equal :new, m.instantiator - end - - def test_garbage - assert !DynamicFinderMatch.match(:fooo), 'should be false' - assert !DynamicFinderMatch.match(:find_by), 'should be false' - end - end -end diff --git a/activerecord/test/cases/explain_subscriber_test.rb b/activerecord/test/cases/explain_subscriber_test.rb new file mode 100644 index 0000000000..e118add44c --- /dev/null +++ b/activerecord/test/cases/explain_subscriber_test.rb @@ -0,0 +1,48 @@ +require 'cases/helper' + +if ActiveRecord::Base.connection.supports_explain? + class ExplainSubscriberTest < ActiveRecord::TestCase + SUBSCRIBER = ActiveRecord::ExplainSubscriber.new + + def test_collects_nothing_if_available_queries_for_explain_is_nil + with_queries(nil) do + SUBSCRIBER.call + assert_nil Thread.current[:available_queries_for_explain] + end + end + + def test_collects_nothing_if_the_payload_has_an_exception + with_queries([]) do |queries| + SUBSCRIBER.call(:exception => Exception.new) + assert queries.empty? + end + end + + def test_collects_nothing_for_ignored_payloads + with_queries([]) do |queries| + ActiveRecord::ExplainSubscriber::IGNORED_PAYLOADS.each do |ip| + SUBSCRIBER.call(:name => ip) + end + assert queries.empty? + end + end + + def test_collects_pairs_of_queries_and_binds + sql = 'select 1 from users' + binds = [1, 2] + with_queries([]) do |queries| + SUBSCRIBER.call(:name => 'SQL', :sql => sql, :binds => binds) + assert_equal 1, queries.size + assert_equal sql, queries[0][0] + assert_equal binds, queries[0][1] + end + end + + def with_queries(queries) + Thread.current[:available_queries_for_explain] = queries + yield queries + ensure + Thread.current[:available_queries_for_explain] = nil + end + end +end
\ No newline at end of file diff --git a/activerecord/test/cases/explain_test.rb b/activerecord/test/cases/explain_test.rb new file mode 100644 index 0000000000..cb7781f8e7 --- /dev/null +++ b/activerecord/test/cases/explain_test.rb @@ -0,0 +1,117 @@ +require 'cases/helper' +require 'models/car' +require 'active_support/core_ext/string/strip' + +if ActiveRecord::Base.connection.supports_explain? + class ExplainTest < ActiveRecord::TestCase + fixtures :cars + + def base + ActiveRecord::Base + end + + def connection + base.connection + end + + def test_logging_query_plan_with_logger + base.logger.expects(:warn).with do |message| + message.starts_with?('EXPLAIN for:') + end + + with_threshold(0) do + Car.where(:name => 'honda').all + end + end + + def test_logging_query_plan_without_logger + original = base.logger + base.logger = nil + + class << base.logger + def warn; raise "Should not be called" end + end + + with_threshold(0) do + car = Car.where(:name => 'honda').first + assert_equal 'honda', car.name + end + ensure + base.logger = original + end + + def test_collect_queries_for_explain + base.auto_explain_threshold_in_seconds = nil + queries = Thread.current[:available_queries_for_explain] = [] + + with_threshold(0) do + Car.where(:name => 'honda').all + end + + sql, binds = queries[0] + assert_match "SELECT", sql + assert_match "honda", sql + assert_equal [], binds + ensure + Thread.current[:available_queries_for_explain] = nil + end + + def test_collecting_queries_for_explain + result, queries = ActiveRecord::Base.collecting_queries_for_explain do + Car.where(:name => 'honda').all + end + + sql, binds = queries[0] + assert_match "SELECT", sql + assert_match "honda", sql + assert_equal [], binds + assert_equal [cars(:honda)], result + end + + def test_exec_explain_with_no_binds + sqls = %w(foo bar) + binds = [[], []] + queries = sqls.zip(binds) + + connection.stubs(:explain).returns('query plan foo', 'query plan bar') + expected = sqls.map {|sql| "EXPLAIN for: #{sql}\nquery plan #{sql}"}.join("\n") + assert_equal expected, base.exec_explain(queries) + end + + def test_exec_explain_with_binds + cols = [Object.new, Object.new] + cols[0].expects(:name).returns('wadus') + cols[1].expects(:name).returns('chaflan') + + sqls = %w(foo bar) + binds = [[[cols[0], 1]], [[cols[1], 2]]] + queries = sqls.zip(binds) + + connection.stubs(:explain).returns("query plan foo\n", "query plan bar\n") + expected = <<-SQL.strip_heredoc + EXPLAIN for: #{sqls[0]} [["wadus", 1]] + query plan foo + + EXPLAIN for: #{sqls[1]} [["chaflan", 2]] + query plan bar + SQL + assert_equal expected, base.exec_explain(queries) + end + + def test_silence_auto_explain + base.expects(:collecting_sqls_for_explain).never + base.logger.expects(:warn).never + base.silence_auto_explain do + with_threshold(0) { Car.all } + end + end + + def with_threshold(threshold) + current_threshold = base.auto_explain_threshold_in_seconds + base.auto_explain_threshold_in_seconds = threshold + yield + ensure + base.auto_explain_threshold_in_seconds = current_threshold + end + end +end diff --git a/activerecord/test/cases/finder_respond_to_test.rb b/activerecord/test/cases/finder_respond_to_test.rb index 235805a67c..810c1500cc 100644 --- a/activerecord/test/cases/finder_respond_to_test.rb +++ b/activerecord/test/cases/finder_respond_to_test.rb @@ -56,6 +56,16 @@ class FinderRespondToTest < ActiveRecord::TestCase assert_respond_to Topic, :find_or_create_by_title_and_author_name end + def test_should_respond_to_find_or_create_from_one_attribute_bang + ensure_topic_method_is_not_cached(:find_or_create_by_title!) + assert_respond_to Topic, :find_or_create_by_title! + end + + def test_should_respond_to_find_or_create_from_two_attributes_bang + ensure_topic_method_is_not_cached(:find_or_create_by_title_and_author_name!) + assert_respond_to Topic, :find_or_create_by_title_and_author_name! + end + def test_should_not_respond_to_find_by_one_missing_attribute assert !Topic.respond_to?(:find_by_undertitle) end diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index 05c4b15407..54801bd101 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -82,12 +82,6 @@ class FinderTest < ActiveRecord::TestCase Address.new(existing_address.street + "1", existing_address.city, existing_address.country)) end - def test_exists_with_scoped_include - Developer.send(:with_scope, :find => { :include => :projects, :order => "projects.name" }) do - assert Developer.exists? - end - end - def test_exists_does_not_instantiate_records Developer.expects(:instantiate).never Developer.exists? @@ -104,14 +98,14 @@ class FinderTest < ActiveRecord::TestCase end def test_find_by_ids_with_limit_and_offset - assert_equal 2, Entrant.find([1,3,2], :limit => 2).size - assert_equal 1, Entrant.find([1,3,2], :limit => 3, :offset => 2).size + assert_equal 2, Entrant.scoped(:limit => 2).find([1,3,2]).size + assert_equal 1, Entrant.scoped(:limit => 3, :offset => 2).find([1,3,2]).size # Also test an edge case: If you have 11 results, and you set a # limit of 3 and offset of 9, then you should find that there # will be only 2 results, regardless of the limit. - devs = Developer.find :all - last_devs = Developer.find devs.map(&:id), :limit => 3, :offset => 9 + devs = Developer.all + last_devs = Developer.scoped(:limit => 3, :offset => 9).find devs.map(&:id) assert_equal 2, last_devs.size end @@ -123,56 +117,6 @@ class FinderTest < ActiveRecord::TestCase assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1, 2, 45) } end - def test_find_all_with_limit - assert_equal(2, Entrant.find(:all, :limit => 2).size) - assert_equal(0, Entrant.find(:all, :limit => 0).size) - end - - def test_find_all_with_prepared_limit_and_offset - entrants = Entrant.find(:all, :order => "id ASC", :limit => 2, :offset => 1) - - assert_equal(2, entrants.size) - assert_equal(entrants(:second).name, entrants.first.name) - - assert_equal 3, Entrant.count - entrants = Entrant.find(:all, :order => "id ASC", :limit => 2, :offset => 2) - assert_equal(1, entrants.size) - assert_equal(entrants(:third).name, entrants.first.name) - end - - def test_find_all_with_limit_and_offset_and_multiple_order_clauses - first_three_posts = Post.find :all, :order => 'author_id, id', :limit => 3, :offset => 0 - second_three_posts = Post.find :all, :order => ' author_id,id ', :limit => 3, :offset => 3 - third_three_posts = Post.find :all, :order => ' author_id, id ', :limit => 3, :offset => 6 - last_posts = Post.find :all, :order => ' author_id, id ', :limit => 3, :offset => 9 - - assert_equal [[0,3],[1,1],[1,2]], first_three_posts.map { |p| [p.author_id, p.id] } - assert_equal [[1,4],[1,5],[1,6]], second_three_posts.map { |p| [p.author_id, p.id] } - assert_equal [[2,7],[2,9],[2,11]], third_three_posts.map { |p| [p.author_id, p.id] } - assert_equal [[3,8],[3,10]], last_posts.map { |p| [p.author_id, p.id] } - end - - - def test_find_with_group - developers = Developer.find(:all, :group => "salary", :select => "salary") - assert_equal 4, developers.size - assert_equal 4, developers.map(&:salary).uniq.size - end - - def test_find_with_group_and_having - developers = Developer.find(:all, :group => "salary", :having => "sum(salary) > 10000", :select => "salary") - assert_equal 3, developers.size - assert_equal 3, developers.map(&:salary).uniq.size - assert developers.all? { |developer| developer.salary > 10000 } - end - - def test_find_with_group_and_sanitized_having - developers = Developer.find(:all, :group => "salary", :having => ["sum(salary) > ?", 10000], :select => "salary") - assert_equal 3, developers.size - assert_equal 3, developers.map(&:salary).uniq.size - assert developers.all? { |developer| developer.salary > 10000 } - end - def test_find_with_group_and_sanitized_having_method developers = Developer.group(:salary).having("sum(salary) > ?", 10000).select('salary').all assert_equal 3, developers.size @@ -199,14 +143,24 @@ class FinderTest < ActiveRecord::TestCase assert_equal [Account], accounts.collect(&:class).uniq end - def test_find_first - first = Topic.find(:first, :conditions => "title = 'The First Topic'") - assert_equal(topics(:first).title, first.title) + def test_take + assert_equal topics(:first), Topic.take + end + + def test_take_failing + assert_nil Topic.where("title = 'This title does not exist'").take + end + + def test_take_bang_present + assert_nothing_raised do + assert_equal topics(:second), Topic.where("title = 'The Second Topic of the day'").take! + end end - def test_find_first_failing - first = Topic.find(:first, :conditions => "title = 'The First Topic!'") - assert_nil(first) + def test_take_bang_missing + assert_raises ActiveRecord::RecordNotFound do + Topic.where("title = 'This title does not exist'").take! + end end def test_first @@ -229,6 +183,12 @@ class FinderTest < ActiveRecord::TestCase end end + def test_first_have_primary_key_order_by_default + expected = topics(:first) + expected.touch # PostgreSQL changes the default order if no order clause is used + assert_equal expected, Topic.first + end + def test_model_class_responds_to_first_bang assert Topic.first! Topic.delete_all @@ -257,7 +217,8 @@ class FinderTest < ActiveRecord::TestCase end end - def test_first_and_last_with_integer_should_use_sql_limit + def test_take_and_first_and_last_with_integer_should_use_sql_limit + assert_sql(/LIMIT 3|ROWNUM <= 3/) { Topic.take(3).entries } assert_sql(/LIMIT 2|ROWNUM <= 2/) { Topic.first(2).entries } assert_sql(/LIMIT 5|ROWNUM <= 5/) { Topic.last(5).entries } end @@ -278,7 +239,8 @@ class FinderTest < ActiveRecord::TestCase assert_no_match(/LIMIT/, query.first) end - def test_first_and_last_with_integer_should_return_an_array + def test_take_and_first_and_last_with_integer_should_return_an_array + assert_kind_of Array, Topic.take(5) assert_kind_of Array, Topic.first(5) assert_kind_of Array, Topic.last(5) end @@ -292,7 +254,7 @@ class FinderTest < ActiveRecord::TestCase end def test_find_only_some_columns - topic = Topic.find(1, :select => "author_name") + topic = Topic.scoped(:select => "author_name").find(1) assert_raise(ActiveModel::MissingAttributeError) {topic.title} assert_nil topic.read_attribute("title") assert_equal "David", topic.author_name @@ -302,36 +264,24 @@ class FinderTest < ActiveRecord::TestCase assert_respond_to topic, "author_name" end - def test_find_on_blank_conditions - [nil, " ", [], {}].each do |blank| - assert_nothing_raised { Topic.find(:first, :conditions => blank) } - end - end - - def test_find_on_blank_bind_conditions - [ [""], ["",{}] ].each do |blank| - assert_nothing_raised { Topic.find(:first, :conditions => blank) } - end - end - def test_find_on_array_conditions - assert Topic.find(1, :conditions => ["approved = ?", false]) - assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => ["approved = ?", true]) } + assert Topic.scoped(:where => ["approved = ?", false]).find(1) + assert_raise(ActiveRecord::RecordNotFound) { Topic.scoped(:where => ["approved = ?", true]).find(1) } end def test_find_on_hash_conditions - assert Topic.find(1, :conditions => { :approved => false }) - assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { :approved => true }) } + assert Topic.scoped(:where => { :approved => false }).find(1) + assert_raise(ActiveRecord::RecordNotFound) { Topic.scoped(:where => { :approved => true }).find(1) } end def test_find_on_hash_conditions_with_explicit_table_name - assert Topic.find(1, :conditions => { 'topics.approved' => false }) - assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { 'topics.approved' => true }) } + assert Topic.scoped(:where => { 'topics.approved' => false }).find(1) + assert_raise(ActiveRecord::RecordNotFound) { Topic.scoped(:where => { 'topics.approved' => true }).find(1) } end def test_find_on_hash_conditions_with_hashed_table_name - assert Topic.find(1, :conditions => {:topics => { :approved => false }}) - assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => {:topics => { :approved => true }}) } + assert Topic.scoped(:where => {:topics => { :approved => false }}).find(1) + assert_raise(ActiveRecord::RecordNotFound) { Topic.scoped(:where => {:topics => { :approved => true }}).find(1) } end def test_find_with_hash_conditions_on_joined_table @@ -341,16 +291,16 @@ class FinderTest < ActiveRecord::TestCase end def test_find_with_hash_conditions_on_joined_table_and_with_range - firms = DependentFirm.all :joins => :account, :conditions => {:name => 'RailsCore', :accounts => { :credit_limit => 55..60 }} + firms = DependentFirm.scoped :joins => :account, :where => {:name => 'RailsCore', :accounts => { :credit_limit => 55..60 }} assert_equal 1, firms.size assert_equal companies(:rails_core), firms.first end def test_find_on_hash_conditions_with_explicit_table_name_and_aggregate david = customers(:david) - assert Customer.find(david.id, :conditions => { 'customers.name' => david.name, :address => david.address }) + assert Customer.scoped(:where => { 'customers.name' => david.name, :address => david.address }).find(david.id) assert_raise(ActiveRecord::RecordNotFound) { - Customer.find(david.id, :conditions => { 'customers.name' => david.name + "1", :address => david.address }) + Customer.scoped(:where => { 'customers.name' => david.name + "1", :address => david.address }).find(david.id) } end @@ -359,71 +309,71 @@ class FinderTest < ActiveRecord::TestCase end def test_find_on_hash_conditions_with_range - assert_equal [1,2], Topic.find(:all, :conditions => { :id => 1..2 }).map(&:id).sort - assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { :id => 2..3 }) } + assert_equal [1,2], Topic.scoped(:where => { :id => 1..2 }).all.map(&:id).sort + assert_raise(ActiveRecord::RecordNotFound) { Topic.scoped(:where => { :id => 2..3 }).find(1) } end def test_find_on_hash_conditions_with_end_exclusive_range - assert_equal [1,2,3], Topic.find(:all, :conditions => { :id => 1..3 }).map(&:id).sort - assert_equal [1,2], Topic.find(:all, :conditions => { :id => 1...3 }).map(&:id).sort - assert_raise(ActiveRecord::RecordNotFound) { Topic.find(3, :conditions => { :id => 2...3 }) } + assert_equal [1,2,3], Topic.scoped(:where => { :id => 1..3 }).all.map(&:id).sort + assert_equal [1,2], Topic.scoped(:where => { :id => 1...3 }).all.map(&:id).sort + assert_raise(ActiveRecord::RecordNotFound) { Topic.scoped(:where => { :id => 2...3 }).find(3) } end def test_find_on_hash_conditions_with_multiple_ranges - assert_equal [1,2,3], Comment.find(:all, :conditions => { :id => 1..3, :post_id => 1..2 }).map(&:id).sort - assert_equal [1], Comment.find(:all, :conditions => { :id => 1..1, :post_id => 1..10 }).map(&:id).sort + assert_equal [1,2,3], Comment.scoped(:where => { :id => 1..3, :post_id => 1..2 }).all.map(&:id).sort + assert_equal [1], Comment.scoped(:where => { :id => 1..1, :post_id => 1..10 }).all.map(&:id).sort end def test_find_on_hash_conditions_with_array_of_integers_and_ranges - assert_equal [1,2,3,5,6,7,8,9], Comment.find(:all, :conditions => {:id => [1..2, 3, 5, 6..8, 9]}).map(&:id).sort + assert_equal [1,2,3,5,6,7,8,9], Comment.scoped(:where => {:id => [1..2, 3, 5, 6..8, 9]}).all.map(&:id).sort end def test_find_on_multiple_hash_conditions - assert Topic.find(1, :conditions => { :author_name => "David", :title => "The First Topic", :replies_count => 1, :approved => false }) - assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { :author_name => "David", :title => "The First Topic", :replies_count => 1, :approved => true }) } - assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { :author_name => "David", :title => "HHC", :replies_count => 1, :approved => false }) } - assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { :author_name => "David", :title => "The First Topic", :replies_count => 1, :approved => true }) } + assert Topic.scoped(:where => { :author_name => "David", :title => "The First Topic", :replies_count => 1, :approved => false }).find(1) + assert_raise(ActiveRecord::RecordNotFound) { Topic.scoped(:where => { :author_name => "David", :title => "The First Topic", :replies_count => 1, :approved => true }).find(1) } + assert_raise(ActiveRecord::RecordNotFound) { Topic.scoped(:where => { :author_name => "David", :title => "HHC", :replies_count => 1, :approved => false }).find(1) } + assert_raise(ActiveRecord::RecordNotFound) { Topic.scoped(:where => { :author_name => "David", :title => "The First Topic", :replies_count => 1, :approved => true }).find(1) } end def test_condition_interpolation - assert_kind_of Firm, Company.find(:first, :conditions => ["name = '%s'", "37signals"]) - assert_nil Company.find(:first, :conditions => ["name = '%s'", "37signals!"]) - assert_nil Company.find(:first, :conditions => ["name = '%s'", "37signals!' OR 1=1"]) - assert_kind_of Time, Topic.find(:first, :conditions => ["id = %d", 1]).written_on + assert_kind_of Firm, Company.where("name = '%s'", "37signals").first + assert_nil Company.scoped(:where => ["name = '%s'", "37signals!"]).first + assert_nil Company.scoped(:where => ["name = '%s'", "37signals!' OR 1=1"]).first + assert_kind_of Time, Topic.scoped(:where => ["id = %d", 1]).first.written_on end def test_condition_array_interpolation - assert_kind_of Firm, Company.find(:first, :conditions => ["name = '%s'", "37signals"]) - assert_nil Company.find(:first, :conditions => ["name = '%s'", "37signals!"]) - assert_nil Company.find(:first, :conditions => ["name = '%s'", "37signals!' OR 1=1"]) - assert_kind_of Time, Topic.find(:first, :conditions => ["id = %d", 1]).written_on + assert_kind_of Firm, Company.scoped(:where => ["name = '%s'", "37signals"]).first + assert_nil Company.scoped(:where => ["name = '%s'", "37signals!"]).first + assert_nil Company.scoped(:where => ["name = '%s'", "37signals!' OR 1=1"]).first + assert_kind_of Time, Topic.scoped(:where => ["id = %d", 1]).first.written_on end def test_condition_hash_interpolation - assert_kind_of Firm, Company.find(:first, :conditions => { :name => "37signals"}) - assert_nil Company.find(:first, :conditions => { :name => "37signals!"}) - assert_kind_of Time, Topic.find(:first, :conditions => {:id => 1}).written_on + assert_kind_of Firm, Company.scoped(:where => { :name => "37signals"}).first + assert_nil Company.scoped(:where => { :name => "37signals!"}).first + assert_kind_of Time, Topic.scoped(:where => {:id => 1}).first.written_on end def test_hash_condition_find_malformed assert_raise(ActiveRecord::StatementInvalid) { - Company.find(:first, :conditions => { :id => 2, :dhh => true }) + Company.scoped(:where => { :id => 2, :dhh => true }).first } end def test_hash_condition_find_with_escaped_characters Company.create("name" => "Ain't noth'n like' \#stuff") - assert Company.find(:first, :conditions => { :name => "Ain't noth'n like' \#stuff" }) + assert Company.scoped(:where => { :name => "Ain't noth'n like' \#stuff" }).first end def test_hash_condition_find_with_array - p1, p2 = Post.find(:all, :limit => 2, :order => 'id asc') - assert_equal [p1, p2], Post.find(:all, :conditions => { :id => [p1, p2] }, :order => 'id asc') - assert_equal [p1, p2], Post.find(:all, :conditions => { :id => [p1, p2.id] }, :order => 'id asc') + p1, p2 = Post.scoped(:limit => 2, :order => 'id asc').all + assert_equal [p1, p2], Post.scoped(:where => { :id => [p1, p2] }, :order => 'id asc').all + assert_equal [p1, p2], Post.scoped(:where => { :id => [p1, p2.id] }, :order => 'id asc').all end def test_hash_condition_find_with_nil - topic = Topic.find(:first, :conditions => { :last_read => nil } ) + topic = Topic.scoped(:where => { :last_read => nil } ).first assert_not_nil topic assert_nil topic.last_read end @@ -431,42 +381,42 @@ class FinderTest < ActiveRecord::TestCase def test_hash_condition_find_with_aggregate_having_one_mapping balance = customers(:david).balance assert_kind_of Money, balance - found_customer = Customer.find(:first, :conditions => {:balance => balance}) + found_customer = Customer.scoped(:where => {:balance => balance}).first assert_equal customers(:david), found_customer end def test_hash_condition_find_with_aggregate_attribute_having_same_name_as_field_and_key_value_being_aggregate gps_location = customers(:david).gps_location assert_kind_of GpsLocation, gps_location - found_customer = Customer.find(:first, :conditions => {:gps_location => gps_location}) + found_customer = Customer.scoped(:where => {:gps_location => gps_location}).first assert_equal customers(:david), found_customer end def test_hash_condition_find_with_aggregate_having_one_mapping_and_key_value_being_attribute_value balance = customers(:david).balance assert_kind_of Money, balance - found_customer = Customer.find(:first, :conditions => {:balance => balance.amount}) + found_customer = Customer.scoped(:where => {:balance => balance.amount}).first assert_equal customers(:david), found_customer end def test_hash_condition_find_with_aggregate_attribute_having_same_name_as_field_and_key_value_being_attribute_value gps_location = customers(:david).gps_location assert_kind_of GpsLocation, gps_location - found_customer = Customer.find(:first, :conditions => {:gps_location => gps_location.gps_location}) + found_customer = Customer.scoped(:where => {:gps_location => gps_location.gps_location}).first assert_equal customers(:david), found_customer end def test_hash_condition_find_with_aggregate_having_three_mappings address = customers(:david).address assert_kind_of Address, address - found_customer = Customer.find(:first, :conditions => {:address => address}) + found_customer = Customer.scoped(:where => {:address => address}).first assert_equal customers(:david), found_customer end def test_hash_condition_find_with_one_condition_being_aggregate_and_another_not address = customers(:david).address assert_kind_of Address, address - found_customer = Customer.find(:first, :conditions => {:address => address, :name => customers(:david).name}) + found_customer = Customer.scoped(:where => {:address => address, :name => customers(:david).name}).first assert_equal customers(:david), found_customer end @@ -474,7 +424,7 @@ class FinderTest < ActiveRecord::TestCase with_env_tz 'America/New_York' do with_active_record_default_timezone :local do topic = Topic.first - assert_equal topic, Topic.find(:first, :conditions => ['written_on = ?', topic.written_on.getutc]) + assert_equal topic, Topic.scoped(:where => ['written_on = ?', topic.written_on.getutc]).first end end end @@ -483,7 +433,7 @@ class FinderTest < ActiveRecord::TestCase with_env_tz 'America/New_York' do with_active_record_default_timezone :local do topic = Topic.first - assert_equal topic, Topic.find(:first, :conditions => {:written_on => topic.written_on.getutc}) + assert_equal topic, Topic.scoped(:where => {:written_on => topic.written_on.getutc}).first end end end @@ -492,7 +442,7 @@ class FinderTest < ActiveRecord::TestCase with_env_tz 'America/New_York' do with_active_record_default_timezone :utc do topic = Topic.first - assert_equal topic, Topic.find(:first, :conditions => ['written_on = ?', topic.written_on.getlocal]) + assert_equal topic, Topic.scoped(:where => ['written_on = ?', topic.written_on.getlocal]).first end end end @@ -501,32 +451,32 @@ class FinderTest < ActiveRecord::TestCase with_env_tz 'America/New_York' do with_active_record_default_timezone :utc do topic = Topic.first - assert_equal topic, Topic.find(:first, :conditions => {:written_on => topic.written_on.getlocal}) + assert_equal topic, Topic.scoped(:where => {:written_on => topic.written_on.getlocal}).first end end end def test_bind_variables - assert_kind_of Firm, Company.find(:first, :conditions => ["name = ?", "37signals"]) - assert_nil Company.find(:first, :conditions => ["name = ?", "37signals!"]) - assert_nil Company.find(:first, :conditions => ["name = ?", "37signals!' OR 1=1"]) - assert_kind_of Time, Topic.find(:first, :conditions => ["id = ?", 1]).written_on + assert_kind_of Firm, Company.scoped(:where => ["name = ?", "37signals"]).first + assert_nil Company.scoped(:where => ["name = ?", "37signals!"]).first + assert_nil Company.scoped(:where => ["name = ?", "37signals!' OR 1=1"]).first + assert_kind_of Time, Topic.scoped(:where => ["id = ?", 1]).first.written_on assert_raise(ActiveRecord::PreparedStatementInvalid) { - Company.find(:first, :conditions => ["id=? AND name = ?", 2]) + Company.scoped(:where => ["id=? AND name = ?", 2]).first } assert_raise(ActiveRecord::PreparedStatementInvalid) { - Company.find(:first, :conditions => ["id=?", 2, 3, 4]) + Company.scoped(:where => ["id=?", 2, 3, 4]).first } end def test_bind_variables_with_quotes Company.create("name" => "37signals' go'es agains") - assert Company.find(:first, :conditions => ["name = ?", "37signals' go'es agains"]) + assert Company.scoped(:where => ["name = ?", "37signals' go'es agains"]).first end def test_named_bind_variables_with_quotes Company.create("name" => "37signals' go'es agains") - assert Company.find(:first, :conditions => ["name = :name", {:name => "37signals' go'es agains"}]) + assert Company.scoped(:where => ["name = :name", {:name => "37signals' go'es agains"}]).first end def test_bind_arity @@ -544,10 +494,10 @@ class FinderTest < ActiveRecord::TestCase assert_nothing_raised { bind("'+00:00'", :foo => "bar") } - assert_kind_of Firm, Company.find(:first, :conditions => ["name = :name", { :name => "37signals" }]) - assert_nil Company.find(:first, :conditions => ["name = :name", { :name => "37signals!" }]) - assert_nil Company.find(:first, :conditions => ["name = :name", { :name => "37signals!' OR 1=1" }]) - assert_kind_of Time, Topic.find(:first, :conditions => ["id = :id", { :id => 1 }]).written_on + assert_kind_of Firm, Company.scoped(:where => ["name = :name", { :name => "37signals" }]).first + assert_nil Company.scoped(:where => ["name = :name", { :name => "37signals!" }]).first + assert_nil Company.scoped(:where => ["name = :name", { :name => "37signals!' OR 1=1" }]).first + assert_kind_of Time, Topic.scoped(:where => ["id = :id", { :id => 1 }]).first.written_on end class SimpleEnumerable @@ -618,12 +568,6 @@ class FinderTest < ActiveRecord::TestCase assert_equal "'something; select table'", ActiveRecord::Base.sanitize("something; select table") end - def test_count - assert_equal(0, Entrant.count(:conditions => "id > 3")) - assert_equal(1, Entrant.count(:conditions => ["id > ?", 2])) - assert_equal(2, Entrant.count(:conditions => ["id > ?", 1])) - end - def test_count_by_sql assert_equal(0, Entrant.count_by_sql("SELECT COUNT(*) FROM entrants WHERE id > 3")) assert_equal(1, Entrant.count_by_sql(["SELECT COUNT(*) FROM entrants WHERE id > ?", 2])) @@ -646,7 +590,7 @@ class FinderTest < ActiveRecord::TestCase end def test_find_by_one_attribute_with_conditions - assert_equal accounts(:rails_core_account), Account.find_by_credit_limit(50, :conditions => ['firm_id = ?', 6]) + assert_equal accounts(:rails_core_account), Account.where('firm_id = ?', 6).find_by_credit_limit(50) end def test_find_by_one_attribute_that_is_an_aggregate @@ -686,12 +630,12 @@ class FinderTest < ActiveRecord::TestCase def test_dynamic_finder_on_one_attribute_with_conditions_returns_same_results_after_caching # ensure this test can run independently of order class << Account; self; end.send(:remove_method, :find_by_credit_limit) if Account.public_methods.any? { |m| m.to_s == 'find_by_credit_limit' } - a = Account.find_by_credit_limit(50, :conditions => ['firm_id = ?', 6]) - assert_equal a, Account.find_by_credit_limit(50, :conditions => ['firm_id = ?', 6]) # find_by_credit_limit has been cached + a = Account.where('firm_id = ?', 6).find_by_credit_limit(50) + assert_equal a, Account.where('firm_id = ?', 6).find_by_credit_limit(50) # find_by_credit_limit has been cached end def test_find_by_one_attribute_with_several_options - assert_equal accounts(:unknown), Account.find_by_credit_limit(50, :order => 'id DESC', :conditions => ['id != ?', 3]) + assert_equal accounts(:unknown), Account.order('id DESC').where('id != ?', 3).find_by_credit_limit(50) end def test_find_by_one_missing_attribute @@ -725,7 +669,7 @@ class FinderTest < ActiveRecord::TestCase end def test_find_last_by_one_attribute_with_several_options - assert_equal accounts(:signals37), Account.find_last_by_credit_limit(50, :order => 'id DESC', :conditions => ['id != ?', 3]) + assert_equal accounts(:signals37), Account.order('id DESC').where('id != ?', 3).find_last_by_credit_limit(50) end def test_find_last_by_one_missing_attribute @@ -862,6 +806,28 @@ class FinderTest < ActiveRecord::TestCase assert another.persisted? end + def test_find_or_create_from_one_attribute_bang + number_of_companies = Company.count + assert_raises(ActiveRecord::RecordInvalid) { Company.find_or_create_by_name!("") } + assert_equal number_of_companies, Company.count + sig38 = Company.find_or_create_by_name!("38signals") + assert_equal number_of_companies + 1, Company.count + assert_equal sig38, Company.find_or_create_by_name!("38signals") + assert sig38.persisted? + end + + def test_find_or_create_from_two_attributes_bang + number_of_companies = Company.count + assert_raises(ActiveRecord::RecordInvalid) { Company.find_or_create_by_name_and_firm_id!("", 17) } + assert_equal number_of_companies, Company.count + sig38 = Company.find_or_create_by_name_and_firm_id!("38signals", 17) + assert_equal number_of_companies + 1, Company.count + assert_equal sig38, Company.find_or_create_by_name_and_firm_id!("38signals", 17) + assert sig38.persisted? + assert_equal "38signals", sig38.name + assert_equal 17, sig38.firm_id + end + def test_find_or_create_from_two_attributes_with_one_being_an_aggregate number_of_customers = Customer.count created_customer = Customer.find_or_create_by_balance_and_name(Money.new(123), "Elizabeth") @@ -881,6 +847,17 @@ class FinderTest < ActiveRecord::TestCase assert_equal 23, sig38.client_of end + def test_find_or_create_from_two_attributes_and_hash + number_of_companies = Company.count + sig38 = Company.find_or_create_by_name_and_firm_id({:name => "38signals", :firm_id => 17, :client_of => 23}) + assert_equal number_of_companies + 1, Company.count + assert_equal sig38, Company.find_or_create_by_name_and_firm_id({:name => "38signals", :firm_id => 17, :client_of => 23}) + assert sig38.persisted? + assert_equal "38signals", sig38.name + assert_equal 17, sig38.firm_id + assert_equal 23, sig38.client_of + end + def test_find_or_create_from_one_aggregate_attribute number_of_customers = Customer.count created_customer = Customer.find_or_create_by_balance(Money.new(123)) @@ -1027,21 +1004,15 @@ class FinderTest < ActiveRecord::TestCase assert_raise(ActiveRecord::StatementInvalid) { Topic.find_by_sql "select 1 from badtable" } end - def test_find_with_invalid_params - assert_raise(ArgumentError) { Topic.find :first, :join => "It should be `joins'" } - assert_raise(ArgumentError) { Topic.find :first, :conditions => '1 = 1', :join => "It should be `joins'" } - end - def test_dynamic_finder_with_invalid_params assert_raise(ArgumentError) { Topic.find_by_title 'No Title', :join => "It should be `joins'" } end def test_find_all_with_join - developers_on_project_one = Developer.find( - :all, + developers_on_project_one = Developer.scoped( :joins => 'LEFT JOIN developers_projects ON developers.id = developers_projects.developer_id', - :conditions => 'project_id=1' - ) + :where => 'project_id=1' + ).all assert_equal 3, developers_on_project_one.length developer_names = developers_on_project_one.map { |d| d.name } assert developer_names.include?('David') @@ -1049,17 +1020,15 @@ class FinderTest < ActiveRecord::TestCase end def test_joins_dont_clobber_id - first = Firm.find( - :first, + first = Firm.scoped( :joins => 'INNER JOIN companies clients ON clients.firm_id = companies.id', - :conditions => 'companies.id = 1' - ) + :where => 'companies.id = 1' + ).first assert_equal 1, first.id end def test_joins_with_string_array - person_with_reader_and_post = Post.find( - :all, + person_with_reader_and_post = Post.scoped( :joins => [ "INNER JOIN categorizations ON categorizations.post_id = posts.id", "INNER JOIN categories ON categories.id = categorizations.category_id AND categories.type = 'SpecialCategory'" @@ -1070,8 +1039,7 @@ class FinderTest < ActiveRecord::TestCase def test_find_by_id_with_conditions_with_or assert_nothing_raised do - Post.find([1,2,3], - :conditions => "posts.id <= 3 OR posts.#{QUOTED_TYPE} = 'Post'") + Post.where("posts.id <= 3 OR posts.#{QUOTED_TYPE} = 'Post'").find([1,2,3]) end end @@ -1086,13 +1054,13 @@ class FinderTest < ActiveRecord::TestCase end def test_find_by_empty_in_condition - assert_equal [], Post.find(:all, :conditions => ['id in (?)', []]) + assert_equal [], Post.where('id in (?)', []) end def test_find_by_records - p1, p2 = Post.find(:all, :limit => 2, :order => 'id asc') - assert_equal [p1, p2], Post.find(:all, :conditions => ['id in (?)', [p1, p2]], :order => 'id asc') - assert_equal [p1, p2], Post.find(:all, :conditions => ['id in (?)', [p1, p2.id]], :order => 'id asc') + p1, p2 = Post.scoped(:limit => 2, :order => 'id asc').all + assert_equal [p1, p2], Post.scoped(:where => ['id in (?)', [p1, p2]], :order => 'id asc') + assert_equal [p1, p2], Post.scoped(:where => ['id in (?)', [p1, p2.id]], :order => 'id asc') end def test_select_value @@ -1119,16 +1087,15 @@ class FinderTest < ActiveRecord::TestCase end def test_find_with_order_on_included_associations_with_construct_finder_sql_for_association_limiting_and_is_distinct - assert_equal 2, Post.find(:all, :include => { :authors => :author_address }, :order => ' author_addresses.id DESC ', :limit => 2).size + assert_equal 2, Post.scoped(:includes => { :authors => :author_address }, :order => 'author_addresses.id DESC ', :limit => 2).all.size - assert_equal 3, Post.find(:all, :include => { :author => :author_address, :authors => :author_address}, - :order => ' author_addresses_authors.id DESC ', :limit => 3).size + assert_equal 3, Post.scoped(:includes => { :author => :author_address, :authors => :author_address}, + :order => 'author_addresses_authors.id DESC ', :limit => 3).all.size end def test_find_with_nil_inside_set_passed_for_one_attribute - client_of = Company.find( - :all, - :conditions => { + client_of = Company.scoped( + :where => { :client_of => [2, 1, nil], :name => ['37signals', 'Summit', 'Microsoft'] }, :order => 'client_of DESC' @@ -1139,9 +1106,8 @@ class FinderTest < ActiveRecord::TestCase end def test_find_with_nil_inside_set_passed_for_attribute - client_of = Company.find( - :all, - :conditions => { :client_of => [nil] }, + client_of = Company.scoped( + :where => { :client_of => [nil] }, :order => 'client_of DESC' ).map { |x| x.client_of } @@ -1149,21 +1115,16 @@ class FinderTest < ActiveRecord::TestCase end def test_with_limiting_with_custom_select - posts = Post.find(:all, :include => :author, :select => ' posts.*, authors.id as "author_id"', :limit => 3, :order => 'posts.id') + posts = Post.references(:authors).scoped( + :includes => :author, :select => ' posts.*, authors.id as "author_id"', + :limit => 3, :order => 'posts.id' + ).all assert_equal 3, posts.size assert_equal [0, 1, 1], posts.map(&:author_id).sort end - def test_finder_with_scoped_from - all_topics = Topic.find(:all) - - Topic.send(:with_scope, :find => { :from => 'fake_topics' }) do - assert_equal all_topics, Topic.from('topics').to_a - end - end - def test_find_one_message_with_custom_primary_key - Toy.set_primary_key :name + Toy.primary_key = :name begin Toy.find 'Hello World!' rescue ActiveRecord::RecordNotFound => e @@ -1171,6 +1132,10 @@ class FinderTest < ActiveRecord::TestCase end end + def test_finder_with_offset_string + assert_nothing_raised(ActiveRecord::StatementInvalid) { Topic.scoped(:offset => "3").all } + end + protected def bind(statement, *vars) if vars.first.is_a?(Hash) diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb index 7e2dafcd01..c28f8de682 100644 --- a/activerecord/test/cases/fixtures_test.rb +++ b/activerecord/test/cases/fixtures_test.rb @@ -1,31 +1,34 @@ -require "cases/helper" -require 'models/post' +require 'cases/helper' +require 'models/admin' +require 'models/admin/account' +require 'models/admin/randomly_named_c1' +require 'models/admin/user' require 'models/binary' -require 'models/topic' +require 'models/book' +require 'models/category' +require 'models/company' require 'models/computer' +require 'models/course' require 'models/developer' -require 'models/company' -require 'models/task' -require 'models/reply' require 'models/joke' -require 'models/course' -require 'models/category' +require 'models/matey' require 'models/parrot' require 'models/pirate' -require 'models/treasure' -require 'models/traffic_light' -require 'models/matey' +require 'models/post' +require 'models/randomly_named_c1' +require 'models/reply' require 'models/ship' -require 'models/book' -require 'models/admin' -require 'models/admin/account' -require 'models/admin/user' +require 'models/task' +require 'models/topic' +require 'models/traffic_light' +require 'models/treasure' require 'tempfile' class FixturesTest < ActiveRecord::TestCase self.use_instantiated_fixtures = true self.use_transactional_fixtures = false + # other_topics fixture should not be included here fixtures :topics, :developers, :accounts, :tasks, :categories, :funny_jokes, :binaries, :traffic_lights FIXTURES = %w( accounts binaries companies customers @@ -62,8 +65,9 @@ class FixturesTest < ActiveRecord::TestCase end def test_create_fixtures - ActiveRecord::Fixtures.create_fixtures(FIXTURES_ROOT, "parrots") - assert Parrot.find_by_name('Curious George'), 'George is in the database' + fixtures = ActiveRecord::Fixtures.create_fixtures(FIXTURES_ROOT, "parrots") + assert Parrot.find_by_name('Curious George'), 'George is not in the database' + assert fixtures.detect { |f| f.name == 'parrots' }, "no fixtures named 'parrots' in #{fixtures.map(&:name).inspect}" end def test_multiple_clean_fixtures @@ -73,6 +77,13 @@ class FixturesTest < ActiveRecord::TestCase fixtures_array.each { |fixtures| assert_kind_of(ActiveRecord::Fixtures, fixtures) } end + def test_create_symbol_fixtures + fixtures = ActiveRecord::Fixtures.create_fixtures(FIXTURES_ROOT, :collections, :collections => Course) { Course.connection } + + assert Course.find_by_name('Collection'), 'course is not in the database' + assert fixtures.detect { |f| f.name == 'collections' }, "no fixtures named 'collections' in #{fixtures.map(&:name).inspect}" + end + def test_attributes topics = create_fixtures("topics").first assert_equal("The First Topic", topics["first"]["title"]) @@ -93,7 +104,7 @@ class FixturesTest < ActiveRecord::TestCase # Reset cache to make finds on the new table work ActiveRecord::Fixtures.reset_cache - ActiveRecord::Base.connection.create_table :prefix_topics_suffix do |t| + ActiveRecord::Base.connection.create_table :prefix_other_topics_suffix do |t| t.column :title, :string t.column :author_name, :string t.column :author_email_address, :string @@ -115,23 +126,36 @@ class FixturesTest < ActiveRecord::TestCase ActiveRecord::Base.table_name_prefix = 'prefix_' ActiveRecord::Base.table_name_suffix = '_suffix' - topics = create_fixtures("topics") - - first_row = ActiveRecord::Base.connection.select_one("SELECT * FROM prefix_topics_suffix WHERE author_name = 'David'") - assert_equal("The First Topic", first_row["title"]) + other_topic_klass = Class.new(ActiveRecord::Base) do + def self.name + "OtherTopic" + end + end - second_row = ActiveRecord::Base.connection.select_one("SELECT * FROM prefix_topics_suffix WHERE author_name = 'Mary'") - assert_nil(second_row["author_email_address"]) + topics = [create_fixtures("other_topics")].flatten.first # This checks for a caching problem which causes a bug in the fixtures # class-level configuration helper. assert_not_nil topics, "Fixture data inserted, but fixture objects not returned from create" + + first_row = ActiveRecord::Base.connection.select_one("SELECT * FROM prefix_other_topics_suffix WHERE author_name = 'David'") + assert_not_nil first_row, "The prefix_other_topics_suffix table appears to be empty despite create_fixtures: the row with author_name = 'David' was not found" + assert_equal("The First Topic", first_row["title"]) + + second_row = ActiveRecord::Base.connection.select_one("SELECT * FROM prefix_other_topics_suffix WHERE author_name = 'Mary'") + assert_nil(second_row["author_email_address"]) + + assert_equal :prefix_other_topics_suffix, topics.table_name.to_sym + # This assertion should preferably be the last in the list, because calling + # other_topic_klass.table_name sets a class-level instance variable + assert_equal :prefix_other_topics_suffix, other_topic_klass.table_name.to_sym + ensure # Restore prefix/suffix to its previous values ActiveRecord::Base.table_name_prefix = old_prefix ActiveRecord::Base.table_name_suffix = old_suffix - ActiveRecord::Base.connection.drop_table :prefix_topics_suffix rescue nil + ActiveRecord::Base.connection.drop_table :prefix_other_topics_suffix rescue nil end end @@ -179,7 +203,7 @@ class FixturesTest < ActiveRecord::TestCase #sanity check to make sure that this file never exists assert Dir[nonexistent_fixture_path+"*"].empty? - assert_raise(FixturesFileNotFound) do + assert_raise(Errno::ENOENT) do ActiveRecord::Fixtures.new( Account.connection, "companies", 'Company', nonexistent_fixture_path) end end @@ -213,7 +237,7 @@ class FixturesTest < ActiveRecord::TestCase def test_binary_in_fixtures data = File.open(ASSETS_ROOT + "/flowers.jpg", 'rb') { |f| f.read } - data.force_encoding('ASCII-8BIT') if data.respond_to?(:force_encoding) + data.force_encoding('ASCII-8BIT') data.freeze assert_equal data, @flowers.data end @@ -490,7 +514,7 @@ class InvalidTableNameFixturesTest < ActiveRecord::TestCase self.use_transactional_fixtures = false def test_raises_error - assert_raise FixtureClassNotFound do + assert_raise ActiveRecord::FixtureClassNotFound do funny_jokes(:a_joke) end end @@ -573,7 +597,7 @@ class FasterFixturesTest < ActiveRecord::TestCase load_extra_fixture('posts') assert ActiveRecord::Fixtures.fixture_is_cached?(ActiveRecord::Base.connection, 'posts') - self.class.setup_fixture_accessors('posts') + self.class.setup_fixture_accessors :posts assert_equal 'Welcome to the weblog', posts(:welcome).title end end @@ -692,7 +716,7 @@ class FoxyFixturesTest < ActiveRecord::TestCase end def test_only_generates_a_pk_if_necessary - m = Matey.find(:first) + m = Matey.first m.pirate = pirates(:blackbeard) m.target = pirates(:redbeard) end @@ -731,3 +755,34 @@ class FixtureLoadingTest < ActiveRecord::TestCase ActiveRecord::TestCase.try_to_load_dependency(:works_out_fine) end end + +class CustomNameForFixtureOrModelTest < ActiveRecord::TestCase + ActiveRecord::Fixtures.reset_cache + + set_fixture_class :randomly_named_a9 => + ClassNameThatDoesNotFollowCONVENTIONS, + :'admin/randomly_named_a9' => + Admin::ClassNameThatDoesNotFollowCONVENTIONS, + 'admin/randomly_named_b0' => + Admin::ClassNameThatDoesNotFollowCONVENTIONS + + fixtures :randomly_named_a9, 'admin/randomly_named_a9', + :'admin/randomly_named_b0' + + def test_named_accessor_for_randomly_named_fixture_and_class + assert_kind_of ClassNameThatDoesNotFollowCONVENTIONS, + randomly_named_a9(:first_instance) + end + + def test_named_accessor_for_randomly_named_namespaced_fixture_and_class + assert_kind_of Admin::ClassNameThatDoesNotFollowCONVENTIONS, + admin_randomly_named_a9(:first_instance) + assert_kind_of Admin::ClassNameThatDoesNotFollowCONVENTIONS, + admin_randomly_named_b0(:second_instance) + end + + def test_table_name_is_defined_in_the_model + assert_equal 'randomly_named_table', ActiveRecord::Fixtures::all_loaded_fixtures["admin/randomly_named_a9"].table_name + assert_equal 'randomly_named_table', Admin::ClassNameThatDoesNotFollowCONVENTIONS.table_name + end +end diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb index 6735bc521b..345ae0b582 100644 --- a/activerecord/test/cases/helper.rb +++ b/activerecord/test/cases/helper.rb @@ -2,12 +2,14 @@ require File.expand_path('../../../../load_paths', __FILE__) require 'config' -require 'test/unit' +require 'minitest/autorun' require 'stringio' require 'mocha' +require 'cases/test_case' require 'active_record' require 'active_support/dependencies' +require 'active_support/logger' require 'support/config' require 'support/connection' @@ -17,8 +19,8 @@ require 'support/connection' # Show backtraces for deprecated behavior for quicker cleanup. ActiveSupport::Deprecation.debug = true -# Enable Identity Map only when ENV['IM'] is set to "true" -ActiveRecord::IdentityMap.enabled = (ENV['IM'] == "true") +# Avoid deprecation warning setting dependent_restrict_raises to false. The default is true +ActiveRecord::Base.dependent_restrict_raises = false # Connect to the database ARTest.connect @@ -34,7 +36,7 @@ def current_adapter?(*types) end def in_memory_db? - current_adapter?(:SQLiteAdapter) && + current_adapter?(:SQLite3Adapter) && ActiveRecord::Base.connection_pool.spec.config[:database] == ":memory:" end @@ -56,36 +58,13 @@ ensure ActiveRecord::Base.default_timezone = old_zone end -module ActiveRecord - class SQLCounter - cattr_accessor :ignored_sql - self.ignored_sql = [/^PRAGMA (?!(table_info))/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/, /^SELECT @@ROWCOUNT/, /^SAVEPOINT/, /^ROLLBACK TO SAVEPOINT/, /^RELEASE SAVEPOINT/, /^SHOW max_identifier_length/, /^BEGIN/, /^COMMIT/] - - # FIXME: this needs to be refactored so specific database can add their own - # ignored SQL. This ignored SQL is for Oracle. - ignored_sql.concat [/^select .*nextval/i, /^SAVEPOINT/, /^ROLLBACK TO/, /^\s*select .* from all_triggers/im] - - cattr_accessor :log - self.log = [] - - def call(name, start, finish, message_id, values) - sql = values[:sql] - - # FIXME: this seems bad. we should probably have a better way to indicate - # the query was cached - unless 'CACHE' == values[:name] - self.class.log << sql unless self.class.ignored_sql.any? { |r| sql =~ r } - end - end - end - - ActiveSupport::Notifications.subscribe('sql.active_record', SQLCounter.new) -end - unless ENV['FIXTURE_DEBUG'] module ActiveRecord::TestFixtures::ClassMethods def try_to_load_dependency_with_silence(*args) - ActiveRecord::Base.logger.silence { try_to_load_dependency_without_silence(*args)} + old = ActiveRecord::Base.logger.level + ActiveRecord::Base.logger.level = ActiveSupport::Logger::ERROR + try_to_load_dependency_without_silence(*args) + ActiveRecord::Base.logger.level = old end alias_method_chain :try_to_load_dependency, :silence diff --git a/activerecord/test/cases/identity_map/middleware_test.rb b/activerecord/test/cases/identity_map/middleware_test.rb deleted file mode 100644 index 60dcad4586..0000000000 --- a/activerecord/test/cases/identity_map/middleware_test.rb +++ /dev/null @@ -1,71 +0,0 @@ -require "cases/helper" - -module ActiveRecord - module IdentityMap - class MiddlewareTest < ActiveRecord::TestCase - def setup - super - @enabled = IdentityMap.enabled - IdentityMap.enabled = false - end - - def teardown - super - IdentityMap.enabled = @enabled - IdentityMap.clear - end - - def test_delegates - called = false - mw = Middleware.new lambda { |env| - called = true - } - mw.call({}) - assert called, 'middleware delegated' - end - - def test_im_enabled_during_delegation - mw = Middleware.new lambda { |env| - assert IdentityMap.enabled?, 'identity map should be enabled' - } - mw.call({}) - end - - class Enum < Struct.new(:iter) - def each(&b) - iter.call(&b) - end - end - - def test_im_enabled_during_body_each - mw = Middleware.new lambda { |env| - [200, {}, Enum.new(lambda { |&b| - assert IdentityMap.enabled?, 'identity map should be enabled' - b.call "hello" - })] - } - body = mw.call({}).last - body.each { |x| assert_equal 'hello', x } - end - - def test_im_disabled_after_body_close - mw = Middleware.new lambda { |env| [200, {}, []] } - body = mw.call({}).last - assert IdentityMap.enabled?, 'identity map should be enabled' - body.close - assert !IdentityMap.enabled?, 'identity map should be disabled' - end - - def test_im_cleared_after_body_close - mw = Middleware.new lambda { |env| [200, {}, []] } - body = mw.call({}).last - - IdentityMap.repository['hello'] = 'world' - assert !IdentityMap.repository.empty?, 'repo should not be empty' - - body.close - assert IdentityMap.repository.empty?, 'repo should be empty' - end - end - end -end diff --git a/activerecord/test/cases/identity_map_test.rb b/activerecord/test/cases/identity_map_test.rb deleted file mode 100644 index 3efc8bf559..0000000000 --- a/activerecord/test/cases/identity_map_test.rb +++ /dev/null @@ -1,439 +0,0 @@ -require "cases/helper" - -require 'models/developer' -require 'models/project' -require 'models/company' -require 'models/topic' -require 'models/reply' -require 'models/computer' -require 'models/customer' -require 'models/order' -require 'models/post' -require 'models/author' -require 'models/tag' -require 'models/tagging' -require 'models/comment' -require 'models/sponsor' -require 'models/member' -require 'models/essay' -require 'models/subscriber' -require "models/pirate" -require "models/bird" -require "models/parrot" - -if ActiveRecord::IdentityMap.enabled? -class IdentityMapTest < ActiveRecord::TestCase - fixtures :accounts, :companies, :developers, :projects, :topics, - :developers_projects, :computers, :authors, :author_addresses, - :posts, :tags, :taggings, :comments, :subscribers - - ############################################################################## - # Basic tests checking if IM is functioning properly on basic find operations# - ############################################################################## - - def test_find_id - assert_same(Client.find(3), Client.find(3)) - end - - def test_find_id_without_identity_map - ActiveRecord::IdentityMap.without do - assert_not_same(Client.find(3), Client.find(3)) - end - end - - def test_find_id_use_identity_map - ActiveRecord::IdentityMap.enabled = false - ActiveRecord::IdentityMap.use do - assert_same(Client.find(3), Client.find(3)) - end - ActiveRecord::IdentityMap.enabled = true - end - - def test_find_pkey - assert_same( - Subscriber.find('swistak'), - Subscriber.find('swistak') - ) - end - - def test_find_by_id - assert_same( - Client.find_by_id(3), - Client.find_by_id(3) - ) - end - - def test_find_by_string_and_numeric_id - assert_same( - Client.find_by_id("3"), - Client.find_by_id(3) - ) - end - - def test_find_by_pkey - assert_same( - Subscriber.find_by_nick('swistak'), - Subscriber.find_by_nick('swistak') - ) - end - - def test_find_first_id - assert_same( - Client.find(:first, :conditions => {:id => 1}), - Client.find(:first, :conditions => {:id => 1}) - ) - end - - def test_find_first_pkey - assert_same( - Subscriber.find(:first, :conditions => {:nick => 'swistak'}), - Subscriber.find(:first, :conditions => {:nick => 'swistak'}) - ) - end - - def test_queries_are_not_executed_when_finding_by_id - Post.find 1 - assert_no_queries do - Post.find 1 - end - end - - ############################################################################## - # Tests checking if IM is functioning properly on more advanced finds # - # and associations # - ############################################################################## - - def test_owner_object_is_associated_from_identity_map - post = Post.find(1) - comment = post.comments.first - - assert_no_queries do - comment.post - end - assert_same post, comment.post - end - - def test_associated_object_are_assigned_from_identity_map - post = Post.find(1) - - post.comments.each do |comment| - assert_same post, comment.post - assert_equal post.object_id, comment.post.object_id - end - end - - def test_creation - t1 = Topic.create("title" => "t1") - t2 = Topic.find(t1.id) - assert_same(t1, t2) - end - - ############################################################################## - # Tests checking if IM is functioning properly on classes with multiple # - # types of inheritance # - ############################################################################## - - def test_inherited_without_type_attribute_without_identity_map - ActiveRecord::IdentityMap.without do - p1 = DestructivePirate.create!(:catchphrase => "I'm not a regular Pirate") - p2 = Pirate.find(p1.id) - assert_not_same(p1, p2) - end - end - - def test_inherited_with_type_attribute_without_identity_map - ActiveRecord::IdentityMap.without do - c = comments(:sub_special_comment) - c1 = SubSpecialComment.find(c.id) - c2 = Comment.find(c.id) - assert_same(c1.class, c2.class) - end - end - - def test_inherited_without_type_attribute - p1 = DestructivePirate.create!(:catchphrase => "I'm not a regular Pirate") - p2 = Pirate.find(p1.id) - assert_not_same(p1, p2) - end - - def test_inherited_with_type_attribute - c = comments(:sub_special_comment) - c1 = SubSpecialComment.find(c.id) - c2 = Comment.find(c.id) - assert_same(c1, c2) - end - - ############################################################################## - # Tests checking dirty attribute behavior with IM # - ############################################################################## - - def test_loading_new_instance_should_not_update_dirty_attributes - swistak = Subscriber.find(:first, :conditions => {:nick => 'swistak'}) - swistak.name = "Swistak Sreberkowiec" - assert_equal(["name"], swistak.changed) - assert_equal({"name" => ["Marcin Raczkowski", "Swistak Sreberkowiec"]}, swistak.changes) - - assert swistak.name_changed? - assert_equal("Swistak Sreberkowiec", swistak.name) - end - - def test_loading_new_instance_should_change_dirty_attribute_original_value - swistak = Subscriber.find(:first, :conditions => {:nick => 'swistak'}) - swistak.name = "Swistak Sreberkowiec" - - Subscriber.update_all({:name => "Raczkowski Marcin"}, {:name => "Marcin Raczkowski"}) - - assert_equal({"name"=>["Marcin Raczkowski", "Swistak Sreberkowiec"]}, swistak.changes) - assert_equal("Swistak Sreberkowiec", swistak.name) - end - - def test_loading_new_instance_should_remove_dirt - swistak = Subscriber.find(:first, :conditions => {:nick => 'swistak'}) - swistak.name = "Swistak Sreberkowiec" - - assert_equal({"name" => ["Marcin Raczkowski", "Swistak Sreberkowiec"]}, swistak.changes) - - Subscriber.update_all({:name => "Swistak Sreberkowiec"}, {:name => "Marcin Raczkowski"}) - - assert_equal("Swistak Sreberkowiec", swistak.name) - assert_equal({"name"=>["Marcin Raczkowski", "Swistak Sreberkowiec"]}, swistak.changes) - assert swistak.name_changed? - end - - def test_has_many_associations - pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?") - pirate.birds.create!(:name => 'Posideons Killer') - pirate.birds.create!(:name => 'Killer bandita Dionne') - - posideons, _ = pirate.birds - - pirate.reload - - pirate.birds_attributes = [{ :id => posideons.id, :name => 'Grace OMalley' }] - assert_equal 'Grace OMalley', pirate.birds.to_a.find { |r| r.id == posideons.id }.name - end - - def test_changing_associations - post1 = Post.create("title" => "One post", "body" => "Posting...") - post2 = Post.create("title" => "Another post", "body" => "Posting... Again...") - comment = Comment.new("body" => "comment") - - comment.post = post1 - assert comment.save - - assert_same(post1.comments.first, comment) - - comment.post = post2 - assert comment.save - - assert_same(post2.comments.first, comment) - assert_equal(0, post1.comments.size) - end - - def test_im_with_polymorphic_has_many_going_through_join_model_with_custom_select_and_joins - tag = posts(:welcome).tags.first - tag_with_joins_and_select = posts(:welcome).tags.add_joins_and_select.first - assert_same(tag, tag_with_joins_and_select) - assert_nothing_raised(NoMethodError, "Joins/select was not loaded") { tag.author_id } - end - - ############################################################################## - # Tests checking Identity Map behavior with preloaded associations, joins, # - # includes etc. # - ############################################################################## - - def test_find_with_preloaded_associations - assert_queries(2) do - posts = Post.preload(:comments).order('posts.id') - assert posts.first.comments.first - end - - # With IM we'll retrieve post object from previous query, it'll have comments - # already preloaded from first call - assert_queries(1) do - posts = Post.preload(:comments).order('posts.id') - assert posts.first.comments.first - end - - assert_queries(2) do - posts = Post.preload(:author).order('posts.id') - assert posts.first.author - end - - # With IM we'll retrieve post object from previous query, it'll have comments - # already preloaded from first call - assert_queries(1) do - posts = Post.preload(:author).order('posts.id') - assert posts.first.author - end - - assert_queries(1) do - posts = Post.preload(:author, :comments).order('posts.id') - assert posts.first.author - assert posts.first.comments.first - end - end - - def test_find_with_included_associations - assert_queries(2) do - posts = Post.includes(:comments).order('posts.id') - assert posts.first.comments.first - end - - assert_queries(1) do - posts = Post.scoped.includes(:comments).order('posts.id') - assert posts.first.comments.first - end - - assert_queries(2) do - posts = Post.includes(:author).order('posts.id') - assert posts.first.author - end - - assert_queries(1) do - posts = Post.includes(:author, :comments).order('posts.id') - assert posts.first.author - assert posts.first.comments.first - end - end - - def test_eager_loading_with_conditions_on_joined_table_preloads - posts = Post.find(:all, :select => 'distinct posts.*', :include => :author, :joins => [:comments], :conditions => "comments.body like 'Thank you%'", :order => 'posts.id') - assert_equal [posts(:welcome)], posts - assert_equal authors(:david), assert_no_queries { posts[0].author} - assert_same posts.first.author, Author.first - - posts = Post.find(:all, :select => 'distinct posts.*', :include => :author, :joins => [:comments], :conditions => "comments.body like 'Thank you%'", :order => 'posts.id') - assert_equal [posts(:welcome)], posts - assert_equal authors(:david), assert_no_queries { posts[0].author} - assert_same posts.first.author, Author.first - - posts = Post.find(:all, :include => :author, :joins => {:taggings => :tag}, :conditions => "tags.name = 'General'", :order => 'posts.id') - assert_equal posts(:welcome, :thinking), posts - assert_same posts.first.author, Author.first - - posts = Post.find(:all, :include => :author, :joins => {:taggings => {:tag => :taggings}}, :conditions => "taggings_tags.super_tag_id=2", :order => 'posts.id') - assert_equal posts(:welcome, :thinking), posts - assert_same posts.first.author, Author.first - end - - def test_eager_loading_with_conditions_on_string_joined_table_preloads - posts = assert_queries(2) do - Post.find(:all, :select => 'distinct posts.*', :include => :author, :joins => "INNER JOIN comments on comments.post_id = posts.id", :conditions => "comments.body like 'Thank you%'", :order => 'posts.id') - end - assert_equal [posts(:welcome)], posts - assert_equal authors(:david), assert_no_queries { posts[0].author} - - posts = assert_queries(1) do - Post.find(:all, :select => 'distinct posts.*', :include => :author, :joins => ["INNER JOIN comments on comments.post_id = posts.id"], :conditions => "comments.body like 'Thank you%'", :order => 'posts.id') - end - assert_equal [posts(:welcome)], posts - assert_equal authors(:david), assert_no_queries { posts[0].author} - end - - ############################################################################## - # Behaviour related to saving failures - ############################################################################## - - def test_reload_object_if_save_failed - developer = Developer.first - developer.salary = 0 - - assert !developer.save - - same_developer = Developer.first - - assert_not_same developer, same_developer - assert_not_equal 0, same_developer.salary - assert_not_equal developer.salary, same_developer.salary - end - - def test_reload_object_if_forced_save_failed - developer = Developer.first - developer.salary = 0 - - assert_raise(ActiveRecord::RecordInvalid) { developer.save! } - - same_developer = Developer.first - - assert_not_same developer, same_developer - assert_not_equal 0, same_developer.salary - assert_not_equal developer.salary, same_developer.salary - end - - def test_reload_object_if_update_attributes_fails - developer = Developer.first - developer.salary = 0 - - assert !developer.update_attributes(:salary => 0) - - same_developer = Developer.first - - assert_not_same developer, same_developer - assert_not_equal 0, same_developer.salary - assert_not_equal developer.salary, same_developer.salary - end - - ############################################################################## - # Behaviour of readonly, frozen, destroyed - ############################################################################## - - def test_find_using_identity_map_respects_readonly_when_loading_associated_object_first - author = Author.first - readonly_comment = author.readonly_comments.first - - comment = Comment.first - assert !comment.readonly? - - assert readonly_comment.readonly? - - assert_raise(ActiveRecord::ReadOnlyRecord) {readonly_comment.save} - assert comment.save - end - - def test_find_using_identity_map_respects_readonly - comment = Comment.first - assert !comment.readonly? - - author = Author.first - readonly_comment = author.readonly_comments.first - - assert readonly_comment.readonly? - - assert_raise(ActiveRecord::ReadOnlyRecord) {readonly_comment.save} - assert comment.save - end - - def test_find_using_select_and_identity_map - author_id, author = Author.select('id').first, Author.first - - assert_equal author_id, author - assert_same author_id, author - assert_not_nil author.name - - post, post_id = Post.first, Post.select('id').first - - assert_equal post_id, post - assert_same post_id, post - assert_not_nil post.title - end - -# Currently AR is not allowing changing primary key (see Persistence#update) -# So we ignore it. If this changes, this test needs to be uncommented. -# def test_updating_of_pkey -# assert client = Client.find(3), -# client.update_attribute(:id, 666) -# -# assert Client.find(666) -# assert_same(client, Client.find(666)) -# -# s = Subscriber.find_by_nick('swistak') -# assert s.update_attribute(:nick, 'swistakTheJester') -# assert_equal('swistakTheJester', s.nick) -# -# assert stj = Subscriber.find_by_nick('swistakTheJester') -# assert_same(s, stj) -# end - -end -end diff --git a/activerecord/test/cases/inclusion_test.rb b/activerecord/test/cases/inclusion_test.rb new file mode 100644 index 0000000000..8726ba5e51 --- /dev/null +++ b/activerecord/test/cases/inclusion_test.rb @@ -0,0 +1,113 @@ +require 'cases/helper' +require 'models/teapot' + +class BasicInclusionModelTest < ActiveRecord::TestCase + def test_basic_model + Teapot.create!(:name => "Ronnie Kemper") + assert_equal "Ronnie Kemper", Teapot.find(1).name + end + + def test_initialization + t = Teapot.new(:name => "Bob") + assert_equal "Bob", t.name + end + + def test_inherited_model + teapot = CoolTeapot.create!(:name => "Bob") + teapot.reload + + assert_equal "Bob", teapot.name + assert_equal "mmm", teapot.aaahhh + end + + def test_generated_feature_methods + assert Teapot < Teapot::GeneratedFeatureMethods + end + + def test_exists + t = Teapot.create!(:name => "Ronnie Kemper") + assert Teapot.exists?(t) + end + + def test_predicate_builder + t = Teapot.create!(:name => "Bob") + assert_equal "Bob", Teapot.where(:id => [t]).first.name + assert_equal "Bob", Teapot.where(:id => t).first.name + end + + def test_nested_model + assert_equal "ceiling_teapots", Ceiling::Teapot.table_name + end +end + +class InclusionUnitTest < ActiveRecord::TestCase + def setup + @klass = Class.new { include ActiveRecord::Model } + end + + def test_non_abstract_class + assert !@klass.abstract_class? + end + + def test_abstract_class + @klass.abstract_class = true + assert @klass.abstract_class? + end + + def test_establish_connection + assert @klass.respond_to?(:establish_connection) + assert ActiveRecord::Model.respond_to?(:establish_connection) + end + + def test_adapter_connection + name = "#{ActiveRecord::Base.connection_config[:adapter]}_connection" + assert @klass.respond_to?(name) + assert ActiveRecord::Model.respond_to?(name) + end + + def test_connection_handler + assert_equal ActiveRecord::Base.connection_handler, @klass.connection_handler + end + + def test_mirrored_configuration + ActiveRecord::Base.time_zone_aware_attributes = true + assert @klass.time_zone_aware_attributes + ActiveRecord::Base.time_zone_aware_attributes = false + assert !@klass.time_zone_aware_attributes + ensure + ActiveRecord::Base.time_zone_aware_attributes = false + end + + # Doesn't really test anything, but this is here to ensure warnings don't occur + def test_included_twice + @klass.send :include, ActiveRecord::Model + end + + def test_deprecation_proxy + assert_equal ActiveRecord::Model.name, ActiveRecord::Model::DeprecationProxy.name + assert_equal ActiveRecord::Base.superclass, assert_deprecated { ActiveRecord::Model::DeprecationProxy.superclass } + + sup, sup2 = nil, nil + ActiveSupport.on_load(:__test_active_record_model_deprecation) do + sup = superclass + sup2 = send(:superclass) + end + assert_deprecated do + ActiveSupport.run_load_hooks(:__test_active_record_model_deprecation, ActiveRecord::Model::DeprecationProxy) + end + assert_equal ActiveRecord::Base.superclass, sup + assert_equal ActiveRecord::Base.superclass, sup2 + end +end + +class InclusionFixturesTest < ActiveRecord::TestCase + fixtures :teapots + + def test_fixtured_record + assert_equal "Bob", teapots(:bob).name + end + + def test_timestamped_fixture + assert_not_nil teapots(:bob).created_at + end +end diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb index b5d8314541..06de51f5cd 100644 --- a/activerecord/test/cases/inheritance_test.rb +++ b/activerecord/test/cases/inheritance_test.rb @@ -15,7 +15,7 @@ class InheritanceTest < ActiveRecord::TestCase end def test_class_with_blank_sti_name - company = Company.find(:first) + company = Company.first company = company.dup company.extend(Module.new { def read_attribute(name) @@ -24,7 +24,7 @@ class InheritanceTest < ActiveRecord::TestCase end }) company.save! - company = Company.find(:all).find { |x| x.id == company.id } + company = Company.all.find { |x| x.id == company.id } assert_equal ' ', company.type end @@ -65,7 +65,6 @@ class InheritanceTest < ActiveRecord::TestCase end def test_company_descends_from_active_record - assert_raise(NoMethodError) { ActiveRecord::Base.descends_from_active_record? } assert AbstractCompany.descends_from_active_record?, 'AbstractCompany should descend from ActiveRecord::Base' assert Company.descends_from_active_record?, 'Company should descend from ActiveRecord::Base' assert !Class.new(Company).descends_from_active_record?, 'Company subclass should not descend from ActiveRecord::Base' @@ -99,7 +98,7 @@ class InheritanceTest < ActiveRecord::TestCase end def test_inheritance_find_all - companies = Company.find(:all, :order => 'id') + companies = Company.scoped(:order => 'id').all assert_kind_of Firm, companies[0], "37signals should be a firm" assert_kind_of Client, companies[1], "Summit should be a client" end @@ -150,9 +149,9 @@ class InheritanceTest < ActiveRecord::TestCase def test_update_all_within_inheritance Client.update_all "name = 'I am a client'" - assert_equal "I am a client", Client.find(:all).first.name + assert_equal "I am a client", Client.all.first.name # Order by added as otherwise Oracle tests were failing because of different order of results - assert_equal "37signals", Firm.find(:all, :order => "id").first.name + assert_equal "37signals", Firm.scoped(:order => "id").all.first.name end def test_alt_update_all_within_inheritance @@ -174,9 +173,9 @@ class InheritanceTest < ActiveRecord::TestCase end def test_find_first_within_inheritance - assert_kind_of Firm, Company.find(:first, :conditions => "name = '37signals'") - assert_kind_of Firm, Firm.find(:first, :conditions => "name = '37signals'") - assert_nil Client.find(:first, :conditions => "name = '37signals'") + assert_kind_of Firm, Company.scoped(:where => "name = '37signals'").first + assert_kind_of Firm, Firm.scoped(:where => "name = '37signals'").first + assert_nil Client.scoped(:where => "name = '37signals'").first end def test_alt_find_first_within_inheritance @@ -188,10 +187,10 @@ class InheritanceTest < ActiveRecord::TestCase def test_complex_inheritance very_special_client = VerySpecialClient.create("name" => "veryspecial") assert_equal very_special_client, VerySpecialClient.where("name = 'veryspecial'").first - assert_equal very_special_client, SpecialClient.find(:first, :conditions => "name = 'veryspecial'") - assert_equal very_special_client, Company.find(:first, :conditions => "name = 'veryspecial'") - assert_equal very_special_client, Client.find(:first, :conditions => "name = 'veryspecial'") - assert_equal 1, Client.find(:all, :conditions => "name = 'Summit'").size + assert_equal very_special_client, SpecialClient.scoped(:where => "name = 'veryspecial'").first + assert_equal very_special_client, Company.scoped(:where => "name = 'veryspecial'").first + assert_equal very_special_client, Client.scoped(:where => "name = 'veryspecial'").first + assert_equal 1, Client.scoped(:where => "name = 'Summit'").all.size assert_equal very_special_client, Client.find(very_special_client.id) end @@ -202,14 +201,14 @@ class InheritanceTest < ActiveRecord::TestCase end def test_eager_load_belongs_to_something_inherited - account = Account.find(1, :include => :firm) + account = Account.scoped(:includes => :firm).find(1) assert account.association_cache.key?(:firm), "nil proves eager load failed" end def test_eager_load_belongs_to_primary_key_quoting con = Account.connection assert_sql(/#{con.quote_table_name('companies')}.#{con.quote_column_name('id')} IN \(1\)/) do - Account.find(1, :include => :firm) + Account.scoped(:includes => :firm).find(1) end end @@ -231,16 +230,16 @@ class InheritanceTest < ActiveRecord::TestCase private def switch_to_alt_inheritance_column # we don't want misleading test results, so get rid of the values in the type column - Company.find(:all, :order => 'id').each do |c| + Company.scoped(:order => 'id').all.each do |c| c['type'] = nil c.save end [ Company, Firm, Client].each { |klass| klass.reset_column_information } - Company.set_inheritance_column('ruby_type') + Company.inheritance_column = 'ruby_type' end def switch_to_default_inheritance_column [ Company, Firm, Client].each { |klass| klass.reset_column_information } - Company.set_inheritance_column('type') + Company.inheritance_column = 'type' end end @@ -260,7 +259,7 @@ class InheritanceComputeTypeTest < ActiveRecord::TestCase def test_instantiation_doesnt_try_to_require_corresponding_file ActiveRecord::Base.store_full_sti_class = false - foo = Firm.find(:first).clone + foo = Firm.first.clone foo.ruby_type = foo.type = 'FirmOnTheFly' foo.save! diff --git a/activerecord/test/cases/invalid_date_test.rb b/activerecord/test/cases/invalid_date_test.rb index 98cda010ae..426a350379 100644 --- a/activerecord/test/cases/invalid_date_test.rb +++ b/activerecord/test/cases/invalid_date_test.rb @@ -7,8 +7,6 @@ class InvalidDateTest < ActiveRecord::TestCase invalid_dates = [[2007, 11, 31], [1993, 2, 29], [2007, 2, 29]] - topic = Topic.new - valid_dates.each do |date_src| topic = Topic.new("last_read(1i)" => date_src[0].to_s, "last_read(2i)" => date_src[1].to_s, "last_read(3i)" => date_src[2].to_s) # Oracle DATE columns are datetime columns and Oracle adapter returns Time value diff --git a/activerecord/test/cases/invertible_migration_test.rb b/activerecord/test/cases/invertible_migration_test.rb index 3ae7b63dff..8f1cdd47ea 100644 --- a/activerecord/test/cases/invertible_migration_test.rb +++ b/activerecord/test/cases/invertible_migration_test.rb @@ -88,5 +88,17 @@ module ActiveRecord LegacyMigration.down assert !ActiveRecord::Base.connection.table_exists?("horses"), "horses should not exist" end + + def test_migrate_down_with_table_name_prefix + ActiveRecord::Base.table_name_prefix = 'p_' + ActiveRecord::Base.table_name_suffix = '_s' + migration = InvertibleMigration.new + migration.migrate(:up) + assert_nothing_raised { migration.migrate(:down) } + assert !ActiveRecord::Base.connection.table_exists?("p_horses_s"), "p_horses_s should not exist" + ensure + ActiveRecord::Base.table_name_prefix = ActiveRecord::Base.table_name_suffix = '' + end + end end diff --git a/activerecord/test/cases/lifecycle_test.rb b/activerecord/test/cases/lifecycle_test.rb index 75e5dfa49b..0b78f2e46b 100644 --- a/activerecord/test/cases/lifecycle_test.rb +++ b/activerecord/test/cases/lifecycle_test.rb @@ -137,7 +137,7 @@ class LifecycleTest < ActiveRecord::TestCase def test_auto_observer topic_observer = TopicaAuditor.instance assert_nil TopicaAuditor.observed_class - assert_equal [Topic], TopicaAuditor.instance.observed_classes.to_a + assert_equal [Topic], TopicaAuditor.observed_classes.to_a topic = Topic.find(1) assert_equal topic.title, topic_observer.topic.title diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb index e9bd7f07b6..afb0bd6fd9 100644 --- a/activerecord/test/cases/locking_test.rb +++ b/activerecord/test/cases/locking_test.rb @@ -6,12 +6,16 @@ require 'models/reader' require 'models/legacy_thing' require 'models/reference' require 'models/string_key_object' +require 'models/car' +require 'models/engine' +require 'models/wheel' +require 'models/treasure' class LockWithoutDefault < ActiveRecord::Base; end class LockWithCustomColumnWithoutDefault < ActiveRecord::Base - set_table_name :lock_without_defaults_cust - set_locking_column :custom_lock_version + self.table_name = :lock_without_defaults_cust + self.locking_column = :custom_lock_version end class ReadonlyFirstNamePerson < Person @@ -19,7 +23,7 @@ class ReadonlyFirstNamePerson < Person end class OptimisticLockingTest < ActiveRecord::TestCase - fixtures :people, :legacy_things, :references, :string_key_objects + fixtures :people, :legacy_things, :references, :string_key_objects, :peoples_treasures def test_non_integer_lock_existing s1 = StringKeyObject.find("record1") @@ -224,6 +228,27 @@ class OptimisticLockingTest < ActiveRecord::TestCase assert_equal lock_version, p1.lock_version end end + + def test_polymorphic_destroy_with_dependencies_and_lock_version + car = Car.create! + + assert_difference 'car.wheels.count' do + car.wheels << Wheel.create! + end + assert_difference 'car.wheels.count', -1 do + car.destroy + end + assert car.destroyed? + end + + def test_removing_has_and_belongs_to_many_associations_upon_destroy + p = RichPerson.create! first_name: 'Jon' + p.treasures.create! + assert !p.treasures.empty? + p.destroy + assert p.treasures.empty? + assert RichPerson.connection.select_all("SELECT * FROM peoples_treasures WHERE rich_person_id = 1").empty? + end end class OptimisticLockingWithSchemaChangeTest < ActiveRecord::TestCase @@ -278,6 +303,8 @@ class OptimisticLockingWithSchemaChangeTest < ActiveRecord::TestCase assert_equal true, p1.frozen? assert_raises(ActiveRecord::RecordNotFound) { Person.find(p1.id) } assert_raises(ActiveRecord::RecordNotFound) { LegacyThing.find(t.id) } + ensure + remove_counter_column_from(Person, 'legacy_things_count') end private @@ -289,14 +316,14 @@ class OptimisticLockingWithSchemaChangeTest < ActiveRecord::TestCase model.update_all(col => 0) if current_adapter?(:OpenBaseAdapter) end - def remove_counter_column_from(model) - model.connection.remove_column model.table_name, :test_count + def remove_counter_column_from(model, col = :test_count) + model.connection.remove_column model.table_name, col model.reset_column_information end def counter_test(model, expected_count) add_counter_column_to(model) - object = model.find(:first) + object = model.first assert_equal 0, object.test_count assert_equal 0, object.send(model.locking_column) yield object.id @@ -331,18 +358,7 @@ unless current_adapter?(:SybaseAdapter, :OpenBaseAdapter) || in_memory_db? def test_sane_find_with_lock assert_nothing_raised do Person.transaction do - Person.find 1, :lock => true - end - end - end - - # Test scoped lock. - def test_sane_find_with_scoped_lock - assert_nothing_raised do - Person.transaction do - Person.send(:with_scope, :find => { :lock => true }) do - Person.find 1 - end + Person.lock.find(1) end end end @@ -353,7 +369,7 @@ unless current_adapter?(:SybaseAdapter, :OpenBaseAdapter) || in_memory_db? def test_eager_find_with_lock assert_nothing_raised do Person.transaction do - Person.find 1, :include => :readers, :lock => true + Person.includes(:readers).lock.find(1) end end end @@ -371,22 +387,32 @@ unless current_adapter?(:SybaseAdapter, :OpenBaseAdapter) || in_memory_db? end end + def test_with_lock_commits_transaction + person = Person.find 1 + person.with_lock do + person.first_name = 'fooman' + person.save! + end + assert_equal 'fooman', person.reload.first_name + end + + def test_with_lock_rolls_back_transaction + person = Person.find 1 + old = person.first_name + person.with_lock do + person.first_name = 'fooman' + person.save! + raise 'oops' + end rescue nil + assert_equal old, person.reload.first_name + end + if current_adapter?(:PostgreSQLAdapter, :OracleAdapter) def test_no_locks_no_wait first, second = duel { Person.find 1 } assert first.end > second.end end - # Hit by ruby deadlock detection since connection checkout is mutexed. - if RUBY_VERSION < '1.9.0' - def test_second_lock_waits - assert [0.2, 1, 5].any? { |zzz| - first, second = duel(zzz) { Person.find 1, :lock => true } - second.end > first.end - } - end - end - protected def duel(zzz = 5) t0, t1, t2, t3 = nil, nil, nil, nil diff --git a/activerecord/test/cases/log_subscriber_test.rb b/activerecord/test/cases/log_subscriber_test.rb index 9e8475465e..acd2fcdad4 100644 --- a/activerecord/test/cases/log_subscriber_test.rb +++ b/activerecord/test/cases/log_subscriber_test.rb @@ -5,14 +5,13 @@ require "active_support/log_subscriber/test_helper" class LogSubscriberTest < ActiveRecord::TestCase include ActiveSupport::LogSubscriber::TestHelper - include ActiveSupport::BufferedLogger::Severity + include ActiveSupport::Logger::Severity fixtures :posts def setup @old_logger = ActiveRecord::Base.logger - @using_identity_map = ActiveRecord::IdentityMap.enabled? - ActiveRecord::IdentityMap.enabled = false + Developer.primary_key super ActiveRecord::LogSubscriber.attach_to(:active_record) end @@ -21,7 +20,6 @@ class LogSubscriberTest < ActiveRecord::TestCase super ActiveRecord::LogSubscriber.log_subscribers.pop ActiveRecord::Base.logger = @old_logger - ActiveRecord::IdentityMap.enabled = @using_identity_map end def set_logger(logger) @@ -102,13 +100,4 @@ class LogSubscriberTest < ActiveRecord::TestCase def test_initializes_runtime Thread.new { assert_equal 0, ActiveRecord::LogSubscriber.runtime }.join end - - def test_log - ActiveRecord::IdentityMap.use do - Post.find 1 - Post.find 1 - end - wait - assert_match(/From Identity Map/, @logger.logged(:debug).last) - end end diff --git a/activerecord/test/cases/mass_assignment_security_test.rb b/activerecord/test/cases/mass_assignment_security_test.rb index 9fff50edcb..8122857f52 100644 --- a/activerecord/test/cases/mass_assignment_security_test.rb +++ b/activerecord/test/cases/mass_assignment_security_test.rb @@ -50,6 +50,13 @@ module MassAssignmentTestHelpers assert_equal 'm', person.gender assert_equal 'rides a sweet bike', person.comments end + + def with_strict_sanitizer + ActiveRecord::Base.mass_assignment_sanitizer = :strict + yield + ensure + ActiveRecord::Base.mass_assignment_sanitizer = :logger + end end module MassAssignmentRelationTestHelpers @@ -323,6 +330,13 @@ class MassAssignmentSecurityHasOneRelationsTest < ActiveRecord::TestCase assert_all_attributes(best_friend) end + def test_has_one_build_with_strict_sanitizer + with_strict_sanitizer do + best_friend = @person.build_best_friend(attributes_hash.except(:id, :comments)) + assert_equal @person.id, best_friend.best_friend_id + end + end + # create def test_has_one_create_with_attr_protected_attributes @@ -350,6 +364,13 @@ class MassAssignmentSecurityHasOneRelationsTest < ActiveRecord::TestCase assert_all_attributes(best_friend) end + def test_has_one_create_with_strict_sanitizer + with_strict_sanitizer do + best_friend = @person.create_best_friend(attributes_hash.except(:id, :comments)) + assert_equal @person.id, best_friend.best_friend_id + end + end + # create! def test_has_one_create_with_bang_with_attr_protected_attributes @@ -377,6 +398,13 @@ class MassAssignmentSecurityHasOneRelationsTest < ActiveRecord::TestCase assert_all_attributes(best_friend) end + def test_has_one_create_with_bang_with_strict_sanitizer + with_strict_sanitizer do + best_friend = @person.create_best_friend!(attributes_hash.except(:id, :comments)) + assert_equal @person.id, best_friend.best_friend_id + end + end + end @@ -438,6 +466,13 @@ class MassAssignmentSecurityBelongsToRelationsTest < ActiveRecord::TestCase assert_all_attributes(best_friend) end + def test_belongs_to_create_with_strict_sanitizer + with_strict_sanitizer do + best_friend = @person.create_best_friend_of(attributes_hash.except(:id, :comments)) + assert_equal best_friend.id, @person.best_friend_of_id + end + end + # create! def test_belongs_to_create_with_bang_with_attr_protected_attributes @@ -465,6 +500,13 @@ class MassAssignmentSecurityBelongsToRelationsTest < ActiveRecord::TestCase assert_all_attributes(best_friend) end + def test_belongs_to_create_with_bang_with_strict_sanitizer + with_strict_sanitizer do + best_friend = @person.create_best_friend_of!(attributes_hash.except(:id, :comments)) + assert_equal best_friend.id, @person.best_friend_of_id + end + end + end @@ -499,6 +541,13 @@ class MassAssignmentSecurityHasManyRelationsTest < ActiveRecord::TestCase assert_all_attributes(best_friend) end + def test_has_many_build_with_strict_sanitizer + with_strict_sanitizer do + best_friend = @person.best_friends.build(attributes_hash.except(:id, :comments)) + assert_equal @person.id, best_friend.best_friend_id + end + end + # create def test_has_many_create_with_attr_protected_attributes @@ -526,6 +575,13 @@ class MassAssignmentSecurityHasManyRelationsTest < ActiveRecord::TestCase assert_all_attributes(best_friend) end + def test_has_many_create_with_strict_sanitizer + with_strict_sanitizer do + best_friend = @person.best_friends.create(attributes_hash.except(:id, :comments)) + assert_equal @person.id, best_friend.best_friend_id + end + end + # create! def test_has_many_create_with_bang_with_attr_protected_attributes @@ -553,6 +609,13 @@ class MassAssignmentSecurityHasManyRelationsTest < ActiveRecord::TestCase assert_all_attributes(best_friend) end + def test_has_many_create_with_bang_with_strict_sanitizer + with_strict_sanitizer do + best_friend = @person.best_friends.create!(attributes_hash.except(:id, :comments)) + assert_equal @person.id, best_friend.best_friend_id + end + end + end diff --git a/activerecord/test/cases/method_scoping_test.rb b/activerecord/test/cases/method_scoping_test.rb deleted file mode 100644 index 0ab4f30363..0000000000 --- a/activerecord/test/cases/method_scoping_test.rb +++ /dev/null @@ -1,558 +0,0 @@ -# This file can be removed once with_exclusive_scope and with_scope are removed. -# All the tests were already ported to relation_scoping_test.rb when the new -# relation scoping API was added. - -require "cases/helper" -require 'models/post' -require 'models/author' -require 'models/developer' -require 'models/project' -require 'models/comment' - -class MethodScopingTest < ActiveRecord::TestCase - fixtures :authors, :developers, :projects, :comments, :posts, :developers_projects - - def test_set_conditions - Developer.send(:with_scope, :find => { :conditions => 'just a test...' }) do - assert_match '(just a test...)', Developer.scoped.to_sql - end - end - - def test_scoped_find - Developer.send(:with_scope, :find => { :conditions => "name = 'David'" }) do - assert_nothing_raised { Developer.find(1) } - end - end - - def test_scoped_find_first - Developer.send(:with_scope, :find => { :conditions => "salary = 100000" }) do - assert_equal Developer.find(10), Developer.find(:first, :order => 'name') - end - end - - def test_scoped_find_last - highest_salary = Developer.find(:first, :order => "salary DESC") - - Developer.send(:with_scope, :find => { :order => "salary" }) do - assert_equal highest_salary, Developer.last - end - end - - def test_scoped_find_last_preserves_scope - lowest_salary = Developer.find(:first, :order => "salary ASC") - highest_salary = Developer.find(:first, :order => "salary DESC") - - Developer.send(:with_scope, :find => { :order => "salary" }) do - assert_equal highest_salary, Developer.last - assert_equal lowest_salary, Developer.first - end - end - - def test_scoped_find_combines_conditions - Developer.send(:with_scope, :find => { :conditions => "salary = 9000" }) do - assert_equal developers(:poor_jamis), Developer.find(:first, :conditions => "name = 'Jamis'") - end - end - - def test_scoped_find_sanitizes_conditions - Developer.send(:with_scope, :find => { :conditions => ['salary = ?', 9000] }) do - assert_equal developers(:poor_jamis), Developer.find(:first) - end - end - - def test_scoped_find_combines_and_sanitizes_conditions - Developer.send(:with_scope, :find => { :conditions => ['salary = ?', 9000] }) do - assert_equal developers(:poor_jamis), Developer.find(:first, :conditions => ['name = ?', 'Jamis']) - end - end - - def test_scoped_find_all - Developer.send(:with_scope, :find => { :conditions => "name = 'David'" }) do - assert_equal [developers(:david)], Developer.all - end - end - - def test_scoped_find_select - Developer.send(:with_scope, :find => { :select => "id, name" }) do - developer = Developer.find(:first, :conditions => "name = 'David'") - assert_equal "David", developer.name - assert !developer.has_attribute?(:salary) - end - end - - def test_scope_select_concatenates - Developer.send(:with_scope, :find => { :select => "name" }) do - developer = Developer.find(:first, :select => 'id, salary', :conditions => "name = 'David'") - assert_equal 80000, developer.salary - assert developer.has_attribute?(:id) - assert developer.has_attribute?(:name) - assert developer.has_attribute?(:salary) - end - end - - def test_scoped_count - Developer.send(:with_scope, :find => { :conditions => "name = 'David'" }) do - assert_equal 1, Developer.count - end - - Developer.send(:with_scope, :find => { :conditions => 'salary = 100000' }) do - assert_equal 8, Developer.count - assert_equal 1, Developer.count(:conditions => "name LIKE 'fixture_1%'") - end - end - - def test_scoped_find_include - # with the include, will retrieve only developers for the given project - scoped_developers = Developer.send(:with_scope, :find => { :include => :projects }) do - Developer.find(:all, :conditions => 'projects.id = 2') - end - assert scoped_developers.include?(developers(:david)) - assert !scoped_developers.include?(developers(:jamis)) - assert_equal 1, scoped_developers.size - end - - def test_scoped_find_joins - scoped_developers = Developer.send(:with_scope, :find => { :joins => 'JOIN developers_projects ON id = developer_id' } ) do - Developer.find(:all, :conditions => 'developers_projects.project_id = 2') - end - assert scoped_developers.include?(developers(:david)) - assert !scoped_developers.include?(developers(:jamis)) - assert_equal 1, scoped_developers.size - assert_equal developers(:david).attributes, scoped_developers.first.attributes - end - - def test_scoped_find_using_new_style_joins - scoped_developers = Developer.send(:with_scope, :find => { :joins => :projects }) do - Developer.find(:all, :conditions => 'projects.id = 2') - end - assert scoped_developers.include?(developers(:david)) - assert !scoped_developers.include?(developers(:jamis)) - assert_equal 1, scoped_developers.size - assert_equal developers(:david).attributes, scoped_developers.first.attributes - end - - def test_scoped_find_merges_old_style_joins - scoped_authors = Author.send(:with_scope, :find => { :joins => 'INNER JOIN posts ON authors.id = posts.author_id ' }) do - Author.find(:all, :select => 'DISTINCT authors.*', :joins => 'INNER JOIN comments ON posts.id = comments.post_id', :conditions => 'comments.id = 1') - end - assert scoped_authors.include?(authors(:david)) - assert !scoped_authors.include?(authors(:mary)) - assert_equal 1, scoped_authors.size - assert_equal authors(:david).attributes, scoped_authors.first.attributes - end - - def test_scoped_find_merges_new_style_joins - scoped_authors = Author.send(:with_scope, :find => { :joins => :posts }) do - Author.find(:all, :select => 'DISTINCT authors.*', :joins => :comments, :conditions => 'comments.id = 1') - end - assert scoped_authors.include?(authors(:david)) - assert !scoped_authors.include?(authors(:mary)) - assert_equal 1, scoped_authors.size - assert_equal authors(:david).attributes, scoped_authors.first.attributes - end - - def test_scoped_find_merges_new_and_old_style_joins - scoped_authors = Author.send(:with_scope, :find => { :joins => :posts }) do - Author.find(:all, :select => 'DISTINCT authors.*', :joins => 'JOIN comments ON posts.id = comments.post_id', :conditions => 'comments.id = 1') - end - assert scoped_authors.include?(authors(:david)) - assert !scoped_authors.include?(authors(:mary)) - assert_equal 1, scoped_authors.size - assert_equal authors(:david).attributes, scoped_authors.first.attributes - end - - def test_scoped_find_merges_string_array_style_and_string_style_joins - scoped_authors = Author.send(:with_scope, :find => { :joins => ["INNER JOIN posts ON posts.author_id = authors.id"]}) do - Author.find(:all, :select => 'DISTINCT authors.*', :joins => 'INNER JOIN comments ON posts.id = comments.post_id', :conditions => 'comments.id = 1') - end - assert scoped_authors.include?(authors(:david)) - assert !scoped_authors.include?(authors(:mary)) - assert_equal 1, scoped_authors.size - assert_equal authors(:david).attributes, scoped_authors.first.attributes - end - - def test_scoped_find_merges_string_array_style_and_hash_style_joins - scoped_authors = Author.send(:with_scope, :find => { :joins => :posts}) do - Author.find(:all, :select => 'DISTINCT authors.*', :joins => ['INNER JOIN comments ON posts.id = comments.post_id'], :conditions => 'comments.id = 1') - end - assert scoped_authors.include?(authors(:david)) - assert !scoped_authors.include?(authors(:mary)) - assert_equal 1, scoped_authors.size - assert_equal authors(:david).attributes, scoped_authors.first.attributes - end - - def test_scoped_find_merges_joins_and_eliminates_duplicate_string_joins - scoped_authors = Author.send(:with_scope, :find => { :joins => 'INNER JOIN posts ON posts.author_id = authors.id'}) do - Author.find(:all, :select => 'DISTINCT authors.*', :joins => ["INNER JOIN posts ON posts.author_id = authors.id", "INNER JOIN comments ON posts.id = comments.post_id"], :conditions => 'comments.id = 1') - end - assert scoped_authors.include?(authors(:david)) - assert !scoped_authors.include?(authors(:mary)) - assert_equal 1, scoped_authors.size - assert_equal authors(:david).attributes, scoped_authors.first.attributes - end - - def test_scoped_find_strips_spaces_from_string_joins_and_eliminates_duplicate_string_joins - scoped_authors = Author.send(:with_scope, :find => { :joins => ' INNER JOIN posts ON posts.author_id = authors.id '}) do - Author.find(:all, :select => 'DISTINCT authors.*', :joins => ['INNER JOIN posts ON posts.author_id = authors.id'], :conditions => 'posts.id = 1') - end - assert scoped_authors.include?(authors(:david)) - assert !scoped_authors.include?(authors(:mary)) - assert_equal 1, scoped_authors.size - assert_equal authors(:david).attributes, scoped_authors.first.attributes - end - - def test_scoped_count_include - # with the include, will retrieve only developers for the given project - Developer.send(:with_scope, :find => { :include => :projects }) do - assert_equal 1, Developer.count(:conditions => 'projects.id = 2') - end - end - - def test_scope_for_create_only_uses_equal - table = VerySpecialComment.arel_table - relation = VerySpecialComment.scoped - relation.where_values << table[:id].not_eq(1) - assert_equal({:type => "VerySpecialComment"}, relation.send(:scope_for_create)) - end - - def test_scoped_create - new_comment = nil - - VerySpecialComment.send(:with_scope, :create => { :post_id => 1 }) do - assert_equal({:post_id => 1, :type => 'VerySpecialComment' }, VerySpecialComment.scoped.send(:scope_for_create)) - new_comment = VerySpecialComment.create :body => "Wonderful world" - end - - assert Post.find(1).comments.include?(new_comment) - end - - def test_scoped_create_with_join_and_merge - Comment.where(:body => "but Who's Buying?").joins(:post).merge(Post.where(:body => 'Peace Sells...')).with_scope do - assert_equal({:body => "but Who's Buying?"}, Comment.scoped.scope_for_create) - end - end - - def test_immutable_scope - options = { :conditions => "name = 'David'" } - Developer.send(:with_scope, :find => options) do - assert_equal %w(David), Developer.all.map(&:name) - options[:conditions] = "name != 'David'" - assert_equal %w(David), Developer.all.map(&:name) - end - - scope = { :find => { :conditions => "name = 'David'" }} - Developer.send(:with_scope, scope) do - assert_equal %w(David), Developer.all.map(&:name) - scope[:find][:conditions] = "name != 'David'" - assert_equal %w(David), Developer.all.map(&:name) - end - end - - def test_scoped_with_duck_typing - scoping = Struct.new(:current_scope).new(:find => { :conditions => ["name = ?", 'David'] }) - Developer.send(:with_scope, scoping) do - assert_equal %w(David), Developer.all.map(&:name) - end - end - - def test_ensure_that_method_scoping_is_correctly_restored - begin - Developer.send(:with_scope, :find => { :conditions => "name = 'Jamis'" }) do - raise "an exception" - end - rescue - end - - assert !Developer.scoped.where_values.include?("name = 'Jamis'") - end -end - -class NestedScopingTest < ActiveRecord::TestCase - fixtures :authors, :developers, :projects, :comments, :posts - - def test_merge_options - Developer.send(:with_scope, :find => { :conditions => 'salary = 80000' }) do - Developer.send(:with_scope, :find => { :limit => 10 }) do - devs = Developer.scoped - assert_match '(salary = 80000)', devs.to_sql - assert_equal 10, devs.taken - end - end - end - - def test_merge_inner_scope_has_priority - Developer.send(:with_scope, :find => { :limit => 5 }) do - Developer.send(:with_scope, :find => { :limit => 10 }) do - assert_equal 10, Developer.scoped.taken - end - end - end - - def test_replace_options - Developer.send(:with_scope, :find => { :conditions => {:name => 'David'} }) do - Developer.send(:with_exclusive_scope, :find => { :conditions => {:name => 'Jamis'} }) do - assert_equal 'Jamis', Developer.scoped.send(:scope_for_create)[:name] - end - - assert_equal 'David', Developer.scoped.send(:scope_for_create)[:name] - end - end - - def test_with_exclusive_scope_with_relation - assert_raise(ArgumentError) do - Developer.all_johns - end - end - - def test_append_conditions - Developer.send(:with_scope, :find => { :conditions => "name = 'David'" }) do - Developer.send(:with_scope, :find => { :conditions => 'salary = 80000' }) do - devs = Developer.scoped - assert_match "(name = 'David') AND (salary = 80000)", devs.to_sql - assert_equal(1, Developer.count) - end - Developer.send(:with_scope, :find => { :conditions => "name = 'Maiha'" }) do - assert_equal(0, Developer.count) - end - end - end - - def test_merge_and_append_options - Developer.send(:with_scope, :find => { :conditions => 'salary = 80000', :limit => 10 }) do - Developer.send(:with_scope, :find => { :conditions => "name = 'David'" }) do - devs = Developer.scoped - assert_match "(salary = 80000) AND (name = 'David')", devs.to_sql - assert_equal 10, devs.taken - end - end - end - - def test_nested_scoped_find - Developer.send(:with_scope, :find => { :conditions => "name = 'Jamis'" }) do - Developer.send(:with_exclusive_scope, :find => { :conditions => "name = 'David'" }) do - assert_nothing_raised { Developer.find(1) } - assert_equal('David', Developer.find(:first).name) - end - assert_equal('Jamis', Developer.find(:first).name) - end - end - - def test_nested_scoped_find_include - Developer.send(:with_scope, :find => { :include => :projects }) do - Developer.send(:with_scope, :find => { :conditions => "projects.id = 2" }) do - assert_nothing_raised { Developer.find(1) } - assert_equal('David', Developer.find(:first).name) - end - end - end - - def test_nested_scoped_find_merged_include - # :include's remain unique and don't "double up" when merging - Developer.send(:with_scope, :find => { :include => :projects, :conditions => "projects.id = 2" }) do - Developer.send(:with_scope, :find => { :include => :projects }) do - assert_equal 1, Developer.scoped.includes_values.uniq.length - assert_equal 'David', Developer.find(:first).name - end - end - - # the nested scope doesn't remove the first :include - Developer.send(:with_scope, :find => { :include => :projects, :conditions => "projects.id = 2" }) do - Developer.send(:with_scope, :find => { :include => [] }) do - assert_equal 1, Developer.scoped.includes_values.uniq.length - assert_equal('David', Developer.find(:first).name) - end - end - - # mixing array and symbol include's will merge correctly - Developer.send(:with_scope, :find => { :include => [:projects], :conditions => "projects.id = 2" }) do - Developer.send(:with_scope, :find => { :include => :projects }) do - assert_equal 1, Developer.scoped.includes_values.uniq.length - assert_equal('David', Developer.find(:first).name) - end - end - end - - def test_nested_scoped_find_replace_include - Developer.send(:with_scope, :find => { :include => :projects }) do - Developer.send(:with_exclusive_scope, :find => { :include => [] }) do - assert_equal 0, Developer.scoped.includes_values.length - end - end - end - - def test_three_level_nested_exclusive_scoped_find - Developer.send(:with_scope, :find => { :conditions => "name = 'Jamis'" }) do - assert_equal('Jamis', Developer.find(:first).name) - - Developer.send(:with_exclusive_scope, :find => { :conditions => "name = 'David'" }) do - assert_equal('David', Developer.find(:first).name) - - Developer.send(:with_exclusive_scope, :find => { :conditions => "name = 'Maiha'" }) do - assert_equal(nil, Developer.find(:first)) - end - - # ensure that scoping is restored - assert_equal('David', Developer.find(:first).name) - end - - # ensure that scoping is restored - assert_equal('Jamis', Developer.find(:first).name) - end - end - - def test_merged_scoped_find - poor_jamis = developers(:poor_jamis) - Developer.send(:with_scope, :find => { :conditions => "salary < 100000" }) do - Developer.send(:with_scope, :find => { :offset => 1, :order => 'id asc' }) do - # Oracle adapter does not generated space after asc therefore trailing space removed from regex - assert_sql(/ORDER BY\s+id asc/) do - assert_equal(poor_jamis, Developer.find(:first, :order => 'id asc')) - end - end - end - end - - def test_merged_scoped_find_sanitizes_conditions - Developer.send(:with_scope, :find => { :conditions => ["name = ?", 'David'] }) do - Developer.send(:with_scope, :find => { :conditions => ['salary = ?', 9000] }) do - assert_raise(ActiveRecord::RecordNotFound) { developers(:poor_jamis) } - end - end - end - - def test_nested_scoped_find_combines_and_sanitizes_conditions - Developer.send(:with_scope, :find => { :conditions => ["name = ?", 'David'] }) do - Developer.send(:with_exclusive_scope, :find => { :conditions => ['salary = ?', 9000] }) do - assert_equal developers(:poor_jamis), Developer.find(:first) - assert_equal developers(:poor_jamis), Developer.find(:first, :conditions => ['name = ?', 'Jamis']) - end - end - end - - def test_merged_scoped_find_combines_and_sanitizes_conditions - Developer.send(:with_scope, :find => { :conditions => ["name = ?", 'David'] }) do - Developer.send(:with_scope, :find => { :conditions => ['salary > ?', 9000] }) do - assert_equal %w(David), Developer.all.map(&:name) - end - end - end - - def test_nested_scoped_create - comment = nil - Comment.send(:with_scope, :create => { :post_id => 1}) do - Comment.send(:with_scope, :create => { :post_id => 2}) do - assert_equal({:post_id => 2}, Comment.scoped.send(:scope_for_create)) - comment = Comment.create :body => "Hey guys, nested scopes are broken. Please fix!" - end - end - assert_equal 2, comment.post_id - end - - def test_nested_exclusive_scope_for_create - comment = nil - - Comment.send(:with_scope, :create => { :body => "Hey guys, nested scopes are broken. Please fix!" }) do - Comment.send(:with_exclusive_scope, :create => { :post_id => 1 }) do - assert_equal({:post_id => 1}, Comment.scoped.send(:scope_for_create)) - assert_blank Comment.new.body - comment = Comment.create :body => "Hey guys" - end - end - assert_equal 1, comment.post_id - assert_equal 'Hey guys', comment.body - end - - def test_merged_scoped_find_on_blank_conditions - [nil, " ", [], {}].each do |blank| - Developer.send(:with_scope, :find => {:conditions => blank}) do - Developer.send(:with_scope, :find => {:conditions => blank}) do - assert_nothing_raised { Developer.find(:first) } - end - end - end - end - - def test_merged_scoped_find_on_blank_bind_conditions - [ [""], ["",{}] ].each do |blank| - Developer.send(:with_scope, :find => {:conditions => blank}) do - Developer.send(:with_scope, :find => {:conditions => blank}) do - assert_nothing_raised { Developer.find(:first) } - end - end - end - end - - def test_immutable_nested_scope - options1 = { :conditions => "name = 'Jamis'" } - options2 = { :conditions => "name = 'David'" } - Developer.send(:with_scope, :find => options1) do - Developer.send(:with_exclusive_scope, :find => options2) do - assert_equal %w(David), Developer.all.map(&:name) - options1[:conditions] = options2[:conditions] = nil - assert_equal %w(David), Developer.all.map(&:name) - end - end - end - - def test_immutable_merged_scope - options1 = { :conditions => "name = 'Jamis'" } - options2 = { :conditions => "salary > 10000" } - Developer.send(:with_scope, :find => options1) do - Developer.send(:with_scope, :find => options2) do - assert_equal %w(Jamis), Developer.all.map(&:name) - options1[:conditions] = options2[:conditions] = nil - assert_equal %w(Jamis), Developer.all.map(&:name) - end - end - end - - def test_ensure_that_method_scoping_is_correctly_restored - Developer.send(:with_scope, :find => { :conditions => "name = 'David'" }) do - begin - Developer.send(:with_scope, :find => { :conditions => "name = 'Maiha'" }) do - raise "an exception" - end - rescue - end - - assert Developer.scoped.where_values.include?("name = 'David'") - assert !Developer.scoped.where_values.include?("name = 'Maiha'") - end - end - - def test_nested_scoped_find_merges_old_style_joins - scoped_authors = Author.send(:with_scope, :find => { :joins => 'INNER JOIN posts ON authors.id = posts.author_id' }) do - Author.send(:with_scope, :find => { :joins => 'INNER JOIN comments ON posts.id = comments.post_id' }) do - Author.find(:all, :select => 'DISTINCT authors.*', :conditions => 'comments.id = 1') - end - end - assert scoped_authors.include?(authors(:david)) - assert !scoped_authors.include?(authors(:mary)) - assert_equal 1, scoped_authors.size - assert_equal authors(:david).attributes, scoped_authors.first.attributes - end - - def test_nested_scoped_find_merges_new_style_joins - scoped_authors = Author.send(:with_scope, :find => { :joins => :posts }) do - Author.send(:with_scope, :find => { :joins => :comments }) do - Author.find(:all, :select => 'DISTINCT authors.*', :conditions => 'comments.id = 1') - end - end - assert scoped_authors.include?(authors(:david)) - assert !scoped_authors.include?(authors(:mary)) - assert_equal 1, scoped_authors.size - assert_equal authors(:david).attributes, scoped_authors.first.attributes - end - - def test_nested_scoped_find_merges_new_and_old_style_joins - scoped_authors = Author.send(:with_scope, :find => { :joins => :posts }) do - Author.send(:with_scope, :find => { :joins => 'INNER JOIN comments ON posts.id = comments.post_id' }) do - Author.find(:all, :select => 'DISTINCT authors.*', :joins => '', :conditions => 'comments.id = 1') - end - end - assert scoped_authors.include?(authors(:david)) - assert !scoped_authors.include?(authors(:mary)) - assert_equal 1, scoped_authors.size - assert_equal authors(:david).attributes, scoped_authors.first.attributes - end -end diff --git a/activerecord/test/cases/migration/change_schema_test.rb b/activerecord/test/cases/migration/change_schema_test.rb new file mode 100644 index 0000000000..f0b1f74bd3 --- /dev/null +++ b/activerecord/test/cases/migration/change_schema_test.rb @@ -0,0 +1,328 @@ +require 'cases/helper' + +module ActiveRecord + class Migration + class ChangeSchemaTest < ActiveRecord::TestCase + attr_reader :connection, :table_name + + def setup + super + @connection = ActiveRecord::Base.connection + @table_name = :testings + end + + def teardown + super + connection.drop_table :testings rescue nil + ActiveRecord::Base.primary_key_prefix_type = nil + end + + def test_create_table_without_id + testing_table_with_only_foo_attribute do + assert_equal connection.columns(:testings).size, 1 + end + end + + def test_add_column_with_primary_key_attribute + testing_table_with_only_foo_attribute do + connection.add_column :testings, :id, :primary_key + assert_equal connection.columns(:testings).size, 2 + end + end + + def test_create_table_adds_id + connection.create_table :testings do |t| + t.column :foo, :string + end + + assert_equal %w(foo id), connection.columns(:testings).map(&:name).sort + end + + def test_create_table_with_not_null_column + connection.create_table :testings do |t| + t.column :foo, :string, :null => false + end + + assert_raises(ActiveRecord::StatementInvalid) do + connection.execute "insert into testings (foo) values (NULL)" + end + end + + def test_create_table_with_defaults + # MySQL doesn't allow defaults on TEXT or BLOB columns. + mysql = current_adapter?(:MysqlAdapter) || current_adapter?(:Mysql2Adapter) + + connection.create_table :testings do |t| + t.column :one, :string, :default => "hello" + t.column :two, :boolean, :default => true + t.column :three, :boolean, :default => false + t.column :four, :integer, :default => 1 + t.column :five, :text, :default => "hello" unless mysql + end + + columns = connection.columns(:testings) + one = columns.detect { |c| c.name == "one" } + two = columns.detect { |c| c.name == "two" } + three = columns.detect { |c| c.name == "three" } + four = columns.detect { |c| c.name == "four" } + five = columns.detect { |c| c.name == "five" } unless mysql + + assert_equal "hello", one.default + assert_equal true, two.default + assert_equal false, three.default + assert_equal 1, four.default + assert_equal "hello", five.default unless mysql + end + + def test_create_table_with_limits + connection.create_table :testings do |t| + t.column :foo, :string, :limit => 255 + + t.column :default_int, :integer + + t.column :one_int, :integer, :limit => 1 + t.column :four_int, :integer, :limit => 4 + t.column :eight_int, :integer, :limit => 8 + t.column :eleven_int, :integer, :limit => 11 + end + + columns = connection.columns(:testings) + foo = columns.detect { |c| c.name == "foo" } + assert_equal 255, foo.limit + + default = columns.detect { |c| c.name == "default_int" } + one = columns.detect { |c| c.name == "one_int" } + four = columns.detect { |c| c.name == "four_int" } + eight = columns.detect { |c| c.name == "eight_int" } + eleven = columns.detect { |c| c.name == "eleven_int" } + + if current_adapter?(:PostgreSQLAdapter) + assert_equal 'integer', default.sql_type + assert_equal 'smallint', one.sql_type + assert_equal 'integer', four.sql_type + assert_equal 'bigint', eight.sql_type + assert_equal 'integer', eleven.sql_type + elsif current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter) + assert_match 'int(11)', default.sql_type + assert_match 'tinyint', one.sql_type + assert_match 'int', four.sql_type + assert_match 'bigint', eight.sql_type + assert_match 'int(11)', eleven.sql_type + elsif current_adapter?(:OracleAdapter) + assert_equal 'NUMBER(38)', default.sql_type + assert_equal 'NUMBER(1)', one.sql_type + assert_equal 'NUMBER(4)', four.sql_type + assert_equal 'NUMBER(8)', eight.sql_type + end + end + + def test_create_table_with_primary_key_prefix_as_table_name_with_underscore + ActiveRecord::Base.primary_key_prefix_type = :table_name_with_underscore + + connection.create_table :testings do |t| + t.column :foo, :string + end + + assert_equal %w(foo testing_id), connection.columns(:testings).map(&:name).sort + end + + def test_create_table_with_primary_key_prefix_as_table_name + ActiveRecord::Base.primary_key_prefix_type = :table_name + + connection.create_table :testings do |t| + t.column :foo, :string + end + + assert_equal %w(foo testingid), connection.columns(:testings).map(&:name).sort + end + + def test_create_table_with_timestamps_should_create_datetime_columns + connection.create_table table_name do |t| + t.timestamps + end + created_columns = connection.columns(table_name) + + created_at_column = created_columns.detect {|c| c.name == 'created_at' } + updated_at_column = created_columns.detect {|c| c.name == 'updated_at' } + + assert !created_at_column.null + assert !updated_at_column.null + end + + def test_create_table_with_timestamps_should_create_datetime_columns_with_options + connection.create_table table_name do |t| + t.timestamps :null => false + end + created_columns = connection.columns(table_name) + + created_at_column = created_columns.detect {|c| c.name == 'created_at' } + updated_at_column = created_columns.detect {|c| c.name == 'updated_at' } + + assert !created_at_column.null + assert !updated_at_column.null + end + + def test_create_table_without_a_block + connection.create_table table_name + end + + def test_add_column_not_null_without_default + # Sybase, and SQLite3 will not allow you to add a NOT NULL + # column to a table without a default value. + if current_adapter?(:SybaseAdapter, :SQLite3Adapter) + skip "not supported on #{connection.class}" + end + + connection.create_table :testings do |t| + t.column :foo, :string + end + connection.add_column :testings, :bar, :string, :null => false + + assert_raise(ActiveRecord::StatementInvalid) do + connection.execute "insert into testings (foo, bar) values ('hello', NULL)" + end + end + + def test_add_column_not_null_with_default + connection.create_table :testings do |t| + t.column :foo, :string + end + + con = connection + connection.enable_identity_insert("testings", true) if current_adapter?(:SybaseAdapter) + connection.execute "insert into testings (#{con.quote_column_name('id')}, #{con.quote_column_name('foo')}) values (1, 'hello')" + connection.enable_identity_insert("testings", false) if current_adapter?(:SybaseAdapter) + assert_nothing_raised {connection.add_column :testings, :bar, :string, :null => false, :default => "default" } + + assert_raises(ActiveRecord::StatementInvalid) do + unless current_adapter?(:OpenBaseAdapter) + connection.execute "insert into testings (#{con.quote_column_name('id')}, #{con.quote_column_name('foo')}, #{con.quote_column_name('bar')}) values (2, 'hello', NULL)" + else + connection.insert("INSERT INTO testings (#{con.quote_column_name('id')}, #{con.quote_column_name('foo')}, #{con.quote_column_name('bar')}) VALUES (2, 'hello', NULL)", + "Testing Insert","id",2) + end + end + end + + def test_change_column_quotes_column_names + connection.create_table :testings do |t| + t.column :select, :string + end + + connection.change_column :testings, :select, :string, :limit => 10 + + # Oracle needs primary key value from sequence + if current_adapter?(:OracleAdapter) + connection.execute "insert into testings (id, #{connection.quote_column_name('select')}) values (testings_seq.nextval, '7 chars')" + else + connection.execute "insert into testings (#{connection.quote_column_name('select')}) values ('7 chars')" + end + end + + def test_keeping_default_and_notnull_constaint_on_change + connection.create_table :testings do |t| + t.column :title, :string + end + person_klass = Class.new(ActiveRecord::Base) + person_klass.table_name = 'testings' + + person_klass.connection.add_column "testings", "wealth", :integer, :null => false, :default => 99 + person_klass.reset_column_information + assert_equal 99, person_klass.columns_hash["wealth"].default + assert_equal false, person_klass.columns_hash["wealth"].null + # Oracle needs primary key value from sequence + if current_adapter?(:OracleAdapter) + assert_nothing_raised {person_klass.connection.execute("insert into testings (id, title) values (testings_seq.nextval, 'tester')")} + else + assert_nothing_raised {person_klass.connection.execute("insert into testings (title) values ('tester')")} + end + + # change column default to see that column doesn't lose its not null definition + person_klass.connection.change_column_default "testings", "wealth", 100 + person_klass.reset_column_information + assert_equal 100, person_klass.columns_hash["wealth"].default + assert_equal false, person_klass.columns_hash["wealth"].null + + # rename column to see that column doesn't lose its not null and/or default definition + person_klass.connection.rename_column "testings", "wealth", "money" + person_klass.reset_column_information + assert_nil person_klass.columns_hash["wealth"] + assert_equal 100, person_klass.columns_hash["money"].default + assert_equal false, person_klass.columns_hash["money"].null + + # change column + person_klass.connection.change_column "testings", "money", :integer, :null => false, :default => 1000 + person_klass.reset_column_information + assert_equal 1000, person_klass.columns_hash["money"].default + assert_equal false, person_klass.columns_hash["money"].null + + # change column, make it nullable and clear default + person_klass.connection.change_column "testings", "money", :integer, :null => true, :default => nil + person_klass.reset_column_information + assert_nil person_klass.columns_hash["money"].default + assert_equal true, person_klass.columns_hash["money"].null + + # change_column_null, make it not nullable and set null values to a default value + person_klass.connection.execute('UPDATE testings SET money = NULL') + person_klass.connection.change_column_null "testings", "money", false, 2000 + person_klass.reset_column_information + assert_nil person_klass.columns_hash["money"].default + assert_equal false, person_klass.columns_hash["money"].null + assert_equal 2000, connection.select_values("SELECT money FROM testings").first.to_i + end + + def test_column_exists + connection.create_table :testings do |t| + t.column :foo, :string + end + + assert connection.column_exists?(:testings, :foo) + refute connection.column_exists?(:testings, :bar) + end + + def test_column_exists_with_type + connection.create_table :testings do |t| + t.column :foo, :string + t.column :bar, :decimal, :precision => 8, :scale => 2 + end + + assert connection.column_exists?(:testings, :foo, :string) + refute connection.column_exists?(:testings, :foo, :integer) + + assert connection.column_exists?(:testings, :bar, :decimal) + refute connection.column_exists?(:testings, :bar, :integer) + end + + def test_column_exists_with_definition + connection.create_table :testings do |t| + t.column :foo, :string, :limit => 100 + t.column :bar, :decimal, :precision => 8, :scale => 2 + end + + assert connection.column_exists?(:testings, :foo, :string, :limit => 100) + refute connection.column_exists?(:testings, :foo, :string, :limit => 50) + assert connection.column_exists?(:testings, :bar, :decimal, :precision => 8, :scale => 2) + refute connection.column_exists?(:testings, :bar, :decimal, :precision => 10, :scale => 2) + end + + def test_column_exists_on_table_with_no_options_parameter_supplied + connection.create_table :testings do |t| + t.string :foo + end + connection.change_table :testings do |t| + assert t.column_exists?(:foo) + assert !(t.column_exists?(:bar)) + end + end + + private + def testing_table_with_only_foo_attribute + connection.create_table :testings, :id => false do |t| + t.column :foo, :string + end + + yield + end + end + end +end diff --git a/activerecord/test/cases/migration/column_attributes_test.rb b/activerecord/test/cases/migration/column_attributes_test.rb new file mode 100644 index 0000000000..409a558f5c --- /dev/null +++ b/activerecord/test/cases/migration/column_attributes_test.rb @@ -0,0 +1,188 @@ +require "cases/migration/helper" + +module ActiveRecord + class Migration + class ColumnAttributesTest < ActiveRecord::TestCase + include ActiveRecord::Migration::TestHelper + + self.use_transactional_fixtures = false + + def test_add_remove_single_field_using_string_arguments + refute TestModel.column_methods_hash.key?(:last_name) + + add_column 'test_models', 'last_name', :string + + TestModel.reset_column_information + + assert TestModel.column_methods_hash.key?(:last_name) + + remove_column 'test_models', 'last_name' + + TestModel.reset_column_information + refute TestModel.column_methods_hash.key?(:last_name) + end + + def test_add_remove_single_field_using_symbol_arguments + refute TestModel.column_methods_hash.key?(:last_name) + + add_column :test_models, :last_name, :string + + TestModel.reset_column_information + assert TestModel.column_methods_hash.key?(:last_name) + + remove_column :test_models, :last_name + + TestModel.reset_column_information + refute TestModel.column_methods_hash.key?(:last_name) + end + + def test_unabstracted_database_dependent_types + skip "not supported" unless current_adapter?(:MysqlAdapter, :Mysql2Adapter) + + add_column :test_models, :intelligence_quotient, :tinyint + TestModel.reset_column_information + assert_match(/tinyint/, TestModel.columns_hash['intelligence_quotient'].sql_type) + end + + # We specifically do a manual INSERT here, and then test only the SELECT + # functionality. This allows us to more easily catch INSERT being broken, + # but SELECT actually working fine. + def test_native_decimal_insert_manual_vs_automatic + correct_value = '0012345678901234567890.0123456789'.to_d + + connection.add_column "test_models", "wealth", :decimal, :precision => '30', :scale => '10' + + # Do a manual insertion + if current_adapter?(:OracleAdapter) + connection.execute "insert into test_models (id, wealth, created_at, updated_at) values (people_seq.nextval, 12345678901234567890.0123456789, sysdate, sysdate)" + elsif current_adapter?(:OpenBaseAdapter) || (current_adapter?(:MysqlAdapter) && Mysql.client_version < 50003) #before mysql 5.0.3 decimals stored as strings + connection.execute "insert into test_models (wealth, created_at, updated_at) values ('12345678901234567890.0123456789', 0, 0)" + elsif current_adapter?(:PostgreSQLAdapter) + connection.execute "insert into test_models (wealth, created_at, updated_at) values (12345678901234567890.0123456789, now(), now())" + else + connection.execute "insert into test_models (wealth, created_at, updated_at) values (12345678901234567890.0123456789, 0, 0)" + end + + # SELECT + row = TestModel.first + assert_kind_of BigDecimal, row.wealth + + # If this assert fails, that means the SELECT is broken! + unless current_adapter?(:SQLite3Adapter) + assert_equal correct_value, row.wealth + end + + # Reset to old state + TestModel.delete_all + + # Now use the Rails insertion + TestModel.create :wealth => BigDecimal.new("12345678901234567890.0123456789") + + # SELECT + row = TestModel.first + assert_kind_of BigDecimal, row.wealth + + # If these asserts fail, that means the INSERT (create function, or cast to SQL) is broken! + unless current_adapter?(:SQLite3Adapter) + assert_equal correct_value, row.wealth + end + end + + def test_add_column_with_precision_and_scale + connection.add_column 'test_models', 'wealth', :decimal, :precision => 9, :scale => 7 + + wealth_column = TestModel.columns_hash['wealth'] + assert_equal 9, wealth_column.precision + assert_equal 7, wealth_column.scale + end + + def test_change_column_preserve_other_column_precision_and_scale + skip "only on sqlite3" unless current_adapter?(:SQLite3Adapter) + + connection.add_column 'test_models', 'last_name', :string + connection.add_column 'test_models', 'wealth', :decimal, :precision => 9, :scale => 7 + + wealth_column = TestModel.columns_hash['wealth'] + assert_equal 9, wealth_column.precision + assert_equal 7, wealth_column.scale + + connection.change_column 'test_models', 'last_name', :string, :null => false + TestModel.reset_column_information + + wealth_column = TestModel.columns_hash['wealth'] + assert_equal 9, wealth_column.precision + assert_equal 7, wealth_column.scale + end + + def test_native_types + add_column "test_models", "first_name", :string + add_column "test_models", "last_name", :string + add_column "test_models", "bio", :text + add_column "test_models", "age", :integer + add_column "test_models", "height", :float + add_column "test_models", "wealth", :decimal, :precision => '30', :scale => '10' + add_column "test_models", "birthday", :datetime + add_column "test_models", "favorite_day", :date + add_column "test_models", "moment_of_truth", :datetime + add_column "test_models", "male", :boolean + + TestModel.create :first_name => 'bob', :last_name => 'bobsen', + :bio => "I was born ....", :age => 18, :height => 1.78, + :wealth => BigDecimal.new("12345678901234567890.0123456789"), + :birthday => 18.years.ago, :favorite_day => 10.days.ago, + :moment_of_truth => "1782-10-10 21:40:18", :male => true + + bob = TestModel.first + assert_equal 'bob', bob.first_name + assert_equal 'bobsen', bob.last_name + assert_equal "I was born ....", bob.bio + assert_equal 18, bob.age + + # Test for 30 significant digits (beyond the 16 of float), 10 of them + # after the decimal place. + + unless current_adapter?(:SQLite3Adapter) + assert_equal BigDecimal.new("0012345678901234567890.0123456789"), bob.wealth + end + + assert_equal true, bob.male? + + assert_equal String, bob.first_name.class + assert_equal String, bob.last_name.class + assert_equal String, bob.bio.class + assert_equal Fixnum, bob.age.class + assert_equal Time, bob.birthday.class + + if current_adapter?(:OracleAdapter, :SybaseAdapter) + # Sybase, and Oracle don't differentiate between date/time + assert_equal Time, bob.favorite_day.class + else + assert_equal Date, bob.favorite_day.class + end + + # Oracle adapter stores Time or DateTime with timezone value already in _before_type_cast column + # therefore no timezone change is done afterwards when default timezone is changed + unless current_adapter?(:OracleAdapter) + # Test DateTime column and defaults, including timezone. + # FIXME: moment of truth may be Time on 64-bit platforms. + if bob.moment_of_truth.is_a?(DateTime) + + with_env_tz 'US/Eastern' do + bob.reload + assert_equal DateTime.local_offset, bob.moment_of_truth.offset + assert_not_equal 0, bob.moment_of_truth.offset + assert_not_equal "Z", bob.moment_of_truth.zone + # US/Eastern is -5 hours from GMT + assert_equal Rational(-5, 24), bob.moment_of_truth.offset + assert_match(/\A-05:?00\Z/, bob.moment_of_truth.zone) #ruby 1.8.6 uses HH:MM, prior versions use HHMM + assert_equal DateTime::ITALY, bob.moment_of_truth.start + end + end + end + + assert_instance_of TrueClass, bob.male? + assert_kind_of BigDecimal, bob.wealth + end + end + end +end diff --git a/activerecord/test/cases/migration/column_positioning_test.rb b/activerecord/test/cases/migration/column_positioning_test.rb new file mode 100644 index 0000000000..913d935f7c --- /dev/null +++ b/activerecord/test/cases/migration/column_positioning_test.rb @@ -0,0 +1,60 @@ +require 'cases/helper' + +module ActiveRecord + class Migration + class ColumnPositioningTest < ActiveRecord::TestCase + attr_reader :connection, :table_name + alias :conn :connection + + def setup + super + + unless current_adapter?(:MysqlAdapter, :Mysql2Adapter) + skip "not supported on #{connection.class}" + end + + @connection = ActiveRecord::Base.connection + + connection.create_table :testings, :id => false do |t| + t.column :first, :integer + t.column :second, :integer + t.column :third, :integer + end + end + + def teardown + super + connection.drop_table :testings rescue nil + ActiveRecord::Base.primary_key_prefix_type = nil + end + + def test_column_positioning + assert_equal %w(first second third), conn.columns(:testings).map {|c| c.name } + end + + def test_add_column_with_positioning + conn.add_column :testings, :new_col, :integer + assert_equal %w(first second third new_col), conn.columns(:testings).map {|c| c.name } + end + + def test_add_column_with_positioning_first + conn.add_column :testings, :new_col, :integer, :first => true + assert_equal %w(new_col first second third), conn.columns(:testings).map {|c| c.name } + end + + def test_add_column_with_positioning_after + conn.add_column :testings, :new_col, :integer, :after => :first + assert_equal %w(first new_col second third), conn.columns(:testings).map {|c| c.name } + end + + def test_change_column_with_positioning + conn.change_column :testings, :second, :integer, :first => true + assert_equal %w(second first third), conn.columns(:testings).map {|c| c.name } + + conn.change_column :testings, :second, :integer, :after => :third + assert_equal %w(first third second), conn.columns(:testings).map {|c| c.name } + end + + end + end +end diff --git a/activerecord/test/cases/migration/command_recorder_test.rb b/activerecord/test/cases/migration/command_recorder_test.rb index d108b456f0..7d026961be 100644 --- a/activerecord/test/cases/migration/command_recorder_test.rb +++ b/activerecord/test/cases/migration/command_recorder_test.rb @@ -67,6 +67,24 @@ module ActiveRecord assert_equal [:drop_table, [:system_settings]], drop_table end + def test_invert_create_table_with_options + @recorder.record :create_table, [:people_reminders, {:id => false}] + drop_table = @recorder.inverse.first + assert_equal [:drop_table, [:people_reminders]], drop_table + end + + def test_invert_create_join_table + @recorder.record :create_join_table, [:musics, :artists] + drop_table = @recorder.inverse.first + assert_equal [:drop_table, [:artists_musics]], drop_table + end + + def test_invert_create_join_table_with_table_name + @recorder.record :create_join_table, [:musics, :artists, {:table_name => :catalog}] + drop_table = @recorder.inverse.first + assert_equal [:drop_table, [:catalog]], drop_table + end + def test_invert_rename_table @recorder.record :rename_table, [:old, :new] rename = @recorder.inverse.first diff --git a/activerecord/test/cases/migration/create_join_table_test.rb b/activerecord/test/cases/migration/create_join_table_test.rb new file mode 100644 index 0000000000..0428d9ba76 --- /dev/null +++ b/activerecord/test/cases/migration/create_join_table_test.rb @@ -0,0 +1,70 @@ +require 'cases/helper' + +module ActiveRecord + class Migration + class CreateJoinTableTest < ActiveRecord::TestCase + attr_reader :connection + + def setup + super + @connection = ActiveRecord::Base.connection + end + + def test_create_join_table + connection.create_join_table :artists, :musics + + assert_equal %w(artist_id music_id), connection.columns(:artists_musics).map(&:name).sort + ensure + connection.drop_table :artists_musics + end + + def test_create_join_table_set_not_null_by_default + connection.create_join_table :artists, :musics + + assert_equal [false, false], connection.columns(:artists_musics).map(&:null) + ensure + connection.drop_table :artists_musics + end + + def test_create_join_table_with_strings + connection.create_join_table 'artists', 'musics' + + assert_equal %w(artist_id music_id), connection.columns(:artists_musics).map(&:name).sort + ensure + connection.drop_table :artists_musics + end + + def test_create_join_table_with_the_proper_order + connection.create_join_table :videos, :musics + + assert_equal %w(music_id video_id), connection.columns(:musics_videos).map(&:name).sort + ensure + connection.drop_table :musics_videos + end + + def test_create_join_table_with_the_table_name + connection.create_join_table :artists, :musics, :table_name => :catalog + + assert_equal %w(artist_id music_id), connection.columns(:catalog).map(&:name).sort + ensure + connection.drop_table :catalog + end + + def test_create_join_table_with_the_table_name_as_string + connection.create_join_table :artists, :musics, :table_name => 'catalog' + + assert_equal %w(artist_id music_id), connection.columns(:catalog).map(&:name).sort + ensure + connection.drop_table :catalog + end + + def test_create_join_table_with_column_options + connection.create_join_table :artists, :musics, :column_options => {:null => true} + + assert_equal [true, true], connection.columns(:artists_musics).map(&:null) + ensure + connection.drop_table :artists_musics + end + end + end +end diff --git a/activerecord/test/cases/migration/helper.rb b/activerecord/test/cases/migration/helper.rb new file mode 100644 index 0000000000..fe53510ba2 --- /dev/null +++ b/activerecord/test/cases/migration/helper.rb @@ -0,0 +1,64 @@ +require "cases/helper" + +module ActiveRecord + class Migration + class << self + attr_accessor :message_count + end + + def puts(text="") + ActiveRecord::Migration.message_count ||= 0 + ActiveRecord::Migration.message_count += 1 + end + + module TestHelper + attr_reader :connection, :table_name + + class TestModel < ActiveRecord::Base + self.table_name = 'test_models' + end + + def setup + super + @connection = ActiveRecord::Base.connection + connection.create_table :test_models do |t| + t.timestamps + end + + TestModel.reset_column_information + end + + def teardown + super + TestModel.reset_table_name + TestModel.reset_sequence_name + connection.drop_table :test_models rescue nil + end + + private + def add_column(*args) + connection.add_column(*args) + end + + def remove_column(*args) + connection.remove_column(*args) + end + + def rename_column(*args) + connection.rename_column(*args) + end + + def add_index(*args) + connection.add_index(*args) + end + + def change_column(*args) + connection.change_column(*args) + end + + def rename_table(*args) + connection.rename_table(*args) + end + end + end +end diff --git a/activerecord/test/cases/migration/index_test.rb b/activerecord/test/cases/migration/index_test.rb new file mode 100644 index 0000000000..dd9492924c --- /dev/null +++ b/activerecord/test/cases/migration/index_test.rb @@ -0,0 +1,185 @@ +require 'cases/helper' + +module ActiveRecord + class Migration + class IndexTest < ActiveRecord::TestCase + attr_reader :connection, :table_name + + def setup + super + @connection = ActiveRecord::Base.connection + @table_name = :testings + + connection.create_table table_name do |t| + t.column :foo, :string, :limit => 100 + t.column :bar, :string, :limit => 100 + + t.string :first_name + t.string :last_name, :limit => 100 + t.string :key, :limit => 100 + t.boolean :administrator + end + end + + def teardown + super + connection.drop_table :testings rescue nil + ActiveRecord::Base.primary_key_prefix_type = nil + end + + def test_rename_index + skip "not supported on openbase" if current_adapter?(:OpenBaseAdapter) + + # keep the names short to make Oracle and similar behave + connection.add_index(table_name, [:foo], :name => 'old_idx') + connection.rename_index(table_name, 'old_idx', 'new_idx') + + # if the adapter doesn't support the indexes call, pick defaults that let the test pass + refute connection.index_name_exists?(table_name, 'old_idx', false) + assert connection.index_name_exists?(table_name, 'new_idx', true) + end + + def test_double_add_index + skip "not supported on openbase" if current_adapter?(:OpenBaseAdapter) + + connection.add_index(table_name, [:foo], :name => 'some_idx') + assert_raises(ArgumentError) { + connection.add_index(table_name, [:foo], :name => 'some_idx') + } + end + + def test_remove_nonexistent_index + skip "not supported on openbase" if current_adapter?(:OpenBaseAdapter) + + # we do this by name, so OpenBase is a wash as noted above + assert_raise(ArgumentError) { connection.remove_index(table_name, "no_such_index") } + end + + def test_add_index_name_length_limit + good_index_name = 'x' * connection.index_name_length + too_long_index_name = good_index_name + 'x' + + assert_raises(ArgumentError) { + connection.add_index(table_name, "foo", :name => too_long_index_name) + } + + refute connection.index_name_exists?(table_name, too_long_index_name, false) + connection.add_index(table_name, "foo", :name => good_index_name) + + assert connection.index_name_exists?(table_name, good_index_name, false) + connection.remove_index(table_name, :name => good_index_name) + end + + def test_index_symbol_names + connection.add_index table_name, :foo, :name => :symbol_index_name + assert connection.index_exists?(table_name, :foo, :name => :symbol_index_name) + + connection.remove_index table_name, :name => :symbol_index_name + refute connection.index_exists?(table_name, :foo, :name => :symbol_index_name) + end + + def test_index_exists + connection.add_index :testings, :foo + + assert connection.index_exists?(:testings, :foo) + assert !connection.index_exists?(:testings, :bar) + end + + def test_index_exists_on_multiple_columns + connection.add_index :testings, [:foo, :bar] + + assert connection.index_exists?(:testings, [:foo, :bar]) + end + + def test_unique_index_exists + connection.add_index :testings, :foo, :unique => true + + assert connection.index_exists?(:testings, :foo, :unique => true) + end + + def test_named_index_exists + connection.add_index :testings, :foo, :name => "custom_index_name" + + assert connection.index_exists?(:testings, :foo, :name => "custom_index_name") + end + + def test_add_index_attribute_length_limit + connection.add_index :testings, [:foo, :bar], :length => {:foo => 10, :bar => nil} + + assert connection.index_exists?(:testings, [:foo, :bar]) + end + + def test_add_index + connection.add_index("testings", "last_name") + connection.remove_index("testings", "last_name") + + # Orcl nds shrt indx nms. Sybs 2. + # OpenBase does not have named indexes. You must specify a single column name + unless current_adapter?(:SybaseAdapter, :OpenBaseAdapter) + connection.add_index("testings", ["last_name", "first_name"]) + connection.remove_index("testings", :column => ["last_name", "first_name"]) + + # Oracle adapter cannot have specified index name larger than 30 characters + # Oracle adapter is shortening index name when just column list is given + unless current_adapter?(:OracleAdapter) + connection.add_index("testings", ["last_name", "first_name"]) + connection.remove_index("testings", :name => :index_testings_on_last_name_and_first_name) + connection.add_index("testings", ["last_name", "first_name"]) + connection.remove_index("testings", "last_name_and_first_name") + end + connection.add_index("testings", ["last_name", "first_name"]) + connection.remove_index("testings", ["last_name", "first_name"]) + + connection.add_index("testings", ["last_name"], :length => 10) + connection.remove_index("testings", "last_name") + + connection.add_index("testings", ["last_name"], :length => {:last_name => 10}) + connection.remove_index("testings", ["last_name"]) + + connection.add_index("testings", ["last_name", "first_name"], :length => 10) + connection.remove_index("testings", ["last_name", "first_name"]) + + connection.add_index("testings", ["last_name", "first_name"], :length => {:last_name => 10, :first_name => 20}) + connection.remove_index("testings", ["last_name", "first_name"]) + end + + # quoting + # Note: changed index name from "key" to "key_idx" since "key" is a Firebird reserved word + # OpenBase does not have named indexes. You must specify a single column name + unless current_adapter?(:OpenBaseAdapter) + connection.add_index("testings", ["key"], :name => "key_idx", :unique => true) + connection.remove_index("testings", :name => "key_idx", :unique => true) + end + + # Sybase adapter does not support indexes on :boolean columns + # OpenBase does not have named indexes. You must specify a single column + unless current_adapter?(:SybaseAdapter, :OpenBaseAdapter) + connection.add_index("testings", %w(last_name first_name administrator), :name => "named_admin") + connection.remove_index("testings", :name => "named_admin") + end + + # Selected adapters support index sort order + if current_adapter?(:SQLite3Adapter, :MysqlAdapter, :Mysql2Adapter, :PostgreSQLAdapter) + connection.add_index("testings", ["last_name"], :order => {:last_name => :desc}) + connection.remove_index("testings", ["last_name"]) + connection.add_index("testings", ["last_name", "first_name"], :order => {:last_name => :desc}) + connection.remove_index("testings", ["last_name", "first_name"]) + connection.add_index("testings", ["last_name", "first_name"], :order => {:last_name => :desc, :first_name => :asc}) + connection.remove_index("testings", ["last_name", "first_name"]) + connection.add_index("testings", ["last_name", "first_name"], :order => :desc) + connection.remove_index("testings", ["last_name", "first_name"]) + end + end + + def test_add_partial_index + skip 'only on pg' unless current_adapter?(:PostgreSQLAdapter) + + connection.add_index("testings", "last_name", :where => "first_name = 'john doe'") + assert connection.index_exists?("testings", "last_name") + + connection.remove_index("testings", "last_name") + assert !connection.index_exists?("testings", "last_name") + end + end + end +end diff --git a/activerecord/test/cases/migration/logger_test.rb b/activerecord/test/cases/migration/logger_test.rb new file mode 100644 index 0000000000..ee0c20747e --- /dev/null +++ b/activerecord/test/cases/migration/logger_test.rb @@ -0,0 +1,37 @@ +require "cases/helper" + +module ActiveRecord + class Migration + class LoggerTest < ActiveRecord::TestCase + # mysql can't roll back ddl changes + self.use_transactional_fixtures = false + + Migration = Struct.new(:name, :version) do + def migrate direction + # do nothing + end + end + + def setup + super + ActiveRecord::SchemaMigration.create_table + ActiveRecord::SchemaMigration.delete_all + end + + def teardown + super + ActiveRecord::SchemaMigration.drop_table + end + + def test_migration_should_be_run_without_logger + previous_logger = ActiveRecord::Base.logger + ActiveRecord::Base.logger = nil + migrations = [Migration.new('a', 1), Migration.new('b', 2), Migration.new('c', 3)] + ActiveRecord::Migrator.new(:up, migrations).migrate + ensure + ActiveRecord::Base.logger = previous_logger + end + end + end +end + diff --git a/activerecord/test/cases/migration/references_index_test.rb b/activerecord/test/cases/migration/references_index_test.rb new file mode 100644 index 0000000000..8ab1c59724 --- /dev/null +++ b/activerecord/test/cases/migration/references_index_test.rb @@ -0,0 +1,99 @@ +require 'cases/helper' + +module ActiveRecord + class Migration + class ReferencesIndexTest < ActiveRecord::TestCase + attr_reader :connection, :table_name + + def setup + super + @connection = ActiveRecord::Base.connection + @table_name = :testings + end + + def teardown + super + connection.drop_table :testings rescue nil + end + + def test_creates_index + connection.create_table table_name do |t| + t.references :foo, :index => true + end + + assert connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_foo_id) + end + + def test_does_not_create_index + connection.create_table table_name do |t| + t.references :foo + end + + refute connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_foo_id) + end + + def test_does_not_create_index_explicit + connection.create_table table_name do |t| + t.references :foo, :index => false + end + + refute connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_foo_id) + end + + def test_creates_index_with_options + connection.create_table table_name do |t| + t.references :foo, :index => {:name => :index_testings_on_yo_momma} + t.references :bar, :index => {:unique => true} + end + + assert connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_yo_momma) + assert connection.index_exists?(table_name, :bar_id, :name => :index_testings_on_bar_id, :unique => true) + end + + def test_creates_polymorphic_index + connection.create_table table_name do |t| + t.references :foo, :polymorphic => true, :index => true + end + + assert connection.index_exists?(table_name, [:foo_id, :foo_type], :name => :index_testings_on_foo_id_and_foo_type) + end + + def test_creates_index_for_existing_table + connection.create_table table_name + connection.change_table table_name do |t| + t.references :foo, :index => true + end + + assert connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_foo_id) + end + + def test_does_not_create_index_for_existing_table + connection.create_table table_name + connection.change_table table_name do |t| + t.references :foo + end + + refute connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_foo_id) + end + + def test_does_not_create_index_for_existing_table_explicit + connection.create_table table_name + connection.change_table table_name do |t| + t.references :foo, :index => false + end + + refute connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_foo_id) + end + + def test_creates_polymorphic_index_for_existing_table + connection.create_table table_name + connection.change_table table_name do |t| + t.references :foo, :polymorphic => true, :index => true + end + + assert connection.index_exists?(table_name, [:foo_id, :foo_type], :name => :index_testings_on_foo_id_and_foo_type) + end + + end + end +end diff --git a/activerecord/test/cases/migration/rename_column_test.rb b/activerecord/test/cases/migration/rename_column_test.rb new file mode 100644 index 0000000000..d1a85ee5e4 --- /dev/null +++ b/activerecord/test/cases/migration/rename_column_test.rb @@ -0,0 +1,194 @@ +require "cases/migration/helper" + +module ActiveRecord + class Migration + class RenameColumnTest < ActiveRecord::TestCase + include ActiveRecord::Migration::TestHelper + + self.use_transactional_fixtures = false + + # FIXME: this is more of an integration test with AR::Base and the + # schema modifications. Maybe we should move this? + def test_add_rename + add_column "test_models", "girlfriend", :string + TestModel.reset_column_information + + TestModel.create :girlfriend => 'bobette' + + rename_column "test_models", "girlfriend", "exgirlfriend" + + TestModel.reset_column_information + bob = TestModel.first + + assert_equal "bobette", bob.exgirlfriend + end + + # FIXME: another integration test. We should decouple this from the + # AR::Base implementation. + def test_rename_column_using_symbol_arguments + add_column :test_models, :first_name, :string + + TestModel.create :first_name => 'foo' + + rename_column :test_models, :first_name, :nick_name + TestModel.reset_column_information + assert TestModel.column_names.include?("nick_name") + assert_equal ['foo'], TestModel.all.map(&:nick_name) + end + + # FIXME: another integration test. We should decouple this from the + # AR::Base implementation. + def test_rename_column + add_column "test_models", "first_name", "string" + + TestModel.create :first_name => 'foo' + + rename_column "test_models", "first_name", "nick_name" + TestModel.reset_column_information + assert TestModel.column_names.include?("nick_name") + assert_equal ['foo'], TestModel.all.map(&:nick_name) + end + + def test_rename_column_preserves_default_value_not_null + add_column 'test_models', 'salary', :integer, :default => 70000 + + default_before = connection.columns("test_models").find { |c| c.name == "salary" }.default + assert_equal 70000, default_before + + rename_column "test_models", "salary", "anual_salary" + + assert TestModel.column_names.include?("anual_salary") + default_after = connection.columns("test_models").find { |c| c.name == "anual_salary" }.default + assert_equal 70000, default_after + end + + def test_rename_nonexistent_column + exception = if current_adapter?(:PostgreSQLAdapter, :OracleAdapter) + ActiveRecord::StatementInvalid + else + ActiveRecord::ActiveRecordError + end + assert_raise(exception) do + rename_column "test_models", "nonexistent", "should_fail" + end + end + + def test_rename_column_with_sql_reserved_word + add_column 'test_models', 'first_name', :string + rename_column "test_models", "first_name", "group" + + assert TestModel.column_names.include?("group") + end + + def test_rename_column_with_an_index + add_column "test_models", :hat_name, :string + add_index :test_models, :hat_name + + # FIXME: we should test that the index goes away + rename_column "test_models", "hat_name", "name" + end + + def test_remove_column_with_index + add_column "test_models", :hat_name, :string + add_index :test_models, :hat_name + + # FIXME: we should test that the index goes away + remove_column("test_models", "hat_name") + end + + def test_remove_column_with_multi_column_index + add_column "test_models", :hat_size, :integer + add_column "test_models", :hat_style, :string, :limit => 100 + add_index "test_models", ["hat_style", "hat_size"], :unique => true + + # FIXME: we should test that the index goes away + remove_column("test_models", "hat_size") + end + + # FIXME: we need to test that these calls do something + def test_change_type_of_not_null_column + change_column "test_models", "updated_at", :datetime, :null => false + change_column "test_models", "updated_at", :datetime, :null => false + change_column "test_models", "updated_at", :datetime, :null => true + end + + def test_change_column_nullability + add_column "test_models", "funny", :boolean + assert TestModel.columns_hash["funny"].null, "Column 'funny' must initially allow nulls" + + change_column "test_models", "funny", :boolean, :null => false, :default => true + + TestModel.reset_column_information + refute TestModel.columns_hash["funny"].null, "Column 'funny' must *not* allow nulls at this point" + + change_column "test_models", "funny", :boolean, :null => true + TestModel.reset_column_information + assert TestModel.columns_hash["funny"].null, "Column 'funny' must allow nulls again at this point" + end + + def test_change_column + add_column 'test_models', 'age', :integer + add_column 'test_models', 'approved', :boolean, :default => true + + old_columns = connection.columns(TestModel.table_name) + + assert old_columns.find { |c| c.name == 'age' && c.type == :integer } + + change_column "test_models", "age", :string + + new_columns = connection.columns(TestModel.table_name) + + refute new_columns.find { |c| c.name == 'age' and c.type == :integer } + assert new_columns.find { |c| c.name == 'age' and c.type == :string } + + old_columns = connection.columns(TestModel.table_name) + assert old_columns.find { |c| + c.name == 'approved' && c.type == :boolean && c.default == true + } + + change_column :test_models, :approved, :boolean, :default => false + new_columns = connection.columns(TestModel.table_name) + + refute new_columns.find { |c| c.name == 'approved' and c.type == :boolean and c.default == true } + assert new_columns.find { |c| c.name == 'approved' and c.type == :boolean and c.default == false } + change_column :test_models, :approved, :boolean, :default => true + end + + def test_change_column_with_nil_default + add_column "test_models", "contributor", :boolean, :default => true + assert TestModel.new.contributor? + + change_column "test_models", "contributor", :boolean, :default => nil + TestModel.reset_column_information + refute TestModel.new.contributor? + assert_nil TestModel.new.contributor + end + + def test_change_column_with_new_default + add_column "test_models", "administrator", :boolean, :default => true + assert TestModel.new.administrator? + + change_column "test_models", "administrator", :boolean, :default => false + TestModel.reset_column_information + refute TestModel.new.administrator? + end + + def test_change_column_default + add_column "test_models", "first_name", :string + connection.change_column_default "test_models", "first_name", "Tester" + + assert_equal "Tester", TestModel.new.first_name + end + + def test_change_column_default_to_null + add_column "test_models", "first_name", :string + connection.change_column_default "test_models", "first_name", nil + assert_nil TestModel.new.first_name + end + + def test_remove_column_no_second_parameter_raises_exception + assert_raise(ArgumentError) { connection.remove_column("funny") } + end + end + end +end diff --git a/activerecord/test/cases/migration/rename_table_test.rb b/activerecord/test/cases/migration/rename_table_test.rb new file mode 100644 index 0000000000..d5ff2c607f --- /dev/null +++ b/activerecord/test/cases/migration/rename_table_test.rb @@ -0,0 +1,72 @@ +require "cases/migration/helper" + +module ActiveRecord + class Migration + class RenameTableTest < ActiveRecord::TestCase + include ActiveRecord::Migration::TestHelper + + self.use_transactional_fixtures = false + + def setup + super + add_column 'test_models', :url, :string + remove_column 'test_models', :created_at + remove_column 'test_models', :updated_at + end + + def test_rename_table_for_sqlite_should_work_with_reserved_words + renamed = false + + skip "not supported" unless current_adapter?(:SQLite3Adapter) + + add_column :test_models, :url, :string + connection.rename_table :references, :old_references + connection.rename_table :test_models, :references + + renamed = true + + # Using explicit id in insert for compatibility across all databases + con = connection + con.execute "INSERT INTO 'references' (url, created_at, updated_at) VALUES ('http://rubyonrails.com', 0, 0)" + assert_equal 'http://rubyonrails.com', connection.select_value("SELECT url FROM 'references' WHERE id=1") + ensure + return unless renamed + connection.rename_table :references, :test_models + connection.rename_table :old_references, :references + end + + def test_rename_table + rename_table :test_models, :octopi + + # Using explicit id in insert for compatibility across all databases + con = connection + con.enable_identity_insert("octopi", true) if current_adapter?(:SybaseAdapter) + + con.execute "INSERT INTO octopi (#{con.quote_column_name('id')}, #{con.quote_column_name('url')}) VALUES (1, 'http://www.foreverflying.com/octopus-black7.jpg')" + + con.enable_identity_insert("octopi", false) if current_adapter?(:SybaseAdapter) + + assert_equal 'http://www.foreverflying.com/octopus-black7.jpg', connection.select_value("SELECT url FROM octopi WHERE id=1") + + rename_table :octopi, :test_models + end + + def test_rename_table_with_an_index + add_index :test_models, :url + + rename_table :test_models, :octopi + + # Using explicit id in insert for compatibility across all databases + con = ActiveRecord::Base.connection + con.enable_identity_insert("octopi", true) if current_adapter?(:SybaseAdapter) + con.execute "INSERT INTO octopi (#{con.quote_column_name('id')}, #{con.quote_column_name('url')}) VALUES (1, 'http://www.foreverflying.com/octopus-black7.jpg')" + con.enable_identity_insert("octopi", false) if current_adapter?(:SybaseAdapter) + + assert_equal 'http://www.foreverflying.com/octopus-black7.jpg', connection.select_value("SELECT url FROM octopi WHERE id=1") + assert connection.indexes(:octopi).first.columns.include?("url") + + rename_table :octopi, :test_models + end + end + end +end diff --git a/activerecord/test/cases/migration/table_and_index_test.rb b/activerecord/test/cases/migration/table_and_index_test.rb new file mode 100644 index 0000000000..8fd770abd1 --- /dev/null +++ b/activerecord/test/cases/migration/table_and_index_test.rb @@ -0,0 +1,24 @@ +require "cases/helper" + +module ActiveRecord + class Migration + class TableAndIndexTest < ActiveRecord::TestCase + def test_add_schema_info_respects_prefix_and_suffix + conn = ActiveRecord::Base.connection + + conn.drop_table(ActiveRecord::Migrator.schema_migrations_table_name) if conn.table_exists?(ActiveRecord::Migrator.schema_migrations_table_name) + # Use shorter prefix and suffix as in Oracle database identifier cannot be larger than 30 characters + ActiveRecord::Base.table_name_prefix = 'p_' + ActiveRecord::Base.table_name_suffix = '_s' + conn.drop_table(ActiveRecord::Migrator.schema_migrations_table_name) if conn.table_exists?(ActiveRecord::Migrator.schema_migrations_table_name) + + conn.initialize_schema_migrations_table + + assert_equal "p_unique_schema_migrations_s", conn.indexes(ActiveRecord::Migrator.schema_migrations_table_name)[0][:name] + ensure + ActiveRecord::Base.table_name_prefix = "" + ActiveRecord::Base.table_name_suffix = "" + end + end + end +end diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index 3e219f2a49..f788690b0d 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -1,4 +1,5 @@ require "cases/helper" +require "cases/migration/helper" require 'bigdecimal/util' require 'models/person' @@ -6,2209 +7,942 @@ require 'models/topic' require 'models/developer' require MIGRATIONS_ROOT + "/valid/2_we_need_reminders" +require MIGRATIONS_ROOT + "/rename/1_we_need_things" +require MIGRATIONS_ROOT + "/rename/2_rename_things" require MIGRATIONS_ROOT + "/decimal/1_give_me_big_numbers" -if ActiveRecord::Base.connection.supports_migrations? - class BigNumber < ActiveRecord::Base; end +class BigNumber < ActiveRecord::Base; end - class Reminder < ActiveRecord::Base; end +class Reminder < ActiveRecord::Base; end - class ActiveRecord::Migration - class << self - attr_accessor :message_count - end - - def puts(text="") - ActiveRecord::Migration.message_count ||= 0 - ActiveRecord::Migration.message_count += 1 - end - end - - class MigrationTableAndIndexTest < ActiveRecord::TestCase - def test_add_schema_info_respects_prefix_and_suffix - conn = ActiveRecord::Base.connection +class Thing < ActiveRecord::Base; end - conn.drop_table(ActiveRecord::Migrator.schema_migrations_table_name) if conn.table_exists?(ActiveRecord::Migrator.schema_migrations_table_name) - # Use shorter prefix and suffix as in Oracle database identifier cannot be larger than 30 characters - ActiveRecord::Base.table_name_prefix = 'p_' - ActiveRecord::Base.table_name_suffix = '_s' - conn.drop_table(ActiveRecord::Migrator.schema_migrations_table_name) if conn.table_exists?(ActiveRecord::Migrator.schema_migrations_table_name) +class MigrationTest < ActiveRecord::TestCase + self.use_transactional_fixtures = false - conn.initialize_schema_migrations_table + fixtures :people - assert_equal "p_unique_schema_migrations_s", conn.indexes(ActiveRecord::Migrator.schema_migrations_table_name)[0][:name] - ensure - ActiveRecord::Base.table_name_prefix = "" - ActiveRecord::Base.table_name_suffix = "" + def setup + super + %w(reminders people_reminders prefix_reminders_suffix).each do |table| + Reminder.connection.drop_table(table) rescue nil end + Reminder.reset_column_information + ActiveRecord::Migration.verbose = true + ActiveRecord::Migration.message_count = 0 end - class MigrationTest < ActiveRecord::TestCase - self.use_transactional_fixtures = false - - fixtures :people - - def setup - ActiveRecord::Migration.verbose = true - ActiveRecord::Migration.message_count = 0 - end - - def teardown - ActiveRecord::Base.connection.initialize_schema_migrations_table - ActiveRecord::Base.connection.execute "DELETE FROM #{ActiveRecord::Migrator.schema_migrations_table_name}" - - %w(reminders people_reminders prefix_reminders_suffix).each do |table| - Reminder.connection.drop_table(table) rescue nil - end - Reminder.reset_column_information - - %w(last_name key bio age height wealth birthday favorite_day - moment_of_truth male administrator funny).each do |column| - Person.connection.remove_column('people', column) rescue nil - end - Person.connection.remove_column("people", "first_name") rescue nil - Person.connection.remove_column("people", "middle_name") rescue nil - Person.connection.add_column("people", "first_name", :string, :limit => 40) - Person.reset_column_information - end - - def test_add_index - # Limit size of last_name and key columns to support Firebird index limitations - Person.connection.add_column "people", "last_name", :string, :limit => 100 - Person.connection.add_column "people", "key", :string, :limit => 100 - Person.connection.add_column "people", "administrator", :boolean - - assert_nothing_raised { Person.connection.add_index("people", "last_name") } - assert_nothing_raised { Person.connection.remove_index("people", "last_name") } - - # Orcl nds shrt indx nms. Sybs 2. - # OpenBase does not have named indexes. You must specify a single column name - unless current_adapter?(:SybaseAdapter, :OpenBaseAdapter) - assert_nothing_raised { Person.connection.add_index("people", ["last_name", "first_name"]) } - assert_nothing_raised { Person.connection.remove_index("people", :column => ["last_name", "first_name"]) } - # Oracle adapter cannot have specified index name larger than 30 characters - # Oracle adapter is shortening index name when just column list is given - unless current_adapter?(:OracleAdapter) - assert_nothing_raised { Person.connection.add_index("people", ["last_name", "first_name"]) } - assert_nothing_raised { Person.connection.remove_index("people", :name => :index_people_on_last_name_and_first_name) } - assert_nothing_raised { Person.connection.add_index("people", ["last_name", "first_name"]) } - assert_nothing_raised { Person.connection.remove_index("people", "last_name_and_first_name") } - end - assert_nothing_raised { Person.connection.add_index("people", ["last_name", "first_name"]) } - assert_nothing_raised { Person.connection.remove_index("people", ["last_name", "first_name"]) } - assert_nothing_raised { Person.connection.add_index("people", ["last_name"], :length => 10) } - assert_nothing_raised { Person.connection.remove_index("people", "last_name") } - assert_nothing_raised { Person.connection.add_index("people", ["last_name"], :length => {:last_name => 10}) } - assert_nothing_raised { Person.connection.remove_index("people", ["last_name"]) } - assert_nothing_raised { Person.connection.add_index("people", ["last_name", "first_name"], :length => 10) } - assert_nothing_raised { Person.connection.remove_index("people", ["last_name", "first_name"]) } - assert_nothing_raised { Person.connection.add_index("people", ["last_name", "first_name"], :length => {:last_name => 10, :first_name => 20}) } - assert_nothing_raised { Person.connection.remove_index("people", ["last_name", "first_name"]) } - end - - # quoting - # Note: changed index name from "key" to "key_idx" since "key" is a Firebird reserved word - # OpenBase does not have named indexes. You must specify a single column name - unless current_adapter?(:OpenBaseAdapter) - Person.update_all "#{Person.connection.quote_column_name 'key'}=#{Person.connection.quote_column_name 'id'}" #some databases (including sqlite2 won't add a unique index if existing data non unique) - assert_nothing_raised { Person.connection.add_index("people", ["key"], :name => "key_idx", :unique => true) } - assert_nothing_raised { Person.connection.remove_index("people", :name => "key_idx", :unique => true) } - end - - # Sybase adapter does not support indexes on :boolean columns - # OpenBase does not have named indexes. You must specify a single column - unless current_adapter?(:SybaseAdapter, :OpenBaseAdapter) - assert_nothing_raised { Person.connection.add_index("people", %w(last_name first_name administrator), :name => "named_admin") } - assert_nothing_raised { Person.connection.remove_index("people", :name => "named_admin") } - end - - # Selected adapters support index sort order - if current_adapter?(:SQLite3Adapter, :MysqlAdapter, :Mysql2Adapter, :PostgreSQLAdapter) - assert_nothing_raised { Person.connection.add_index("people", ["last_name"], :order => {:last_name => :desc}) } - assert_nothing_raised { Person.connection.remove_index("people", ["last_name"]) } - assert_nothing_raised { Person.connection.add_index("people", ["last_name", "first_name"], :order => {:last_name => :desc}) } - assert_nothing_raised { Person.connection.remove_index("people", ["last_name", "first_name"]) } - assert_nothing_raised { Person.connection.add_index("people", ["last_name", "first_name"], :order => {:last_name => :desc, :first_name => :asc}) } - assert_nothing_raised { Person.connection.remove_index("people", ["last_name", "first_name"]) } - assert_nothing_raised { Person.connection.add_index("people", ["last_name", "first_name"], :order => :desc) } - assert_nothing_raised { Person.connection.remove_index("people", ["last_name", "first_name"]) } - end - end - - def test_index_symbol_names - assert_nothing_raised { Person.connection.add_index :people, :primary_contact_id, :name => :symbol_index_name } - assert Person.connection.index_exists?(:people, :primary_contact_id, :name => :symbol_index_name) - assert_nothing_raised { Person.connection.remove_index :people, :name => :symbol_index_name } - assert !Person.connection.index_exists?(:people, :primary_contact_id, :name => :symbol_index_name) - end - - def test_add_index_length_limit - good_index_name = 'x' * Person.connection.index_name_length - too_long_index_name = good_index_name + 'x' - assert_raise(ArgumentError) { Person.connection.add_index("people", "first_name", :name => too_long_index_name) } - assert !Person.connection.index_name_exists?("people", too_long_index_name, false) - assert_nothing_raised { Person.connection.add_index("people", "first_name", :name => good_index_name) } - assert Person.connection.index_name_exists?("people", good_index_name, false) - Person.connection.remove_index("people", :name => good_index_name) - end - - def test_remove_nonexistent_index - # we do this by name, so OpenBase is a wash as noted above - unless current_adapter?(:OpenBaseAdapter) - assert_raise(ArgumentError) { Person.connection.remove_index("people", "no_such_index") } - end - end - - def test_rename_index - unless current_adapter?(:OpenBaseAdapter) - # keep the names short to make Oracle and similar behave - Person.connection.add_index('people', [:first_name], :name => 'old_idx') - assert_nothing_raised { Person.connection.rename_index('people', 'old_idx', 'new_idx') } - # if the adapter doesn't support the indexes call, pick defaults that let the test pass - assert !Person.connection.index_name_exists?('people', 'old_idx', false) - assert Person.connection.index_name_exists?('people', 'new_idx', true) - end - end - - def test_double_add_index - unless current_adapter?(:OpenBaseAdapter) - Person.connection.add_index('people', [:first_name], :name => 'some_idx') - assert_raise(ArgumentError) { Person.connection.add_index('people', [:first_name], :name => 'some_idx') } - end - end - - def test_index_exists - Person.connection.create_table :testings do |t| - t.column :foo, :string, :limit => 100 - t.column :bar, :string, :limit => 100 - end - Person.connection.add_index :testings, :foo - - assert Person.connection.index_exists?(:testings, :foo) - assert !Person.connection.index_exists?(:testings, :bar) - ensure - Person.connection.drop_table :testings rescue nil - end - - def test_index_exists_on_multiple_columns - Person.connection.create_table :testings do |t| - t.column :foo, :string, :limit => 100 - t.column :bar, :string, :limit => 100 - end - Person.connection.add_index :testings, [:foo, :bar] - - assert Person.connection.index_exists?(:testings, [:foo, :bar]) - ensure - Person.connection.drop_table :testings rescue nil - end - - def test_unique_index_exists - Person.connection.create_table :testings do |t| - t.column :foo, :string, :limit => 100 - end - Person.connection.add_index :testings, :foo, :unique => true - - assert Person.connection.index_exists?(:testings, :foo, :unique => true) - ensure - Person.connection.drop_table :testings rescue nil - end - - def test_named_index_exists - Person.connection.create_table :testings do |t| - t.column :foo, :string, :limit => 100 - end - Person.connection.add_index :testings, :foo, :name => "custom_index_name" - - assert Person.connection.index_exists?(:testings, :foo, :name => "custom_index_name") - ensure - Person.connection.drop_table :testings rescue nil - end - - def testing_table_with_only_foo_attribute - Person.connection.create_table :testings, :id => false do |t| - t.column :foo, :string - end - - yield Person.connection - ensure - Person.connection.drop_table :testings rescue nil - end - protected :testing_table_with_only_foo_attribute + def teardown + ActiveRecord::Base.connection.initialize_schema_migrations_table + ActiveRecord::Base.connection.execute "DELETE FROM #{ActiveRecord::Migrator.schema_migrations_table_name}" - def test_create_table_without_id - testing_table_with_only_foo_attribute do |connection| - assert_equal connection.columns(:testings).size, 1 - end - end - - def test_add_column_with_primary_key_attribute - testing_table_with_only_foo_attribute do |connection| - assert_nothing_raised { connection.add_column :testings, :id, :primary_key } - assert_equal connection.columns(:testings).size, 2 - end - end - - def test_create_table_adds_id - Person.connection.create_table :testings do |t| - t.column :foo, :string - end - - assert_equal %w(foo id), - Person.connection.columns(:testings).map { |c| c.name }.sort - ensure - Person.connection.drop_table :testings rescue nil + %w(things awesome_things prefix_things_suffix p_awesome_things_s ).each do |table| + Thing.connection.drop_table(table) rescue nil end + Thing.reset_column_information - def test_create_table_with_not_null_column - assert_nothing_raised do - Person.connection.create_table :testings do |t| - t.column :foo, :string, :null => false - end - end - - assert_raise(ActiveRecord::StatementInvalid) do - Person.connection.execute "insert into testings (foo) values (NULL)" - end - ensure - Person.connection.drop_table :testings rescue nil + %w(reminders people_reminders prefix_reminders_suffix).each do |table| + Reminder.connection.drop_table(table) rescue nil end + Reminder.reset_column_information - def test_create_table_with_defaults - # MySQL doesn't allow defaults on TEXT or BLOB columns. - mysql = current_adapter?(:MysqlAdapter) || current_adapter?(:Mysql2Adapter) - - Person.connection.create_table :testings do |t| - t.column :one, :string, :default => "hello" - t.column :two, :boolean, :default => true - t.column :three, :boolean, :default => false - t.column :four, :integer, :default => 1 - t.column :five, :text, :default => "hello" unless mysql - end - - columns = Person.connection.columns(:testings) - one = columns.detect { |c| c.name == "one" } - two = columns.detect { |c| c.name == "two" } - three = columns.detect { |c| c.name == "three" } - four = columns.detect { |c| c.name == "four" } - five = columns.detect { |c| c.name == "five" } unless mysql - - assert_equal "hello", one.default - assert_equal true, two.default - assert_equal false, three.default - assert_equal 1, four.default - assert_equal "hello", five.default unless mysql - - ensure - Person.connection.drop_table :testings rescue nil + %w(last_name key bio age height wealth birthday favorite_day + moment_of_truth male administrator funny).each do |column| + Person.connection.remove_column('people', column) rescue nil end + Person.connection.remove_column("people", "first_name") rescue nil + Person.connection.remove_column("people", "middle_name") rescue nil + Person.connection.add_column("people", "first_name", :string, :limit => 40) + Person.reset_column_information + end - def test_create_table_with_limits - assert_nothing_raised do - Person.connection.create_table :testings do |t| - t.column :foo, :string, :limit => 255 - - t.column :default_int, :integer - - t.column :one_int, :integer, :limit => 1 - t.column :four_int, :integer, :limit => 4 - t.column :eight_int, :integer, :limit => 8 - t.column :eleven_int, :integer, :limit => 11 - end - end - - columns = Person.connection.columns(:testings) - foo = columns.detect { |c| c.name == "foo" } - assert_equal 255, foo.limit - - default = columns.detect { |c| c.name == "default_int" } - one = columns.detect { |c| c.name == "one_int" } - four = columns.detect { |c| c.name == "four_int" } - eight = columns.detect { |c| c.name == "eight_int" } - eleven = columns.detect { |c| c.name == "eleven_int" } - - if current_adapter?(:PostgreSQLAdapter) - assert_equal 'integer', default.sql_type - assert_equal 'smallint', one.sql_type - assert_equal 'integer', four.sql_type - assert_equal 'bigint', eight.sql_type - assert_equal 'integer', eleven.sql_type - elsif current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter) - assert_match 'int(11)', default.sql_type - assert_match 'tinyint', one.sql_type - assert_match 'int', four.sql_type - assert_match 'bigint', eight.sql_type - assert_match 'int(11)', eleven.sql_type - elsif current_adapter?(:OracleAdapter) - assert_equal 'NUMBER(38)', default.sql_type - assert_equal 'NUMBER(1)', one.sql_type - assert_equal 'NUMBER(4)', four.sql_type - assert_equal 'NUMBER(8)', eight.sql_type - end - ensure - Person.connection.drop_table :testings rescue nil + def test_create_table_with_force_true_does_not_drop_nonexisting_table + if Person.connection.table_exists?(:testings2) + Person.connection.drop_table :testings2 end - def test_create_table_with_primary_key_prefix_as_table_name_with_underscore - ActiveRecord::Base.primary_key_prefix_type = :table_name_with_underscore + # using a copy as we need the drop_table method to + # continue to work for the ensure block of the test + temp_conn = Person.connection.dup - Person.connection.create_table :testings do |t| - t.column :foo, :string - end + assert_not_equal temp_conn, Person.connection - assert_equal %w(foo testing_id), Person.connection.columns(:testings).map { |c| c.name }.sort - ensure - Person.connection.drop_table :testings rescue nil - ActiveRecord::Base.primary_key_prefix_type = nil + temp_conn.create_table :testings2, :force => true do |t| + t.column :foo, :string end + ensure + Person.connection.drop_table :testings2 rescue nil + end - def test_create_table_with_primary_key_prefix_as_table_name - ActiveRecord::Base.primary_key_prefix_type = :table_name - - Person.connection.create_table :testings do |t| - t.column :foo, :string - end - - assert_equal %w(foo testingid), Person.connection.columns(:testings).map { |c| c.name }.sort - ensure - Person.connection.drop_table :testings rescue nil - ActiveRecord::Base.primary_key_prefix_type = nil - end + def connection + ActiveRecord::Base.connection + end - def test_create_table_with_force_true_does_not_drop_nonexisting_table - if Person.connection.table_exists?(:testings2) - Person.connection.drop_table :testings2 - end + def test_migration_instance_has_connection + migration = Class.new(ActiveRecord::Migration).new + assert_equal connection, migration.connection + end - # using a copy as we need the drop_table method to - # continue to work for the ensure block of the test - temp_conn = Person.connection.dup - temp_conn.expects(:drop_table).never - temp_conn.create_table :testings2, :force => true do |t| - t.column :foo, :string + def test_method_missing_delegates_to_connection + migration = Class.new(ActiveRecord::Migration) { + def connection + Class.new { + def create_table; "hi mom!"; end + }.new end - ensure - Person.connection.drop_table :testings2 rescue nil - end + }.new - def test_create_table_with_timestamps_should_create_datetime_columns - table_name = :testings + assert_equal "hi mom!", migration.method_missing(:create_table) + end - Person.connection.create_table table_name do |t| - t.timestamps - end - created_columns = Person.connection.columns(table_name) + def test_add_table_with_decimals + Person.connection.drop_table :big_numbers rescue nil + + assert !BigNumber.table_exists? + GiveMeBigNumbers.up + + assert BigNumber.create( + :bank_balance => 1586.43, + :big_bank_balance => BigDecimal("1000234000567.95"), + :world_population => 6000000000, + :my_house_population => 3, + :value_of_e => BigDecimal("2.7182818284590452353602875") + ) + + b = BigNumber.first + assert_not_nil b + + assert_not_nil b.bank_balance + assert_not_nil b.big_bank_balance + assert_not_nil b.world_population + assert_not_nil b.my_house_population + assert_not_nil b.value_of_e + + # TODO: set world_population >= 2**62 to cover 64-bit platforms and test + # is_a?(Bignum) + assert_kind_of Integer, b.world_population + assert_equal 6000000000, b.world_population + assert_kind_of Fixnum, b.my_house_population + assert_equal 3, b.my_house_population + assert_kind_of BigDecimal, b.bank_balance + assert_equal BigDecimal("1586.43"), b.bank_balance + assert_kind_of BigDecimal, b.big_bank_balance + assert_equal BigDecimal("1000234000567.95"), b.big_bank_balance + + # This one is fun. The 'value_of_e' field is defined as 'DECIMAL' with + # precision/scale explicitly left out. By the SQL standard, numbers + # assigned to this field should be truncated but that's seldom respected. + if current_adapter?(:PostgreSQLAdapter) + # - PostgreSQL changes the SQL spec on columns declared simply as + # "decimal" to something more useful: instead of being given a scale + # of 0, they take on the compile-time limit for precision and scale, + # so the following should succeed unless you have used really wacky + # compilation options + # - SQLite2 has the default behavior of preserving all data sent in, + # so this happens there too + assert_kind_of BigDecimal, b.value_of_e + assert_equal BigDecimal("2.7182818284590452353602875"), b.value_of_e + elsif current_adapter?(:SQLite3Adapter) + # - SQLite3 stores a float, in violation of SQL + assert_kind_of BigDecimal, b.value_of_e + assert_in_delta BigDecimal("2.71828182845905"), b.value_of_e, 0.00000000000001 + else + # - SQL standard is an integer + assert_kind_of Fixnum, b.value_of_e + assert_equal 2, b.value_of_e + end + + GiveMeBigNumbers.down + assert_raise(ActiveRecord::StatementInvalid) { BigNumber.first } + end - created_at_column = created_columns.detect {|c| c.name == 'created_at' } - updated_at_column = created_columns.detect {|c| c.name == 'updated_at' } + def test_filtering_migrations + assert !Person.column_methods_hash.include?(:last_name) + assert !Reminder.table_exists? - assert !created_at_column.null - assert !updated_at_column.null - ensure - Person.connection.drop_table table_name rescue nil - end + name_filter = lambda { |migration| migration.name == "ValidPeopleHaveLastNames" } + ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", &name_filter) - def test_create_table_with_timestamps_should_create_datetime_columns_with_options - table_name = :testings + Person.reset_column_information + assert Person.column_methods_hash.include?(:last_name) + assert_raise(ActiveRecord::StatementInvalid) { Reminder.first } - Person.connection.create_table table_name do |t| - t.timestamps :null => false - end - created_columns = Person.connection.columns(table_name) + ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid", &name_filter) - created_at_column = created_columns.detect {|c| c.name == 'created_at' } - updated_at_column = created_columns.detect {|c| c.name == 'updated_at' } + Person.reset_column_information + assert !Person.column_methods_hash.include?(:last_name) + assert_raise(ActiveRecord::StatementInvalid) { Reminder.first } + end - assert !created_at_column.null - assert !updated_at_column.null - ensure - Person.connection.drop_table table_name rescue nil + class MockMigration < ActiveRecord::Migration + attr_reader :went_up, :went_down + def initialize + @went_up = false + @went_down = false end - def test_create_table_without_a_block - table_name = :testings - Person.connection.create_table table_name - ensure - Person.connection.drop_table table_name rescue nil + def up + @went_up = true + super end - # Sybase, and SQLite3 will not allow you to add a NOT NULL - # column to a table without a default value. - unless current_adapter?(:SybaseAdapter, :SQLite3Adapter) - def test_add_column_not_null_without_default - Person.connection.create_table :testings do |t| - t.column :foo, :string - end - Person.connection.add_column :testings, :bar, :string, :null => false - - assert_raise(ActiveRecord::StatementInvalid) do - Person.connection.execute "insert into testings (foo, bar) values ('hello', NULL)" - end - ensure - Person.connection.drop_table :testings rescue nil - end + def down + @went_down = true + super end + end - def test_add_column_not_null_with_default - Person.connection.create_table :testings do |t| - t.column :foo, :string - end - - con = Person.connection - Person.connection.enable_identity_insert("testings", true) if current_adapter?(:SybaseAdapter) - Person.connection.execute "insert into testings (#{con.quote_column_name('id')}, #{con.quote_column_name('foo')}) values (1, 'hello')" - Person.connection.enable_identity_insert("testings", false) if current_adapter?(:SybaseAdapter) - assert_nothing_raised {Person.connection.add_column :testings, :bar, :string, :null => false, :default => "default" } - - assert_raise(ActiveRecord::StatementInvalid) do - unless current_adapter?(:OpenBaseAdapter) - Person.connection.execute "insert into testings (#{con.quote_column_name('id')}, #{con.quote_column_name('foo')}, #{con.quote_column_name('bar')}) values (2, 'hello', NULL)" - else - Person.connection.insert("INSERT INTO testings (#{con.quote_column_name('id')}, #{con.quote_column_name('foo')}, #{con.quote_column_name('bar')}) VALUES (2, 'hello', NULL)", - "Testing Insert","id",2) - end - end - ensure - Person.connection.drop_table :testings rescue nil - end - - # We specifically do a manual INSERT here, and then test only the SELECT - # functionality. This allows us to more easily catch INSERT being broken, - # but SELECT actually working fine. - def test_native_decimal_insert_manual_vs_automatic - correct_value = '0012345678901234567890.0123456789'.to_d - - Person.delete_all - Person.connection.add_column "people", "wealth", :decimal, :precision => '30', :scale => '10' - Person.reset_column_information - - # Do a manual insertion - if current_adapter?(:OracleAdapter) - Person.connection.execute "insert into people (id, wealth, created_at, updated_at) values (people_seq.nextval, 12345678901234567890.0123456789, sysdate, sysdate)" - elsif current_adapter?(:OpenBaseAdapter) || (current_adapter?(:MysqlAdapter) && Mysql.client_version < 50003) #before mysql 5.0.3 decimals stored as strings - Person.connection.execute "insert into people (wealth, created_at, updated_at) values ('12345678901234567890.0123456789', 0, 0)" - elsif current_adapter?(:PostgreSQLAdapter) - Person.connection.execute "insert into people (wealth, created_at, updated_at) values (12345678901234567890.0123456789, now(), now())" - else - Person.connection.execute "insert into people (wealth, created_at, updated_at) values (12345678901234567890.0123456789, 0, 0)" - end - - # SELECT - row = Person.find(:first) - assert_kind_of BigDecimal, row.wealth - - # If this assert fails, that means the SELECT is broken! - unless current_adapter?(:SQLite3Adapter) - assert_equal correct_value, row.wealth - end - - # Reset to old state - Person.delete_all - - # Now use the Rails insertion - assert_nothing_raised { Person.create :wealth => BigDecimal.new("12345678901234567890.0123456789") } - - # SELECT - row = Person.find(:first) - assert_kind_of BigDecimal, row.wealth + def test_instance_based_migration_up + migration = MockMigration.new + assert !migration.went_up, 'have not gone up' + assert !migration.went_down, 'have not gone down' - # If these asserts fail, that means the INSERT (create function, or cast to SQL) is broken! - unless current_adapter?(:SQLite3Adapter) - assert_equal correct_value, row.wealth - end + migration.migrate :up + assert migration.went_up, 'have gone up' + assert !migration.went_down, 'have not gone down' + end - # Reset to old state - Person.connection.del_column "people", "wealth" rescue nil - Person.reset_column_information - end + def test_instance_based_migration_down + migration = MockMigration.new + assert !migration.went_up, 'have not gone up' + assert !migration.went_down, 'have not gone down' - def test_add_column_with_precision_and_scale - Person.connection.add_column 'people', 'wealth', :decimal, :precision => 9, :scale => 7 - Person.reset_column_information + migration.migrate :down + assert !migration.went_up, 'have gone up' + assert migration.went_down, 'have not gone down' + end - wealth_column = Person.columns_hash['wealth'] - assert_equal 9, wealth_column.precision - assert_equal 7, wealth_column.scale + def test_migrator_one_up_with_exception_and_rollback + unless ActiveRecord::Base.connection.supports_ddl_transactions? + skip "not supported on #{ActiveRecord::Base.connection.class}" end - # Test SQLite adapter specifically for decimal types with precision and scale - # attributes, since these need to be maintained in schema but aren't actually - # used in SQLite itself - if current_adapter?(:SQLite3Adapter) - def test_change_column_with_new_precision_and_scale - Person.delete_all - Person.connection.add_column 'people', 'wealth', :decimal, :precision => 9, :scale => 7 - Person.reset_column_information - - Person.connection.change_column 'people', 'wealth', :decimal, :precision => 12, :scale => 8 - Person.reset_column_information - - wealth_column = Person.columns_hash['wealth'] - assert_equal 12, wealth_column.precision - assert_equal 8, wealth_column.scale - end - - def test_change_column_preserve_other_column_precision_and_scale - Person.delete_all - Person.connection.add_column 'people', 'last_name', :string - Person.connection.add_column 'people', 'wealth', :decimal, :precision => 9, :scale => 7 - Person.reset_column_information - - wealth_column = Person.columns_hash['wealth'] - assert_equal 9, wealth_column.precision - assert_equal 7, wealth_column.scale - - Person.connection.change_column 'people', 'last_name', :string, :null => false - Person.reset_column_information - - wealth_column = Person.columns_hash['wealth'] - assert_equal 9, wealth_column.precision - assert_equal 7, wealth_column.scale - end - end + refute Person.column_methods_hash.include?(:last_name) - def test_native_types - Person.delete_all - Person.connection.add_column "people", "last_name", :string - Person.connection.add_column "people", "bio", :text - Person.connection.add_column "people", "age", :integer - Person.connection.add_column "people", "height", :float - Person.connection.add_column "people", "wealth", :decimal, :precision => '30', :scale => '10' - Person.connection.add_column "people", "birthday", :datetime - Person.connection.add_column "people", "favorite_day", :date - Person.connection.add_column "people", "moment_of_truth", :datetime - Person.connection.add_column "people", "male", :boolean - Person.reset_column_information - - assert_nothing_raised do - Person.create :first_name => 'bob', :last_name => 'bobsen', - :bio => "I was born ....", :age => 18, :height => 1.78, - :wealth => BigDecimal.new("12345678901234567890.0123456789"), - :birthday => 18.years.ago, :favorite_day => 10.days.ago, - :moment_of_truth => "1782-10-10 21:40:18", :male => true - end + migration = Struct.new(:name, :version) { + def migrate(x); raise 'Something broke'; end + }.new('zomg', 100) - bob = Person.find(:first) - assert_equal 'bob', bob.first_name - assert_equal 'bobsen', bob.last_name - assert_equal "I was born ....", bob.bio - assert_equal 18, bob.age + migrator = ActiveRecord::Migrator.new(:up, [migration], 100) - # Test for 30 significant digits (beyond the 16 of float), 10 of them - # after the decimal place. + e = assert_raise(StandardError) { migrator.migrate } - unless current_adapter?(:SQLite3Adapter) - assert_equal BigDecimal.new("0012345678901234567890.0123456789"), bob.wealth - end + assert_equal "An error has occurred, this and all later migrations canceled:\n\nSomething broke", e.message - assert_equal true, bob.male? + Person.reset_column_information + refute Person.column_methods_hash.include?(:last_name) + end - assert_equal String, bob.first_name.class - assert_equal String, bob.last_name.class - assert_equal String, bob.bio.class - assert_equal Fixnum, bob.age.class - assert_equal Time, bob.birthday.class + def test_schema_migrations_table_name + ActiveRecord::Base.table_name_prefix = "prefix_" + ActiveRecord::Base.table_name_suffix = "_suffix" + Reminder.reset_table_name + assert_equal "prefix_schema_migrations_suffix", ActiveRecord::Migrator.schema_migrations_table_name + ActiveRecord::Base.table_name_prefix = "" + ActiveRecord::Base.table_name_suffix = "" + Reminder.reset_table_name + assert_equal "schema_migrations", ActiveRecord::Migrator.schema_migrations_table_name + ensure + ActiveRecord::Base.table_name_prefix = "" + ActiveRecord::Base.table_name_suffix = "" + end - if current_adapter?(:OracleAdapter, :SybaseAdapter) - # Sybase, and Oracle don't differentiate between date/time - assert_equal Time, bob.favorite_day.class - else - assert_equal Date, bob.favorite_day.class - end + def test_proper_table_name + assert_equal "table", ActiveRecord::Migrator.proper_table_name('table') + assert_equal "table", ActiveRecord::Migrator.proper_table_name(:table) + assert_equal "reminders", ActiveRecord::Migrator.proper_table_name(Reminder) + Reminder.reset_table_name + assert_equal Reminder.table_name, ActiveRecord::Migrator.proper_table_name(Reminder) + + # 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", ActiveRecord::Migrator.proper_table_name(Reminder) + Reminder.table_name_prefix = '' + Reminder.table_name_suffix = '' + Reminder.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 + assert_equal "prefix_table_suffix", ActiveRecord::Migrator.proper_table_name('table') + assert_equal "prefix_table_suffix", ActiveRecord::Migrator.proper_table_name(:table) + ActiveRecord::Base.table_name_prefix = "" + ActiveRecord::Base.table_name_suffix = "" + Reminder.reset_table_name + end - # Oracle adapter stores Time or DateTime with timezone value already in _before_type_cast column - # therefore no timezone change is done afterwards when default timezone is changed - unless current_adapter?(:OracleAdapter) - # Test DateTime column and defaults, including timezone. - # FIXME: moment of truth may be Time on 64-bit platforms. - if bob.moment_of_truth.is_a?(DateTime) - - with_env_tz 'US/Eastern' do - bob.reload - assert_equal DateTime.local_offset, bob.moment_of_truth.offset - assert_not_equal 0, bob.moment_of_truth.offset - assert_not_equal "Z", bob.moment_of_truth.zone - # US/Eastern is -5 hours from GMT - assert_equal Rational(-5, 24), bob.moment_of_truth.offset - assert_match(/\A-05:?00\Z/, bob.moment_of_truth.zone) #ruby 1.8.6 uses HH:MM, prior versions use HHMM - assert_equal DateTime::ITALY, bob.moment_of_truth.start - end - end - end + def test_rename_table_with_prefix_and_suffix + assert !Thing.table_exists? + ActiveRecord::Base.table_name_prefix = 'p_' + ActiveRecord::Base.table_name_suffix = '_s' + Thing.reset_table_name + Thing.reset_sequence_name + WeNeedThings.up + + assert Thing.create("content" => "hello world") + assert_equal "hello world", Thing.first.content + + RenameThings.up + Thing.table_name = "p_awesome_things_s" + + assert_equal "hello world", Thing.first.content + ensure + ActiveRecord::Base.table_name_prefix = '' + ActiveRecord::Base.table_name_suffix = '' + Thing.reset_table_name + Thing.reset_sequence_name + end - assert_instance_of TrueClass, bob.male? - assert_kind_of BigDecimal, bob.wealth - end + def test_add_drop_table_with_prefix_and_suffix + assert !Reminder.table_exists? + ActiveRecord::Base.table_name_prefix = 'prefix_' + ActiveRecord::Base.table_name_suffix = '_suffix' + Reminder.reset_table_name + Reminder.reset_sequence_name + WeNeedReminders.up + assert Reminder.create("content" => "hello world", "remind_at" => Time.now) + assert_equal "hello world", Reminder.first.content + + WeNeedReminders.down + assert_raise(ActiveRecord::StatementInvalid) { Reminder.first } + ensure + ActiveRecord::Base.table_name_prefix = '' + ActiveRecord::Base.table_name_suffix = '' + Reminder.reset_table_name + Reminder.reset_sequence_name + end - if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter) - def test_unabstracted_database_dependent_types - Person.delete_all + def test_create_table_with_binary_column + Person.connection.drop_table :binary_testings rescue nil - ActiveRecord::Migration.add_column :people, :intelligence_quotient, :tinyint - Person.reset_column_information - assert_match(/tinyint/, Person.columns_hash['intelligence_quotient'].sql_type) - ensure - ActiveRecord::Migration.remove_column :people, :intelligence_quotient rescue nil + assert_nothing_raised { + Person.connection.create_table :binary_testings do |t| + t.column "data", :binary, :null => false end - end - - def test_add_remove_single_field_using_string_arguments - assert !Person.column_methods_hash.include?(:last_name) - - ActiveRecord::Migration.add_column 'people', 'last_name', :string - - Person.reset_column_information - assert Person.column_methods_hash.include?(:last_name) + } - ActiveRecord::Migration.remove_column 'people', 'last_name' - - Person.reset_column_information - assert !Person.column_methods_hash.include?(:last_name) - end - - def test_add_remove_single_field_using_symbol_arguments - assert !Person.column_methods_hash.include?(:last_name) - - ActiveRecord::Migration.add_column :people, :last_name, :string - - Person.reset_column_information - assert Person.column_methods_hash.include?(:last_name) - - ActiveRecord::Migration.remove_column :people, :last_name - - Person.reset_column_information - assert !Person.column_methods_hash.include?(:last_name) - end + columns = Person.connection.columns(:binary_testings) + data_column = columns.detect { |c| c.name == "data" } if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter) - def testing_table_for_positioning - Person.connection.create_table :testings, :id => false do |t| - t.column :first, :integer - t.column :second, :integer - t.column :third, :integer - end - - yield Person.connection - ensure - Person.connection.drop_table :testings rescue nil - end - protected :testing_table_for_positioning - - def test_column_positioning - testing_table_for_positioning do |conn| - assert_equal %w(first second third), conn.columns(:testings).map {|c| c.name } - end - end - - def test_add_column_with_positioning - testing_table_for_positioning do |conn| - conn.add_column :testings, :new_col, :integer - assert_equal %w(first second third new_col), conn.columns(:testings).map {|c| c.name } - end - testing_table_for_positioning do |conn| - conn.add_column :testings, :new_col, :integer, :first => true - assert_equal %w(new_col first second third), conn.columns(:testings).map {|c| c.name } - end - testing_table_for_positioning do |conn| - conn.add_column :testings, :new_col, :integer, :after => :first - assert_equal %w(first new_col second third), conn.columns(:testings).map {|c| c.name } - end - end - - def test_change_column_with_positioning - testing_table_for_positioning do |conn| - conn.change_column :testings, :second, :integer, :first => true - assert_equal %w(second first third), conn.columns(:testings).map {|c| c.name } - end - testing_table_for_positioning do |conn| - conn.change_column :testings, :second, :integer, :after => :third - assert_equal %w(first third second), conn.columns(:testings).map {|c| c.name } - end - end - end - - def test_add_rename - Person.delete_all - - begin - Person.connection.add_column "people", "girlfriend", :string - Person.reset_column_information - Person.create :girlfriend => 'bobette' - - Person.connection.rename_column "people", "girlfriend", "exgirlfriend" - - Person.reset_column_information - bob = Person.find(:first) - - assert_equal "bobette", bob.exgirlfriend - ensure - Person.connection.remove_column("people", "girlfriend") rescue nil - Person.connection.remove_column("people", "exgirlfriend") rescue nil - end - - end - - def test_rename_column_using_symbol_arguments - begin - names_before = Person.find(:all).map(&:first_name) - Person.connection.rename_column :people, :first_name, :nick_name - Person.reset_column_information - assert Person.column_names.include?("nick_name") - assert_equal names_before, Person.find(:all).map(&:nick_name) - ensure - Person.connection.remove_column("people","nick_name") - Person.connection.add_column("people","first_name", :string) - end - end - - def test_rename_column - begin - names_before = Person.find(:all).map(&:first_name) - Person.connection.rename_column "people", "first_name", "nick_name" - Person.reset_column_information - assert Person.column_names.include?("nick_name") - assert_equal names_before, Person.find(:all).map(&:nick_name) - ensure - Person.connection.remove_column("people","nick_name") - Person.connection.add_column("people","first_name", :string) - end - end - - def test_rename_column_preserves_default_value_not_null - begin - default_before = Developer.connection.columns("developers").find { |c| c.name == "salary" }.default - assert_equal 70000, default_before - Developer.connection.rename_column "developers", "salary", "anual_salary" - Developer.reset_column_information - assert Developer.column_names.include?("anual_salary") - default_after = Developer.connection.columns("developers").find { |c| c.name == "anual_salary" }.default - assert_equal 70000, default_after - ensure - Developer.connection.rename_column "developers", "anual_salary", "salary" - Developer.reset_column_information - end - end - - def test_rename_nonexistent_column - ActiveRecord::Base.connection.create_table(:hats) do |table| - table.column :hat_name, :string, :default => nil - end - exception = if current_adapter?(:PostgreSQLAdapter, :OracleAdapter) - ActiveRecord::StatementInvalid - else - ActiveRecord::ActiveRecordError - end - assert_raise(exception) do - Person.connection.rename_column "hats", "nonexistent", "should_fail" - end - ensure - ActiveRecord::Base.connection.drop_table(:hats) - end - - def test_rename_column_with_sql_reserved_word - begin - assert_nothing_raised { Person.connection.rename_column "people", "first_name", "group" } - Person.reset_column_information - assert Person.column_names.include?("group") - ensure - Person.connection.remove_column("people", "group") rescue nil - Person.connection.add_column("people", "first_name", :string) rescue nil - end - end - - def test_rename_column_with_an_index - ActiveRecord::Base.connection.create_table(:hats) do |table| - table.column :hat_name, :string, :limit => 100 - table.column :hat_size, :integer - end - Person.connection.add_index :hats, :hat_name - assert_nothing_raised do - Person.connection.rename_column "hats", "hat_name", "name" - end - ensure - ActiveRecord::Base.connection.drop_table(:hats) - end - - def test_remove_column_with_index - ActiveRecord::Base.connection.create_table(:hats) do |table| - table.column :hat_name, :string, :limit => 100 - table.column :hat_size, :integer - end - ActiveRecord::Base.connection.add_index "hats", "hat_size" - - assert_nothing_raised { Person.connection.remove_column("hats", "hat_size") } - ensure - ActiveRecord::Base.connection.drop_table(:hats) - end - - def test_remove_column_with_multi_column_index - ActiveRecord::Base.connection.create_table(:hats) do |table| - table.column :hat_name, :string, :limit => 100 - table.column :hat_size, :integer - table.column :hat_style, :string, :limit => 100 - end - ActiveRecord::Base.connection.add_index "hats", ["hat_style", "hat_size"], :unique => true - - assert_nothing_raised { Person.connection.remove_column("hats", "hat_size") } - ensure - ActiveRecord::Base.connection.drop_table(:hats) - end - - def test_remove_column_no_second_parameter_raises_exception - assert_raise(ArgumentError) { Person.connection.remove_column("funny") } + assert_equal '', data_column.default + else + assert_nil data_column.default end - def test_change_type_of_not_null_column - assert_nothing_raised do - Topic.connection.change_column "topics", "written_on", :datetime, :null => false - Topic.reset_column_information - - Topic.connection.change_column "topics", "written_on", :datetime, :null => false - Topic.reset_column_information - - Topic.connection.change_column "topics", "written_on", :datetime, :null => true - Topic.reset_column_information - end - end + Person.connection.drop_table :binary_testings rescue nil + end - if current_adapter?(:SQLite3Adapter) - def test_rename_table_for_sqlite_should_work_with_reserved_words - begin - assert_nothing_raised do - ActiveRecord::Base.connection.rename_table :references, :old_references - ActiveRecord::Base.connection.create_table :octopuses do |t| - t.column :url, :string - end - end - - assert_nothing_raised { ActiveRecord::Base.connection.rename_table :octopuses, :references } - - # Using explicit id in insert for compatibility across all databases - con = ActiveRecord::Base.connection - assert_nothing_raised do - con.execute "INSERT INTO 'references' (#{con.quote_column_name('id')}, #{con.quote_column_name('url')}) VALUES (1, 'http://rubyonrails.com')" - end - assert_equal 'http://rubyonrails.com', ActiveRecord::Base.connection.select_value("SELECT url FROM 'references' WHERE id=1") - - ensure - ActiveRecord::Base.connection.drop_table :references - ActiveRecord::Base.connection.rename_table :old_references, :references - end - end - end + def test_create_table_with_custom_sequence_name + skip "not supported" unless current_adapter? :OracleAdapter - def test_rename_table + # table name is 29 chars, the standard sequence name will + # be 33 chars and should be shortened + assert_nothing_raised do begin - ActiveRecord::Base.connection.create_table :octopuses do |t| - t.column :url, :string + Person.connection.create_table :table_with_name_thats_just_ok do |t| + t.column :foo, :string, :null => false end - ActiveRecord::Base.connection.rename_table :octopuses, :octopi - - # Using explicit id in insert for compatibility across all databases - con = ActiveRecord::Base.connection - con.enable_identity_insert("octopi", true) if current_adapter?(:SybaseAdapter) - assert_nothing_raised { con.execute "INSERT INTO octopi (#{con.quote_column_name('id')}, #{con.quote_column_name('url')}) VALUES (1, 'http://www.foreverflying.com/octopus-black7.jpg')" } - con.enable_identity_insert("octopi", false) if current_adapter?(:SybaseAdapter) - - assert_equal 'http://www.foreverflying.com/octopus-black7.jpg', ActiveRecord::Base.connection.select_value("SELECT url FROM octopi WHERE id=1") - ensure - ActiveRecord::Base.connection.drop_table :octopuses rescue nil - ActiveRecord::Base.connection.drop_table :octopi rescue nil + Person.connection.drop_table :table_with_name_thats_just_ok rescue nil end end - def test_change_column_nullability - Person.delete_all - Person.connection.add_column "people", "funny", :boolean - Person.reset_column_information - assert Person.columns_hash["funny"].null, "Column 'funny' must initially allow nulls" - Person.connection.change_column "people", "funny", :boolean, :null => false, :default => true - Person.reset_column_information - assert !Person.columns_hash["funny"].null, "Column 'funny' must *not* allow nulls at this point" - Person.connection.change_column "people", "funny", :boolean, :null => true - Person.reset_column_information - assert Person.columns_hash["funny"].null, "Column 'funny' must allow nulls again at this point" - end - - def test_rename_table_with_an_index + # should be all good w/ a custom sequence name + assert_nothing_raised do begin - ActiveRecord::Base.connection.create_table :octopuses do |t| - t.column :url, :string + Person.connection.create_table :table_with_name_thats_just_ok, + :sequence_name => 'suitably_short_seq' do |t| + t.column :foo, :string, :null => false end - ActiveRecord::Base.connection.add_index :octopuses, :url - - ActiveRecord::Base.connection.rename_table :octopuses, :octopi - # Using explicit id in insert for compatibility across all databases - con = ActiveRecord::Base.connection - con.enable_identity_insert("octopi", true) if current_adapter?(:SybaseAdapter) - assert_nothing_raised { con.execute "INSERT INTO octopi (#{con.quote_column_name('id')}, #{con.quote_column_name('url')}) VALUES (1, 'http://www.foreverflying.com/octopus-black7.jpg')" } - con.enable_identity_insert("octopi", false) if current_adapter?(:SybaseAdapter) + Person.connection.execute("select suitably_short_seq.nextval from dual") - assert_equal 'http://www.foreverflying.com/octopus-black7.jpg', ActiveRecord::Base.connection.select_value("SELECT url FROM octopi WHERE id=1") - assert ActiveRecord::Base.connection.indexes(:octopi).first.columns.include?("url") ensure - ActiveRecord::Base.connection.drop_table :octopuses rescue nil - ActiveRecord::Base.connection.drop_table :octopi rescue nil - end - end - - def test_change_column - Person.connection.add_column 'people', 'age', :integer - label = "test_change_column Columns" - old_columns = Person.connection.columns(Person.table_name, label) - assert old_columns.find { |c| c.name == 'age' and c.type == :integer } - - assert_nothing_raised { Person.connection.change_column "people", "age", :string } - - new_columns = Person.connection.columns(Person.table_name, label) - assert_nil new_columns.find { |c| c.name == 'age' and c.type == :integer } - assert new_columns.find { |c| c.name == 'age' and c.type == :string } - - old_columns = Topic.connection.columns(Topic.table_name, label) - assert old_columns.find { |c| c.name == 'approved' and c.type == :boolean and c.default == true } - assert_nothing_raised { Topic.connection.change_column :topics, :approved, :boolean, :default => false } - new_columns = Topic.connection.columns(Topic.table_name, label) - assert_nil new_columns.find { |c| c.name == 'approved' and c.type == :boolean and c.default == true } - assert new_columns.find { |c| c.name == 'approved' and c.type == :boolean and c.default == false } - assert_nothing_raised { Topic.connection.change_column :topics, :approved, :boolean, :default => true } - end - - def test_change_column_with_nil_default - Person.connection.add_column "people", "contributor", :boolean, :default => true - Person.reset_column_information - assert Person.new.contributor? - - assert_nothing_raised { Person.connection.change_column "people", "contributor", :boolean, :default => nil } - Person.reset_column_information - assert !Person.new.contributor? - assert_nil Person.new.contributor - ensure - Person.connection.remove_column("people", "contributor") rescue nil - end - - def test_change_column_with_new_default - Person.connection.add_column "people", "administrator", :boolean, :default => true - Person.reset_column_information - assert Person.new.administrator? - - assert_nothing_raised { Person.connection.change_column "people", "administrator", :boolean, :default => false } - Person.reset_column_information - assert !Person.new.administrator? - ensure - Person.connection.remove_column("people", "administrator") rescue nil - end - - def test_change_column_default - Person.connection.change_column_default "people", "first_name", "Tester" - Person.reset_column_information - assert_equal "Tester", Person.new.first_name - end - - def test_change_column_quotes_column_names - Person.connection.create_table :testings do |t| - t.column :select, :string - end - - assert_nothing_raised { Person.connection.change_column :testings, :select, :string, :limit => 10 } - - # Oracle needs primary key value from sequence - if current_adapter?(:OracleAdapter) - assert_nothing_raised { Person.connection.execute "insert into testings (id, #{Person.connection.quote_column_name('select')}) values (testings_seq.nextval, '7 chars')" } - else - assert_nothing_raised { Person.connection.execute "insert into testings (#{Person.connection.quote_column_name('select')}) values ('7 chars')" } - end - ensure - Person.connection.drop_table :testings rescue nil - end - - def test_keeping_default_and_notnull_constaint_on_change - Person.connection.create_table :testings do |t| - t.column :title, :string - end - person_klass = Class.new(Person) - person_klass.set_table_name 'testings' - - person_klass.connection.add_column "testings", "wealth", :integer, :null => false, :default => 99 - person_klass.reset_column_information - assert_equal 99, person_klass.columns_hash["wealth"].default - assert_equal false, person_klass.columns_hash["wealth"].null - # Oracle needs primary key value from sequence - if current_adapter?(:OracleAdapter) - assert_nothing_raised {person_klass.connection.execute("insert into testings (id, title) values (testings_seq.nextval, 'tester')")} - else - assert_nothing_raised {person_klass.connection.execute("insert into testings (title) values ('tester')")} + Person.connection.drop_table :table_with_name_thats_just_ok, + :sequence_name => 'suitably_short_seq' rescue nil end - - # change column default to see that column doesn't lose its not null definition - person_klass.connection.change_column_default "testings", "wealth", 100 - person_klass.reset_column_information - assert_equal 100, person_klass.columns_hash["wealth"].default - assert_equal false, person_klass.columns_hash["wealth"].null - - # rename column to see that column doesn't lose its not null and/or default definition - person_klass.connection.rename_column "testings", "wealth", "money" - person_klass.reset_column_information - assert_nil person_klass.columns_hash["wealth"] - assert_equal 100, person_klass.columns_hash["money"].default - assert_equal false, person_klass.columns_hash["money"].null - - # change column - person_klass.connection.change_column "testings", "money", :integer, :null => false, :default => 1000 - person_klass.reset_column_information - assert_equal 1000, person_klass.columns_hash["money"].default - assert_equal false, person_klass.columns_hash["money"].null - - # change column, make it nullable and clear default - person_klass.connection.change_column "testings", "money", :integer, :null => true, :default => nil - person_klass.reset_column_information - assert_nil person_klass.columns_hash["money"].default - assert_equal true, person_klass.columns_hash["money"].null - - # change_column_null, make it not nullable and set null values to a default value - person_klass.connection.execute('UPDATE testings SET money = NULL') - person_klass.connection.change_column_null "testings", "money", false, 2000 - person_klass.reset_column_information - assert_nil person_klass.columns_hash["money"].default - assert_equal false, person_klass.columns_hash["money"].null - assert_equal [2000], Person.connection.select_values("SELECT money FROM testings").map { |s| s.to_i }.sort - ensure - Person.connection.drop_table :testings rescue nil end - def test_change_column_default_to_null - Person.connection.change_column_default "people", "first_name", nil - Person.reset_column_information - assert_nil Person.new.first_name - end - - def test_column_exists - Person.connection.create_table :testings do |t| - t.column :foo, :string - end - - assert Person.connection.column_exists?(:testings, :foo) - assert !Person.connection.column_exists?(:testings, :bar) - ensure - Person.connection.drop_table :testings rescue nil - end - - def test_column_exists_with_type - Person.connection.create_table :testings do |t| - t.column :foo, :string - t.column :bar, :decimal, :precision => 8, :scale => 2 - end - - assert Person.connection.column_exists?(:testings, :foo, :string) - assert !Person.connection.column_exists?(:testings, :foo, :integer) - assert Person.connection.column_exists?(:testings, :bar, :decimal) - assert !Person.connection.column_exists?(:testings, :bar, :integer) - ensure - Person.connection.drop_table :testings rescue nil - end - - def test_column_exists_with_definition - Person.connection.create_table :testings do |t| - t.column :foo, :string, :limit => 100 - t.column :bar, :decimal, :precision => 8, :scale => 2 - end - - assert Person.connection.column_exists?(:testings, :foo, :string, :limit => 100) - assert !Person.connection.column_exists?(:testings, :foo, :string, :limit => 50) - assert Person.connection.column_exists?(:testings, :bar, :decimal, :precision => 8, :scale => 2) - assert !Person.connection.column_exists?(:testings, :bar, :decimal, :precision => 10, :scale => 2) - ensure - Person.connection.drop_table :testings rescue nil + # confirm the custom sequence got dropped + assert_raise(ActiveRecord::StatementInvalid) do + Person.connection.execute("select suitably_short_seq.nextval from dual") end + end - def test_column_exists_on_table_with_no_options_parameter_supplied - Person.connection.create_table :testings do |t| - t.string :foo - end - Person.connection.change_table :testings do |t| - assert t.column_exists?(:foo) - assert !(t.column_exists?(:bar)) - end + protected + def with_env_tz(new_tz = 'US/Eastern') + old_tz, ENV['TZ'] = ENV['TZ'], new_tz + yield ensure - Person.connection.drop_table :testings rescue nil - end - - def test_add_table - assert !Reminder.table_exists? - - WeNeedReminders.up - - assert Reminder.create("content" => "hello world", "remind_at" => Time.now) - assert_equal "hello world", Reminder.find(:first).content - - WeNeedReminders.down - assert_raise(ActiveRecord::StatementInvalid) { Reminder.find(:first) } - end - - def test_add_table_with_decimals - Person.connection.drop_table :big_numbers rescue nil - - assert !BigNumber.table_exists? - GiveMeBigNumbers.up - - assert BigNumber.create( - :bank_balance => 1586.43, - :big_bank_balance => BigDecimal("1000234000567.95"), - :world_population => 6000000000, - :my_house_population => 3, - :value_of_e => BigDecimal("2.7182818284590452353602875") - ) - - b = BigNumber.find(:first) - assert_not_nil b - - assert_not_nil b.bank_balance - assert_not_nil b.big_bank_balance - assert_not_nil b.world_population - assert_not_nil b.my_house_population - assert_not_nil b.value_of_e - - # TODO: set world_population >= 2**62 to cover 64-bit platforms and test - # is_a?(Bignum) - assert_kind_of Integer, b.world_population - assert_equal 6000000000, b.world_population - assert_kind_of Fixnum, b.my_house_population - assert_equal 3, b.my_house_population - assert_kind_of BigDecimal, b.bank_balance - assert_equal BigDecimal("1586.43"), b.bank_balance - assert_kind_of BigDecimal, b.big_bank_balance - assert_equal BigDecimal("1000234000567.95"), b.big_bank_balance - - # This one is fun. The 'value_of_e' field is defined as 'DECIMAL' with - # precision/scale explicitly left out. By the SQL standard, numbers - # assigned to this field should be truncated but that's seldom respected. - if current_adapter?(:PostgreSQLAdapter) - # - PostgreSQL changes the SQL spec on columns declared simply as - # "decimal" to something more useful: instead of being given a scale - # of 0, they take on the compile-time limit for precision and scale, - # so the following should succeed unless you have used really wacky - # compilation options - # - SQLite2 has the default behavior of preserving all data sent in, - # so this happens there too - assert_kind_of BigDecimal, b.value_of_e - assert_equal BigDecimal("2.7182818284590452353602875"), b.value_of_e - elsif current_adapter?(:SQLite3Adapter) - # - SQLite3 stores a float, in violation of SQL - assert_kind_of BigDecimal, b.value_of_e - assert_in_delta BigDecimal("2.71828182845905"), b.value_of_e, 0.00000000000001 - else - # - SQL standard is an integer - assert_kind_of Fixnum, b.value_of_e - assert_equal 2, b.value_of_e - end - - GiveMeBigNumbers.down - assert_raise(ActiveRecord::StatementInvalid) { BigNumber.find(:first) } - end - - def test_migrator - assert !Person.column_methods_hash.include?(:last_name) - assert !Reminder.table_exists? - - ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid") - - assert_equal 3, ActiveRecord::Migrator.current_version - Person.reset_column_information - assert Person.column_methods_hash.include?(:last_name) - assert Reminder.create("content" => "hello world", "remind_at" => Time.now) - assert_equal "hello world", Reminder.find(:first).content - - ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid") - - assert_equal 0, ActiveRecord::Migrator.current_version - Person.reset_column_information - assert !Person.column_methods_hash.include?(:last_name) - assert_raise(ActiveRecord::StatementInvalid) { Reminder.find(:first) } - end - - class MockMigration < ActiveRecord::Migration - attr_reader :went_up, :went_down - def initialize - @went_up = false - @went_down = false - end - - def up - @went_up = true - super - end - - def down - @went_down = true - super - end + old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ') end +end - def test_instance_based_migration_up - migration = MockMigration.new - assert !migration.went_up, 'have not gone up' - assert !migration.went_down, 'have not gone down' - - migration.migrate :up - assert migration.went_up, 'have gone up' - assert !migration.went_down, 'have not gone down' +class ReservedWordsMigrationTest < ActiveRecord::TestCase + def test_drop_index_from_table_named_values + connection = Person.connection + connection.create_table :values, :force => true do |t| + t.integer :value end - def test_instance_based_migration_down - migration = MockMigration.new - assert !migration.went_up, 'have not gone up' - assert !migration.went_down, 'have not gone down' - - migration.migrate :down - assert !migration.went_up, 'have gone up' - assert migration.went_down, 'have not gone down' + assert_nothing_raised do + connection.add_index :values, :value + connection.remove_index :values, :column => :value end - def test_migrator_one_up - assert !Person.column_methods_hash.include?(:last_name) - assert !Reminder.table_exists? - - ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", 1) - - Person.reset_column_information - assert Person.column_methods_hash.include?(:last_name) - assert !Reminder.table_exists? + connection.drop_table :values rescue nil + end +end - ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", 2) - assert Reminder.create("content" => "hello world", "remind_at" => Time.now) - assert_equal "hello world", Reminder.find(:first).content +class ChangeTableMigrationsTest < ActiveRecord::TestCase + def setup + @connection = Person.connection + @connection.stubs(:add_index) + @connection.create_table :delete_me, :force => true do |t| end + end - def test_migrator_one_down - ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid") - - ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid", 1) + def teardown + Person.connection.drop_table :delete_me rescue nil + end - Person.reset_column_information - assert Person.column_methods_hash.include?(:last_name) - assert !Reminder.table_exists? + def test_references_column_type_adds_id + with_change_table do |t| + @connection.expects(:add_column).with(:delete_me, 'customer_id', :integer, {}) + t.references :customer end + end - def test_migrator_one_up_one_down - ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", 1) - ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid", 0) - - assert !Person.column_methods_hash.include?(:last_name) - assert !Reminder.table_exists? + def test_remove_references_column_type_removes_id + with_change_table do |t| + @connection.expects(:remove_column).with(:delete_me, 'customer_id') + t.remove_references :customer end + end - def test_migrator_double_up - assert_equal(0, ActiveRecord::Migrator.current_version) - ActiveRecord::Migrator.run(:up, MIGRATIONS_ROOT + "/valid", 1) - assert_nothing_raised { ActiveRecord::Migrator.run(:up, MIGRATIONS_ROOT + "/valid", 1) } - assert_equal(1, ActiveRecord::Migrator.current_version) + def test_add_belongs_to_works_like_add_references + with_change_table do |t| + @connection.expects(:add_column).with(:delete_me, 'customer_id', :integer, {}) + t.belongs_to :customer end + end - def test_migrator_double_down - assert_equal(0, ActiveRecord::Migrator.current_version) - ActiveRecord::Migrator.run(:up, MIGRATIONS_ROOT + "/valid", 1) - ActiveRecord::Migrator.run(:down, MIGRATIONS_ROOT + "/valid", 1) - assert_nothing_raised { ActiveRecord::Migrator.run(:down, MIGRATIONS_ROOT + "/valid", 1) } - assert_equal(0, ActiveRecord::Migrator.current_version) + def test_remove_belongs_to_works_like_remove_references + with_change_table do |t| + @connection.expects(:remove_column).with(:delete_me, 'customer_id') + t.remove_belongs_to :customer end + end - if ActiveRecord::Base.connection.supports_ddl_transactions? - def test_migrator_one_up_with_exception_and_rollback - assert !Person.column_methods_hash.include?(:last_name) - - e = assert_raise(StandardError) do - ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/broken", 100) - end - - assert_equal "An error has occurred, this and all later migrations canceled:\n\nSomething broke", e.message - - Person.reset_column_information - assert !Person.column_methods_hash.include?(:last_name) - end + def test_references_column_type_with_polymorphic_adds_type + with_change_table do |t| + @connection.expects(:add_column).with(:delete_me, 'taggable_type', :string, {}) + @connection.expects(:add_column).with(:delete_me, 'taggable_id', :integer, {}) + t.references :taggable, :polymorphic => true end + end - def test_finds_migrations - migrations = ActiveRecord::Migrator.new(:up, MIGRATIONS_ROOT + "/valid").migrations - - [[1, 'ValidPeopleHaveLastNames'], [2, 'WeNeedReminders'], [3, 'InnocentJointable']].each_with_index do |pair, i| - assert_equal migrations[i].version, pair.first - assert_equal migrations[i].name, pair.last - end + def test_remove_references_column_type_with_polymorphic_removes_type + with_change_table do |t| + @connection.expects(:remove_column).with(:delete_me, 'taggable_type') + @connection.expects(:remove_column).with(:delete_me, 'taggable_id') + t.remove_references :taggable, :polymorphic => true end + end - def test_finds_migrations_from_two_directories - directories = [MIGRATIONS_ROOT + '/valid_with_timestamps', MIGRATIONS_ROOT + '/to_copy_with_timestamps'] - migrations = ActiveRecord::Migrator.new(:up, directories).migrations - - [[20090101010101, "PeopleHaveHobbies"], - [20090101010202, "PeopleHaveDescriptions"], - [20100101010101, "ValidWithTimestampsPeopleHaveLastNames"], - [20100201010101, "ValidWithTimestampsWeNeedReminders"], - [20100301010101, "ValidWithTimestampsInnocentJointable"]].each_with_index do |pair, i| - assert_equal pair.first, migrations[i].version - assert_equal pair.last, migrations[i].name - end + def test_references_column_type_with_polymorphic_and_options_null_is_false_adds_table_flag + with_change_table do |t| + @connection.expects(:add_column).with(:delete_me, 'taggable_type', :string, {:null => false}) + @connection.expects(:add_column).with(:delete_me, 'taggable_id', :integer, {:null => false}) + t.references :taggable, :polymorphic => true, :null => false end + end - def test_dump_schema_information_outputs_lexically_ordered_versions - migration_path = MIGRATIONS_ROOT + '/valid_with_timestamps' - ActiveRecord::Migrator.run(:up, migration_path, 20100301010101) - ActiveRecord::Migrator.run(:up, migration_path, 20100201010101) - - schema_info = ActiveRecord::Base.connection.dump_schema_information - assert_match(/20100201010101.*20100301010101/m, schema_info) + def test_remove_references_column_type_with_polymorphic_and_options_null_is_false_removes_table_flag + with_change_table do |t| + @connection.expects(:remove_column).with(:delete_me, 'taggable_type') + @connection.expects(:remove_column).with(:delete_me, 'taggable_id') + t.remove_references :taggable, :polymorphic => true, :null => false end + end - def test_finds_pending_migrations - ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/interleaved/pass_2", 1) - migrations = ActiveRecord::Migrator.new(:up, MIGRATIONS_ROOT + "/interleaved/pass_2").pending_migrations - - assert_equal 1, migrations.size - assert_equal migrations[0].version, 3 - assert_equal migrations[0].name, 'InterleavedInnocentJointable' + def test_timestamps_creates_updated_at_and_created_at + with_change_table do |t| + @connection.expects(:add_timestamps).with(:delete_me) + t.timestamps end + end - def test_relative_migrations - list = Dir.chdir(MIGRATIONS_ROOT) do - ActiveRecord::Migrator.up("valid/", 1) - end - - migration_proxy = list.find { |item| - item.name == 'ValidPeopleHaveLastNames' - } - assert migration_proxy, 'should find pending migration' + def test_remove_timestamps_creates_updated_at_and_created_at + with_change_table do |t| + @connection.expects(:remove_timestamps).with(:delete_me) + t.remove_timestamps end + end - def test_only_loads_pending_migrations - # migrate up to 1 - ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", 1) - - proxies = ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid", nil) - - names = proxies.map(&:name) - assert !names.include?('ValidPeopleHaveLastNames') - assert names.include?('WeNeedReminders') - assert names.include?('InnocentJointable') + def string_column + if current_adapter?(:PostgreSQLAdapter) + "character varying(255)" + elsif current_adapter?(:OracleAdapter) + 'VARCHAR2(255)' + else + 'varchar(255)' end + end - def test_target_version_zero_should_run_only_once - # migrate up to 1 - ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid", 1) - - # migrate down to 0 - ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid", 0) - - # migrate down to 0 again - proxies = ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid", 0) - assert_equal [], proxies + def integer_column + if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter) + 'int(11)' + elsif current_adapter?(:OracleAdapter) + 'NUMBER(38)' + else + 'integer' end + end - def test_migrator_db_has_no_schema_migrations_table - # Oracle adapter raises error if semicolon is present as last character - if current_adapter?(:OracleAdapter) - ActiveRecord::Base.connection.execute("DROP TABLE schema_migrations") - else - ActiveRecord::Base.connection.execute("DROP TABLE schema_migrations;") - end - assert_nothing_raised do - ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid", 1) - end + def test_integer_creates_integer_column + with_change_table do |t| + @connection.expects(:add_column).with(:delete_me, :foo, integer_column, {}) + @connection.expects(:add_column).with(:delete_me, :bar, integer_column, {}) + t.integer :foo, :bar end + end - def test_migrator_verbosity - ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", 1) - assert_not_equal 0, ActiveRecord::Migration.message_count - ActiveRecord::Migration.message_count = 0 - - ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid", 0) - assert_not_equal 0, ActiveRecord::Migration.message_count - ActiveRecord::Migration.message_count = 0 + def test_string_creates_string_column + with_change_table do |t| + @connection.expects(:add_column).with(:delete_me, :foo, string_column, {}) + @connection.expects(:add_column).with(:delete_me, :bar, string_column, {}) + t.string :foo, :bar end + end - def test_migrator_verbosity_off - ActiveRecord::Migration.verbose = false - ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", 1) - assert_equal 0, ActiveRecord::Migration.message_count - ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid", 0) - assert_equal 0, ActiveRecord::Migration.message_count + def test_column_creates_column + with_change_table do |t| + @connection.expects(:add_column).with(:delete_me, :bar, :integer, {}) + t.column :bar, :integer end + end - def test_migrator_going_down_due_to_version_target - ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", 1) - ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid", 0) - - assert !Person.column_methods_hash.include?(:last_name) - assert !Reminder.table_exists? - - ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid") - - Person.reset_column_information - assert Person.column_methods_hash.include?(:last_name) - assert Reminder.create("content" => "hello world", "remind_at" => Time.now) - assert_equal "hello world", Reminder.find(:first).content + def test_column_creates_column_with_options + with_change_table do |t| + @connection.expects(:add_column).with(:delete_me, :bar, :integer, {:null => false}) + t.column :bar, :integer, :null => false end + end - def test_migrator_rollback - ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid") - assert_equal(3, ActiveRecord::Migrator.current_version) - - ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid") - assert_equal(2, ActiveRecord::Migrator.current_version) - - ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid") - assert_equal(1, ActiveRecord::Migrator.current_version) - - ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid") - assert_equal(0, ActiveRecord::Migrator.current_version) - - ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid") - assert_equal(0, ActiveRecord::Migrator.current_version) + def test_index_creates_index + with_change_table do |t| + @connection.expects(:add_index).with(:delete_me, :bar, {}) + t.index :bar end + end - def test_migrator_forward - ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid", 1) - assert_equal(1, ActiveRecord::Migrator.current_version) - - ActiveRecord::Migrator.forward(MIGRATIONS_ROOT + "/valid", 2) - assert_equal(3, ActiveRecord::Migrator.current_version) - - ActiveRecord::Migrator.forward(MIGRATIONS_ROOT + "/valid") - assert_equal(3, ActiveRecord::Migrator.current_version) + def test_index_creates_index_with_options + with_change_table do |t| + @connection.expects(:add_index).with(:delete_me, :bar, {:unique => true}) + t.index :bar, :unique => true end + end - def test_get_all_versions - ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid") - assert_equal([1,2,3], ActiveRecord::Migrator.get_all_versions) - - ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid") - assert_equal([1,2], ActiveRecord::Migrator.get_all_versions) - - ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid") - assert_equal([1], ActiveRecord::Migrator.get_all_versions) - - ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid") - assert_equal([], ActiveRecord::Migrator.get_all_versions) + def test_index_exists + with_change_table do |t| + @connection.expects(:index_exists?).with(:delete_me, :bar, {}) + t.index_exists?(:bar) end + end - def test_schema_migrations_table_name - ActiveRecord::Base.table_name_prefix = "prefix_" - ActiveRecord::Base.table_name_suffix = "_suffix" - Reminder.reset_table_name - assert_equal "prefix_schema_migrations_suffix", ActiveRecord::Migrator.schema_migrations_table_name - ActiveRecord::Base.table_name_prefix = "" - ActiveRecord::Base.table_name_suffix = "" - Reminder.reset_table_name - assert_equal "schema_migrations", ActiveRecord::Migrator.schema_migrations_table_name - ensure - ActiveRecord::Base.table_name_prefix = "" - ActiveRecord::Base.table_name_suffix = "" - end - - def test_proper_table_name - assert_equal "table", ActiveRecord::Migrator.proper_table_name('table') - assert_equal "table", ActiveRecord::Migrator.proper_table_name(:table) - assert_equal "reminders", ActiveRecord::Migrator.proper_table_name(Reminder) - Reminder.reset_table_name - assert_equal Reminder.table_name, ActiveRecord::Migrator.proper_table_name(Reminder) - - # 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", ActiveRecord::Migrator.proper_table_name(Reminder) - Reminder.table_name_prefix = '' - Reminder.table_name_suffix = '' - Reminder.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 - assert_equal "prefix_table_suffix", ActiveRecord::Migrator.proper_table_name('table') - assert_equal "prefix_table_suffix", ActiveRecord::Migrator.proper_table_name(:table) - ActiveRecord::Base.table_name_prefix = "" - ActiveRecord::Base.table_name_suffix = "" - Reminder.reset_table_name - end - - def test_add_drop_table_with_prefix_and_suffix - assert !Reminder.table_exists? - ActiveRecord::Base.table_name_prefix = 'prefix_' - ActiveRecord::Base.table_name_suffix = '_suffix' - Reminder.reset_table_name - Reminder.reset_sequence_name - WeNeedReminders.up - assert Reminder.create("content" => "hello world", "remind_at" => Time.now) - assert_equal "hello world", Reminder.find(:first).content - - WeNeedReminders.down - assert_raise(ActiveRecord::StatementInvalid) { Reminder.find(:first) } - ensure - ActiveRecord::Base.table_name_prefix = '' - ActiveRecord::Base.table_name_suffix = '' - Reminder.reset_table_name - Reminder.reset_sequence_name + def test_index_exists_with_options + with_change_table do |t| + @connection.expects(:index_exists?).with(:delete_me, :bar, {:unique => true}) + t.index_exists?(:bar, :unique => true) end + end - def test_create_table_with_binary_column - Person.connection.drop_table :binary_testings rescue nil - - assert_nothing_raised { - Person.connection.create_table :binary_testings do |t| - t.column "data", :binary, :null => false - end - } - - columns = Person.connection.columns(:binary_testings) - data_column = columns.detect { |c| c.name == "data" } - - if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter) - assert_equal '', data_column.default - else - assert_nil data_column.default - end - - Person.connection.drop_table :binary_testings rescue nil + def test_change_changes_column + with_change_table do |t| + @connection.expects(:change_column).with(:delete_me, :bar, :string, {}) + t.change :bar, :string end + end - def test_migrator_with_duplicates - assert_raise(ActiveRecord::DuplicateMigrationVersionError) do - ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/duplicate", nil) - end + def test_change_changes_column_with_options + with_change_table do |t| + @connection.expects(:change_column).with(:delete_me, :bar, :string, {:null => true}) + t.change :bar, :string, :null => true end + end - def test_migrator_with_duplicate_names - assert_raise(ActiveRecord::DuplicateMigrationNameError, "Multiple migrations have the name Chunky") do - ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/duplicate_names", nil) - end + def test_change_default_changes_column + with_change_table do |t| + @connection.expects(:change_column_default).with(:delete_me, :bar, :string) + t.change_default :bar, :string end + end - def test_migrator_with_missing_version_numbers - assert_raise(ActiveRecord::UnknownMigrationVersionError) do - ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/missing", 500) - end + def test_remove_drops_single_column + with_change_table do |t| + @connection.expects(:remove_column).with(:delete_me, :bar) + t.remove :bar end + end - def test_create_table_with_custom_sequence_name - return unless current_adapter? :OracleAdapter - - # table name is 29 chars, the standard sequence name will - # be 33 chars and should be shortened - assert_nothing_raised do - begin - Person.connection.create_table :table_with_name_thats_just_ok do |t| - t.column :foo, :string, :null => false - end - ensure - Person.connection.drop_table :table_with_name_thats_just_ok rescue nil - end - end - - # should be all good w/ a custom sequence name - assert_nothing_raised do - begin - Person.connection.create_table :table_with_name_thats_just_ok, - :sequence_name => 'suitably_short_seq' do |t| - t.column :foo, :string, :null => false - end - - Person.connection.execute("select suitably_short_seq.nextval from dual") - - ensure - Person.connection.drop_table :table_with_name_thats_just_ok, - :sequence_name => 'suitably_short_seq' rescue nil - end - end - - # confirm the custom sequence got dropped - assert_raise(ActiveRecord::StatementInvalid) do - Person.connection.execute("select suitably_short_seq.nextval from dual") - end + def test_remove_drops_multiple_columns + with_change_table do |t| + @connection.expects(:remove_column).with(:delete_me, :bar, :baz) + t.remove :bar, :baz 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') - end - end - class MigrationLoggerTest < ActiveRecord::TestCase - def test_migration_should_be_run_without_logger - previous_logger = ActiveRecord::Base.logger - ActiveRecord::Base.logger = nil - assert_nothing_raised do - ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid") - end - ensure - ActiveRecord::Base.logger = previous_logger + def test_remove_index_removes_index_with_options + with_change_table do |t| + @connection.expects(:remove_index).with(:delete_me, {:unique => true}) + t.remove_index :unique => true end end - class InterleavedMigrationsTest < ActiveRecord::TestCase - def test_migrator_interleaved_migrations - ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/interleaved/pass_1") - - assert_nothing_raised do - ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/interleaved/pass_2") - end - - Person.reset_column_information - assert Person.column_methods_hash.include?(:last_name) - - assert_nothing_raised do - proxies = ActiveRecord::Migrator.down( - MIGRATIONS_ROOT + "/interleaved/pass_3") - names = proxies.map(&:name) - assert names.include?('InterleavedPeopleHaveLastNames') - assert names.include?('InterleavedInnocentJointable') - end + def test_rename_renames_column + with_change_table do |t| + @connection.expects(:rename_column).with(:delete_me, :bar, :baz) + t.rename :bar, :baz end end - class ReservedWordsMigrationTest < ActiveRecord::TestCase - def test_drop_index_from_table_named_values - connection = Person.connection - connection.create_table :values, :force => true do |t| - t.integer :value - end - - assert_nothing_raised do - connection.add_index :values, :value - connection.remove_index :values, :column => :value - end - - connection.drop_table :values rescue nil + protected + def with_change_table + Person.connection.change_table :delete_me do |t| + yield t end end +end - - class ChangeTableMigrationsTest < ActiveRecord::TestCase +if ActiveRecord::Base.connection.supports_bulk_alter? + class BulkAlterTableMigrationsTest < ActiveRecord::TestCase def setup @connection = Person.connection - @connection.create_table :delete_me, :force => true do |t| - end + @connection.create_table(:delete_me, :force => true) {|t| } end def teardown - Person.connection.drop_table :delete_me rescue nil + Person.connection.drop_table(:delete_me) rescue nil end - def test_references_column_type_adds_id - with_change_table do |t| - @connection.expects(:add_column).with(:delete_me, 'customer_id', :integer, {}) - t.references :customer - end - end - - def test_remove_references_column_type_removes_id - with_change_table do |t| - @connection.expects(:remove_column).with(:delete_me, 'customer_id') - t.remove_references :customer + def test_adding_multiple_columns + assert_queries(1) do + with_bulk_change_table do |t| + t.column :name, :string + t.string :qualification, :experience + t.integer :age, :default => 0 + t.date :birthdate + t.timestamps + end end - end - def test_add_belongs_to_works_like_add_references - with_change_table do |t| - @connection.expects(:add_column).with(:delete_me, 'customer_id', :integer, {}) - t.belongs_to :customer - end + assert_equal 8, columns.size + [:name, :qualification, :experience].each {|s| assert_equal :string, column(s).type } + assert_equal 0, column(:age).default end - def test_remove_belongs_to_works_like_remove_references - with_change_table do |t| - @connection.expects(:remove_column).with(:delete_me, 'customer_id') - t.remove_belongs_to :customer + def test_removing_columns + with_bulk_change_table do |t| + t.string :qualification, :experience end - end - def test_references_column_type_with_polymorphic_adds_type - with_change_table do |t| - @connection.expects(:add_column).with(:delete_me, 'taggable_type', :string, {}) - @connection.expects(:add_column).with(:delete_me, 'taggable_id', :integer, {}) - t.references :taggable, :polymorphic => true - end - end + [:qualification, :experience].each {|c| assert column(c) } - def test_remove_references_column_type_with_polymorphic_removes_type - with_change_table do |t| - @connection.expects(:remove_column).with(:delete_me, 'taggable_type') - @connection.expects(:remove_column).with(:delete_me, 'taggable_id') - t.remove_references :taggable, :polymorphic => true + assert_queries(1) do + with_bulk_change_table do |t| + t.remove :qualification, :experience + t.string :qualification_experience + end end - end - def test_references_column_type_with_polymorphic_and_options_null_is_false_adds_table_flag - with_change_table do |t| - @connection.expects(:add_column).with(:delete_me, 'taggable_type', :string, {:null => false}) - @connection.expects(:add_column).with(:delete_me, 'taggable_id', :integer, {:null => false}) - t.references :taggable, :polymorphic => true, :null => false - end + [:qualification, :experience].each {|c| assert ! column(c) } + assert column(:qualification_experience) end - def test_remove_references_column_type_with_polymorphic_and_options_null_is_false_removes_table_flag - with_change_table do |t| - @connection.expects(:remove_column).with(:delete_me, 'taggable_type') - @connection.expects(:remove_column).with(:delete_me, 'taggable_id') - t.remove_references :taggable, :polymorphic => true, :null => false + def test_adding_indexes + with_bulk_change_table do |t| + t.string :username + t.string :name + t.integer :age end - end - def test_timestamps_creates_updated_at_and_created_at - with_change_table do |t| - @connection.expects(:add_timestamps).with(:delete_me) - t.timestamps + # Adding an index fires a query every time to check if an index already exists or not + assert_queries(3) do + with_bulk_change_table do |t| + t.index :username, :unique => true, :name => :awesome_username_index + t.index [:name, :age] + end end - end - def test_remove_timestamps_creates_updated_at_and_created_at - with_change_table do |t| - @connection.expects(:remove_timestamps).with(:delete_me) - t.remove_timestamps - end - end + assert_equal 2, indexes.size - def string_column - if current_adapter?(:PostgreSQLAdapter) - "character varying(255)" - elsif current_adapter?(:OracleAdapter) - 'VARCHAR2(255)' - else - 'varchar(255)' - end - end + name_age_index = index(:index_delete_me_on_name_and_age) + assert_equal ['name', 'age'].sort, name_age_index.columns.sort + assert ! name_age_index.unique - def integer_column - if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter) - 'int(11)' - elsif current_adapter?(:OracleAdapter) - 'NUMBER(38)' - else - 'integer' - end + assert index(:awesome_username_index).unique end - def test_integer_creates_integer_column - with_change_table do |t| - @connection.expects(:add_column).with(:delete_me, :foo, integer_column, {}) - @connection.expects(:add_column).with(:delete_me, :bar, integer_column, {}) - t.integer :foo, :bar + def test_removing_index + with_bulk_change_table do |t| + t.string :name + t.index :name end - end - def test_string_creates_string_column - with_change_table do |t| - @connection.expects(:add_column).with(:delete_me, :foo, string_column, {}) - @connection.expects(:add_column).with(:delete_me, :bar, string_column, {}) - t.string :foo, :bar - end - end + assert index(:index_delete_me_on_name) - def test_column_creates_column - with_change_table do |t| - @connection.expects(:add_column).with(:delete_me, :bar, :integer, {}) - t.column :bar, :integer + assert_queries(3) do + with_bulk_change_table do |t| + t.remove_index :name + t.index :name, :name => :new_name_index, :unique => true + end end - end - def test_column_creates_column_with_options - with_change_table do |t| - @connection.expects(:add_column).with(:delete_me, :bar, :integer, {:null => false}) - t.column :bar, :integer, :null => false - end - end + assert ! index(:index_delete_me_on_name) - def test_index_creates_index - with_change_table do |t| - @connection.expects(:add_index).with(:delete_me, :bar, {}) - t.index :bar - end + new_name_index = index(:new_name_index) + assert new_name_index.unique end - def test_index_creates_index_with_options - with_change_table do |t| - @connection.expects(:add_index).with(:delete_me, :bar, {:unique => true}) - t.index :bar, :unique => true + def test_changing_columns + with_bulk_change_table do |t| + t.string :name + t.date :birthdate end - end - def test_index_exists - with_change_table do |t| - @connection.expects(:index_exists?).with(:delete_me, :bar, {}) - t.index_exists?(:bar) - end - end + assert ! column(:name).default + assert_equal :date, column(:birthdate).type - def test_index_exists_with_options - with_change_table do |t| - @connection.expects(:index_exists?).with(:delete_me, :bar, {:unique => true}) - t.index_exists?(:bar, :unique => true) + # One query for columns (delete_me table) + # One query for primary key (delete_me table) + # One query to do the bulk change + assert_queries(3) do + with_bulk_change_table do |t| + t.change :name, :string, :default => 'NONAME' + t.change :birthdate, :datetime + end end - end - def test_change_changes_column - with_change_table do |t| - @connection.expects(:change_column).with(:delete_me, :bar, :string, {}) - t.change :bar, :string - end + assert_equal 'NONAME', column(:name).default + assert_equal :datetime, column(:birthdate).type end - def test_change_changes_column_with_options - with_change_table do |t| - @connection.expects(:change_column).with(:delete_me, :bar, :string, {:null => true}) - t.change :bar, :string, :null => true - end - end + protected - def test_change_default_changes_column - with_change_table do |t| - @connection.expects(:change_column_default).with(:delete_me, :bar, :string) - t.change_default :bar, :string - end - end + def with_bulk_change_table + # Reset columns/indexes cache as we're changing the table + @columns = @indexes = nil - def test_remove_drops_single_column - with_change_table do |t| - @connection.expects(:remove_column).with(:delete_me, [:bar]) - t.remove :bar + Person.connection.change_table(:delete_me, :bulk => true) do |t| + yield t end end - def test_remove_drops_multiple_columns - with_change_table do |t| - @connection.expects(:remove_column).with(:delete_me, [:bar, :baz]) - t.remove :bar, :baz - end + def column(name) + columns.detect {|c| c.name == name.to_s } end - def test_remove_index_removes_index_with_options - with_change_table do |t| - @connection.expects(:remove_index).with(:delete_me, {:unique => true}) - t.remove_index :unique => true - end + def columns + @columns ||= Person.connection.columns('delete_me') end - def test_rename_renames_column - with_change_table do |t| - @connection.expects(:rename_column).with(:delete_me, :bar, :baz) - t.rename :bar, :baz - end + def index(name) + indexes.detect {|i| i.name == name.to_s } end - protected - def with_change_table - Person.connection.change_table :delete_me do |t| - yield t - end + def indexes + @indexes ||= Person.connection.indexes('delete_me') end - end - - if ActiveRecord::Base.connection.supports_bulk_alter? - class BulkAlterTableMigrationsTest < ActiveRecord::TestCase - def setup - @connection = Person.connection - @connection.create_table(:delete_me, :force => true) {|t| } - end - - def teardown - Person.connection.drop_table(:delete_me) rescue nil - end - - def test_adding_multiple_columns - assert_queries(1) do - with_bulk_change_table do |t| - t.column :name, :string - t.string :qualification, :experience - t.integer :age, :default => 0 - t.date :birthdate - t.timestamps - end - end - - assert_equal 8, columns.size - [:name, :qualification, :experience].each {|s| assert_equal :string, column(s).type } - assert_equal 0, column(:age).default - end - - def test_removing_columns - with_bulk_change_table do |t| - t.string :qualification, :experience - end - - [:qualification, :experience].each {|c| assert column(c) } - - assert_queries(1) do - with_bulk_change_table do |t| - t.remove :qualification, :experience - t.string :qualification_experience - end - end - - [:qualification, :experience].each {|c| assert ! column(c) } - assert column(:qualification_experience) - end - - def test_adding_indexes - with_bulk_change_table do |t| - t.string :username - t.string :name - t.integer :age - end - - # Adding an index fires a query every time to check if an index already exists or not - assert_queries(3) do - with_bulk_change_table do |t| - t.index :username, :unique => true, :name => :awesome_username_index - t.index [:name, :age] - end - end - - assert_equal 2, indexes.size - - name_age_index = index(:index_delete_me_on_name_and_age) - assert_equal ['name', 'age'].sort, name_age_index.columns.sort - assert ! name_age_index.unique + end # AlterTableMigrationsTest - assert index(:awesome_username_index).unique - end - - def test_removing_index - with_bulk_change_table do |t| - t.string :name - t.index :name - end - - assert index(:index_delete_me_on_name) - - assert_queries(3) do - with_bulk_change_table do |t| - t.remove_index :name - t.index :name, :name => :new_name_index, :unique => true - end - end - - assert ! index(:index_delete_me_on_name) - - new_name_index = index(:new_name_index) - assert new_name_index.unique - end - - def test_changing_columns - with_bulk_change_table do |t| - t.string :name - t.date :birthdate - end - - assert ! column(:name).default - assert_equal :date, column(:birthdate).type - - # One query for columns (delete_me table) - # One query for primary key (delete_me table) - # One query to do the bulk change - assert_queries(3) do - with_bulk_change_table do |t| - t.change :name, :string, :default => 'NONAME' - t.change :birthdate, :datetime - end - end - - assert_equal 'NONAME', column(:name).default - assert_equal :datetime, column(:birthdate).type - end - - protected - - def with_bulk_change_table - # Reset columns/indexes cache as we're changing the table - @columns = @indexes = nil - - Person.connection.change_table(:delete_me, :bulk => true) do |t| - yield t - end - end - - def column(name) - columns.detect {|c| c.name == name.to_s } - end - - def columns - @columns ||= Person.connection.columns('delete_me') - end - - def index(name) - indexes.detect {|i| i.name == name.to_s } - end +end - def indexes - @indexes ||= Person.connection.indexes('delete_me') - end - end # AlterTableMigrationsTest +class CopyMigrationsTest < ActiveRecord::TestCase + def setup + end + def clear + ActiveRecord::Base.timestamped_migrations = true + to_delete = Dir[@migrations_path + "/*.rb"] - @existing_migrations + File.delete(*to_delete) end - class CopyMigrationsTest < ActiveRecord::TestCase - def setup - end + def test_copying_migrations_without_timestamps + ActiveRecord::Base.timestamped_migrations = false + @migrations_path = MIGRATIONS_ROOT + "/valid" + @existing_migrations = Dir[@migrations_path + "/*.rb"] + + copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy"}) + assert File.exists?(@migrations_path + "/4_people_have_hobbies.bukkits.rb") + assert File.exists?(@migrations_path + "/5_people_have_descriptions.bukkits.rb") + assert_equal [@migrations_path + "/4_people_have_hobbies.bukkits.rb", @migrations_path + "/5_people_have_descriptions.bukkits.rb"], copied.map(&:filename) + + expected = "# This migration comes from bukkits (originally 1)" + assert_equal expected, IO.readlines(@migrations_path + "/4_people_have_hobbies.bukkits.rb")[0].chomp + + files_count = Dir[@migrations_path + "/*.rb"].length + copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy"}) + assert_equal files_count, Dir[@migrations_path + "/*.rb"].length + assert copied.empty? + ensure + clear + end - def clear - ActiveRecord::Base.timestamped_migrations = true - to_delete = Dir[@migrations_path + "/*.rb"] - @existing_migrations - File.delete(*to_delete) - end + def test_copying_migrations_without_timestamps_from_2_sources + ActiveRecord::Base.timestamped_migrations = false + @migrations_path = MIGRATIONS_ROOT + "/valid" + @existing_migrations = Dir[@migrations_path + "/*.rb"] + + sources = {} + sources[:bukkits] = MIGRATIONS_ROOT + "/to_copy" + sources[:omg] = MIGRATIONS_ROOT + "/to_copy2" + ActiveRecord::Migration.copy(@migrations_path, sources) + assert File.exists?(@migrations_path + "/4_people_have_hobbies.bukkits.rb") + assert File.exists?(@migrations_path + "/5_people_have_descriptions.bukkits.rb") + assert File.exists?(@migrations_path + "/6_create_articles.omg.rb") + assert File.exists?(@migrations_path + "/7_create_comments.omg.rb") + + files_count = Dir[@migrations_path + "/*.rb"].length + ActiveRecord::Migration.copy(@migrations_path, sources) + assert_equal files_count, Dir[@migrations_path + "/*.rb"].length + ensure + clear + end - def test_copying_migrations_without_timestamps - ActiveRecord::Base.timestamped_migrations = false - @migrations_path = MIGRATIONS_ROOT + "/valid" - @existing_migrations = Dir[@migrations_path + "/*.rb"] + def test_copying_migrations_with_timestamps + @migrations_path = MIGRATIONS_ROOT + "/valid_with_timestamps" + @existing_migrations = Dir[@migrations_path + "/*.rb"] - copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy"}) - assert File.exists?(@migrations_path + "/4_people_have_hobbies.rb") - assert File.exists?(@migrations_path + "/5_people_have_descriptions.rb") - assert_equal [@migrations_path + "/4_people_have_hobbies.rb", @migrations_path + "/5_people_have_descriptions.rb"], copied.map(&:filename) + Time.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.exists?(@migrations_path + "/20100726101010_people_have_hobbies.bukkits.rb") + assert File.exists?(@migrations_path + "/20100726101011_people_have_descriptions.bukkits.rb") + expected = [@migrations_path + "/20100726101010_people_have_hobbies.bukkits.rb", + @migrations_path + "/20100726101011_people_have_descriptions.bukkits.rb"] + assert_equal expected, copied.map(&:filename) files_count = Dir[@migrations_path + "/*.rb"].length - copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy"}) + copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"}) assert_equal files_count, Dir[@migrations_path + "/*.rb"].length assert copied.empty? - ensure - clear end + ensure + clear + end - def test_copying_migrations_without_timestamps_from_2_sources - ActiveRecord::Base.timestamped_migrations = false - @migrations_path = MIGRATIONS_ROOT + "/valid" - @existing_migrations = Dir[@migrations_path + "/*.rb"] + def test_copying_migrations_with_timestamps_from_2_sources + @migrations_path = MIGRATIONS_ROOT + "/valid_with_timestamps" + @existing_migrations = Dir[@migrations_path + "/*.rb"] - sources = ActiveSupport::OrderedHash.new - sources[:bukkits] = MIGRATIONS_ROOT + "/to_copy" - sources[:omg] = MIGRATIONS_ROOT + "/to_copy2" - ActiveRecord::Migration.copy(@migrations_path, sources) - assert File.exists?(@migrations_path + "/4_people_have_hobbies.rb") - assert File.exists?(@migrations_path + "/5_people_have_descriptions.rb") - assert File.exists?(@migrations_path + "/6_create_articles.rb") - assert File.exists?(@migrations_path + "/7_create_comments.rb") + sources = {} + 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 + copied = ActiveRecord::Migration.copy(@migrations_path, sources) + assert File.exists?(@migrations_path + "/20100726101010_people_have_hobbies.bukkits.rb") + assert File.exists?(@migrations_path + "/20100726101011_people_have_descriptions.bukkits.rb") + assert File.exists?(@migrations_path + "/20100726101012_create_articles.omg.rb") + assert File.exists?(@migrations_path + "/20100726101013_create_comments.omg.rb") + assert_equal 4, copied.length files_count = Dir[@migrations_path + "/*.rb"].length ActiveRecord::Migration.copy(@migrations_path, sources) assert_equal files_count, Dir[@migrations_path + "/*.rb"].length - ensure - clear end + ensure + clear + end - def test_copying_migrations_with_timestamps - @migrations_path = MIGRATIONS_ROOT + "/valid_with_timestamps" - @existing_migrations = Dir[@migrations_path + "/*.rb"] + def test_copying_migrations_with_timestamps_to_destination_with_timestamps_in_future + @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 - copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"}) - assert File.exists?(@migrations_path + "/20100726101010_people_have_hobbies.rb") - assert File.exists?(@migrations_path + "/20100726101011_people_have_descriptions.rb") - expected = [@migrations_path + "/20100726101010_people_have_hobbies.rb", - @migrations_path + "/20100726101011_people_have_descriptions.rb"] - assert_equal expected, copied.map(&:filename) + Time.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.exists?(@migrations_path + "/20100301010102_people_have_hobbies.bukkits.rb") + assert File.exists?(@migrations_path + "/20100301010103_people_have_descriptions.bukkits.rb") - files_count = Dir[@migrations_path + "/*.rb"].length - copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"}) - assert_equal files_count, Dir[@migrations_path + "/*.rb"].length - assert copied.empty? - end - ensure - clear + files_count = Dir[@migrations_path + "/*.rb"].length + copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"}) + assert_equal files_count, Dir[@migrations_path + "/*.rb"].length + assert copied.empty? end + ensure + clear + end - def test_copying_migrations_with_timestamps_from_2_sources - @migrations_path = MIGRATIONS_ROOT + "/valid_with_timestamps" - @existing_migrations = Dir[@migrations_path + "/*.rb"] - - sources = ActiveSupport::OrderedHash.new - 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 - copied = ActiveRecord::Migration.copy(@migrations_path, sources) - assert File.exists?(@migrations_path + "/20100726101010_people_have_hobbies.rb") - assert File.exists?(@migrations_path + "/20100726101011_people_have_descriptions.rb") - assert File.exists?(@migrations_path + "/20100726101012_create_articles.rb") - assert File.exists?(@migrations_path + "/20100726101013_create_comments.rb") - assert_equal 4, copied.length - - files_count = Dir[@migrations_path + "/*.rb"].length - ActiveRecord::Migration.copy(@migrations_path, sources) - assert_equal files_count, Dir[@migrations_path + "/*.rb"].length - end - ensure - clear - end + def test_skipping_migrations + @migrations_path = MIGRATIONS_ROOT + "/valid_with_timestamps" + @existing_migrations = Dir[@migrations_path + "/*.rb"] + + sources = {} + sources[:bukkits] = MIGRATIONS_ROOT + "/to_copy_with_timestamps" + sources[:omg] = MIGRATIONS_ROOT + "/to_copy_with_name_collision" + + skipped = [] + on_skip = Proc.new { |name, migration| skipped << "#{name} #{migration.name}" } + copied = ActiveRecord::Migration.copy(@migrations_path, sources, :on_skip => on_skip) + assert_equal 2, copied.length + + assert_equal 1, skipped.length + assert_equal ["omg PeopleHaveHobbies"], skipped + ensure + clear + end - def test_copying_migrations_with_timestamps_to_destination_with_timestamps_in_future - @migrations_path = MIGRATIONS_ROOT + "/valid_with_timestamps" - @existing_migrations = Dir[@migrations_path + "/*.rb"] + def test_skip_is_not_called_if_migrations_are_from_the_same_plugin + @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 - ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"}) - assert File.exists?(@migrations_path + "/20100301010102_people_have_hobbies.rb") - assert File.exists?(@migrations_path + "/20100301010103_people_have_descriptions.rb") + sources = {} + sources[:bukkits] = MIGRATIONS_ROOT + "/to_copy_with_timestamps" - files_count = Dir[@migrations_path + "/*.rb"].length - copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"}) - assert_equal files_count, Dir[@migrations_path + "/*.rb"].length - assert copied.empty? - end - ensure - clear - end + skipped = [] + on_skip = Proc.new { |name, migration| skipped << "#{name} #{migration.name}" } + copied = ActiveRecord::Migration.copy(@migrations_path, sources, :on_skip => on_skip) + ActiveRecord::Migration.copy(@migrations_path, sources, :on_skip => on_skip) - def test_skipping_migrations - @migrations_path = MIGRATIONS_ROOT + "/valid_with_timestamps" - @existing_migrations = Dir[@migrations_path + "/*.rb"] + assert_equal 2, copied.length + assert_equal 0, skipped.length + ensure + clear + end - sources = ActiveSupport::OrderedHash.new - sources[:bukkits] = sources[:omg] = MIGRATIONS_ROOT + "/to_copy_with_timestamps" + def test_copying_migrations_to_non_existing_directory + @migrations_path = MIGRATIONS_ROOT + "/non_existing" + @existing_migrations = [] - skipped = [] - on_skip = Proc.new { |name, migration| skipped << "#{name} #{migration.name}" } - copied = ActiveRecord::Migration.copy(@migrations_path, sources, :on_skip => on_skip) + Time.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.exists?(@migrations_path + "/20100726101010_people_have_hobbies.bukkits.rb") + assert File.exists?(@migrations_path + "/20100726101011_people_have_descriptions.bukkits.rb") assert_equal 2, copied.length - - assert_equal 2, skipped.length - assert_equal ["bukkits PeopleHaveHobbies", "bukkits PeopleHaveDescriptions"], skipped - ensure - clear - end - - def test_copying_migrations_to_non_existing_directory - @migrations_path = MIGRATIONS_ROOT + "/non_existing" - @existing_migrations = [] - - Time.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.exists?(@migrations_path + "/20100726101010_people_have_hobbies.rb") - assert File.exists?(@migrations_path + "/20100726101011_people_have_descriptions.rb") - assert_equal 2, copied.length - end - ensure - clear - Dir.delete(@migrations_path) end + ensure + clear + Dir.delete(@migrations_path) + end - def test_copying_migrations_to_empty_directory - @migrations_path = MIGRATIONS_ROOT + "/empty" - @existing_migrations = [] + def test_copying_migrations_to_empty_directory + @migrations_path = MIGRATIONS_ROOT + "/empty" + @existing_migrations = [] - Time.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.exists?(@migrations_path + "/20100726101010_people_have_hobbies.rb") - assert File.exists?(@migrations_path + "/20100726101011_people_have_descriptions.rb") - assert_equal 2, copied.length - end - ensure - clear + Time.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.exists?(@migrations_path + "/20100726101010_people_have_hobbies.bukkits.rb") + assert File.exists?(@migrations_path + "/20100726101011_people_have_descriptions.bukkits.rb") + assert_equal 2, copied.length end + ensure + clear end end diff --git a/activerecord/test/cases/migrator_test.rb b/activerecord/test/cases/migrator_test.rb new file mode 100644 index 0000000000..1e16addcf3 --- /dev/null +++ b/activerecord/test/cases/migrator_test.rb @@ -0,0 +1,377 @@ +require "cases/helper" +require "cases/migration/helper" + +module ActiveRecord + class MigratorTest < ActiveRecord::TestCase + self.use_transactional_fixtures = false + + # Use this class to sense if migrations have gone + # up or down. + class Sensor < ActiveRecord::Migration + attr_reader :went_up, :went_down + + def initialize name = self.class.name, version = nil + super + @went_up = false + @went_down = false + end + + def up; @went_up = true; end + def down; @went_down = true; end + end + + def setup + super + ActiveRecord::SchemaMigration.create_table + ActiveRecord::SchemaMigration.delete_all rescue nil + end + + def teardown + super + ActiveRecord::SchemaMigration.delete_all rescue nil + end + + def test_migrator_with_duplicate_names + assert_raises(ActiveRecord::DuplicateMigrationNameError, "Multiple migrations have the name Chunky") do + list = [Migration.new('Chunky'), Migration.new('Chunky')] + ActiveRecord::Migrator.new(:up, list) + end + end + + def test_migrator_with_duplicate_versions + assert_raises(ActiveRecord::DuplicateMigrationVersionError) do + list = [Migration.new('Foo', 1), Migration.new('Bar', 1)] + ActiveRecord::Migrator.new(:up, list) + end + end + + def test_migrator_with_missing_version_numbers + assert_raises(ActiveRecord::UnknownMigrationVersionError) do + list = [Migration.new('Foo', 1), Migration.new('Bar', 2)] + ActiveRecord::Migrator.new(:up, list, 3).run + end + end + + def test_finds_migrations + migrations = ActiveRecord::Migrator.migrations(MIGRATIONS_ROOT + "/valid") + + [[1, 'ValidPeopleHaveLastNames'], [2, 'WeNeedReminders'], [3, 'InnocentJointable']].each_with_index do |pair, i| + assert_equal migrations[i].version, pair.first + assert_equal migrations[i].name, pair.last + end + end + + def test_finds_migrations_in_subdirectories + migrations = ActiveRecord::Migrator.migrations(MIGRATIONS_ROOT + "/valid_with_subdirectories") + + [[1, 'ValidPeopleHaveLastNames'], [2, 'WeNeedReminders'], [3, 'InnocentJointable']].each_with_index do |pair, i| + assert_equal migrations[i].version, pair.first + assert_equal migrations[i].name, pair.last + end + end + + def test_finds_migrations_from_two_directories + directories = [MIGRATIONS_ROOT + '/valid_with_timestamps', MIGRATIONS_ROOT + '/to_copy_with_timestamps'] + migrations = ActiveRecord::Migrator.migrations directories + + [[20090101010101, "PeopleHaveHobbies"], + [20090101010202, "PeopleHaveDescriptions"], + [20100101010101, "ValidWithTimestampsPeopleHaveLastNames"], + [20100201010101, "ValidWithTimestampsWeNeedReminders"], + [20100301010101, "ValidWithTimestampsInnocentJointable"]].each_with_index do |pair, i| + assert_equal pair.first, migrations[i].version + assert_equal pair.last, migrations[i].name + end + end + + def test_deprecated_constructor + assert_deprecated do + ActiveRecord::Migrator.new(:up, MIGRATIONS_ROOT + "/valid") + end + end + + def test_relative_migrations + list = Dir.chdir(MIGRATIONS_ROOT) do + ActiveRecord::Migrator.migrations("valid/") + end + + migration_proxy = list.find { |item| + item.name == 'ValidPeopleHaveLastNames' + } + assert migration_proxy, 'should find pending migration' + end + + def test_finds_pending_migrations + ActiveRecord::SchemaMigration.create!(:version => '1') + migration_list = [ Migration.new('foo', 1), Migration.new('bar', 3) ] + migrations = ActiveRecord::Migrator.new(:up, migration_list).pending_migrations + + assert_equal 1, migrations.size + assert_equal migration_list.last, migrations.first + end + + def test_migrator_interleaved_migrations + pass_one = [Sensor.new('One', 1)] + + ActiveRecord::Migrator.new(:up, pass_one).migrate + assert pass_one.first.went_up + refute pass_one.first.went_down + + pass_two = [Sensor.new('One', 1), Sensor.new('Three', 3)] + ActiveRecord::Migrator.new(:up, pass_two).migrate + refute pass_two[0].went_up + assert pass_two[1].went_up + assert pass_two.all? { |x| !x.went_down } + + pass_three = [Sensor.new('One', 1), + Sensor.new('Two', 2), + Sensor.new('Three', 3)] + + ActiveRecord::Migrator.new(:down, pass_three).migrate + assert pass_three[0].went_down + refute pass_three[1].went_down + assert pass_three[2].went_down + end + + def test_up_calls_up + migrations = [Sensor.new(nil, 0), Sensor.new(nil, 1), Sensor.new(nil, 2)] + ActiveRecord::Migrator.new(:up, migrations).migrate + assert migrations.all? { |m| m.went_up } + assert migrations.all? { |m| !m.went_down } + assert_equal 2, ActiveRecord::Migrator.current_version + end + + def test_down_calls_down + test_up_calls_up + + migrations = [Sensor.new(nil, 0), Sensor.new(nil, 1), Sensor.new(nil, 2)] + ActiveRecord::Migrator.new(:down, migrations).migrate + assert migrations.all? { |m| !m.went_up } + assert migrations.all? { |m| m.went_down } + assert_equal 0, ActiveRecord::Migrator.current_version + end + + def test_current_version + ActiveRecord::SchemaMigration.create!(:version => '1000') + assert_equal 1000, ActiveRecord::Migrator.current_version + end + + def test_migrator_one_up + calls, migrations = sensors(3) + + ActiveRecord::Migrator.new(:up, migrations, 1).migrate + assert_equal [[:up, 1]], calls + calls.clear + + ActiveRecord::Migrator.new(:up, migrations, 2).migrate + assert_equal [[:up, 2]], calls + end + + def test_migrator_one_down + calls, migrations = sensors(3) + + ActiveRecord::Migrator.new(:up, migrations).migrate + assert_equal [[:up, 1], [:up, 2], [:up, 3]], calls + calls.clear + + ActiveRecord::Migrator.new(:down, migrations, 1).migrate + + assert_equal [[:down, 3], [:down, 2]], calls + end + + def test_migrator_one_up_one_down + calls, migrations = sensors(3) + + ActiveRecord::Migrator.new(:up, migrations, 1).migrate + assert_equal [[:up, 1]], calls + calls.clear + + ActiveRecord::Migrator.new(:down, migrations, 0).migrate + assert_equal [[:down, 1]], calls + end + + def test_migrator_double_up + calls, migrations = sensors(3) + assert_equal(0, ActiveRecord::Migrator.current_version) + + ActiveRecord::Migrator.new(:up, migrations, 1).migrate + assert_equal [[:up, 1]], calls + calls.clear + + ActiveRecord::Migrator.new(:up, migrations, 1).migrate + assert_equal [], calls + end + + def test_migrator_double_down + calls, migrations = sensors(3) + + assert_equal(0, ActiveRecord::Migrator.current_version) + + ActiveRecord::Migrator.new(:up, migrations, 1).run + assert_equal [[:up, 1]], calls + calls.clear + + ActiveRecord::Migrator.new(:down, migrations, 1).run + assert_equal [[:down, 1]], calls + calls.clear + + ActiveRecord::Migrator.new(:down, migrations, 1).run + assert_equal [], calls + + assert_equal(0, ActiveRecord::Migrator.current_version) + end + + def test_migrator_verbosity + _, migrations = sensors(3) + + ActiveRecord::Migrator.new(:up, migrations, 1).migrate + assert_not_equal 0, ActiveRecord::Migration.message_count + + ActiveRecord::Migration.message_count = 0 + + ActiveRecord::Migrator.new(:down, migrations, 0).migrate + assert_not_equal 0, ActiveRecord::Migration.message_count + ActiveRecord::Migration.message_count = 0 + end + + def test_migrator_verbosity_off + _, migrations = sensors(3) + + ActiveRecord::Migration.message_count = 0 + ActiveRecord::Migration.verbose = false + ActiveRecord::Migrator.new(:up, migrations, 1).migrate + assert_equal 0, ActiveRecord::Migration.message_count + ActiveRecord::Migrator.new(:down, migrations, 0).migrate + assert_equal 0, ActiveRecord::Migration.message_count + end + + def test_target_version_zero_should_run_only_once + calls, migrations = sensors(3) + + # migrate up to 1 + ActiveRecord::Migrator.new(:up, migrations, 1).migrate + assert_equal [[:up, 1]], calls + calls.clear + + # migrate down to 0 + ActiveRecord::Migrator.new(:down, migrations, 0).migrate + assert_equal [[:down, 1]], calls + calls.clear + + # migrate down to 0 again + ActiveRecord::Migrator.new(:down, migrations, 0).migrate + assert_equal [], calls + end + + def test_migrator_going_down_due_to_version_target + calls, migrator = migrator_class(3) + + migrator.up("valid", 1) + assert_equal [[:up, 1]], calls + calls.clear + + migrator.migrate("valid", 0) + assert_equal [[:down, 1]], calls + calls.clear + + migrator.migrate("valid") + assert_equal [[:up, 1], [:up, 2], [:up, 3]], calls + end + + def test_migrator_rollback + _, migrator = migrator_class(3) + + migrator.migrate("valid") + assert_equal(3, ActiveRecord::Migrator.current_version) + + migrator.rollback("valid") + assert_equal(2, ActiveRecord::Migrator.current_version) + + migrator.rollback("valid") + assert_equal(1, ActiveRecord::Migrator.current_version) + + migrator.rollback("valid") + assert_equal(0, ActiveRecord::Migrator.current_version) + + migrator.rollback("valid") + assert_equal(0, ActiveRecord::Migrator.current_version) + end + + def test_migrator_db_has_no_schema_migrations_table + _, migrator = migrator_class(3) + + ActiveRecord::Base.connection.execute("DROP TABLE schema_migrations") + refute ActiveRecord::Base.connection.table_exists?('schema_migrations') + migrator.migrate("valid", 1) + assert ActiveRecord::Base.connection.table_exists?('schema_migrations') + end + + def test_migrator_forward + _, migrator = migrator_class(3) + migrator.migrate("/valid", 1) + assert_equal(1, ActiveRecord::Migrator.current_version) + + migrator.forward("/valid", 2) + assert_equal(3, ActiveRecord::Migrator.current_version) + + migrator.forward("/valid") + assert_equal(3, ActiveRecord::Migrator.current_version) + end + + def test_only_loads_pending_migrations + # migrate up to 1 + ActiveRecord::SchemaMigration.create!(:version => '1') + + calls, migrator = migrator_class(3) + migrator.migrate("valid", nil) + + assert_equal [[:up, 2], [:up, 3]], calls + end + + def test_get_all_versions + _, migrator = migrator_class(3) + + migrator.migrate("valid") + assert_equal([1,2,3], ActiveRecord::Migrator.get_all_versions) + + migrator.rollback("valid") + assert_equal([1,2], ActiveRecord::Migrator.get_all_versions) + + migrator.rollback("valid") + assert_equal([1], ActiveRecord::Migrator.get_all_versions) + + migrator.rollback("valid") + assert_equal([], ActiveRecord::Migrator.get_all_versions) + end + + private + def m(name, version, &block) + x = Sensor.new name, version + x.extend(Module.new { + define_method(:up) { block.call(:up, x); super() } + define_method(:down) { block.call(:down, x); super() } + }) if block_given? + end + + def sensors(count) + calls = [] + migrations = count.times.map { |i| + m(nil, i + 1) { |c,migration| + calls << [c, migration.version] + } + } + [calls, migrations] + end + + def migrator_class(count) + calls, migrations = sensors(count) + + migrator = Class.new(Migrator).extend(Module.new { + define_method(:migrations) { |paths| + migrations + } + }) + [calls, migrator] + end + end +end diff --git a/activerecord/test/cases/modules_test.rb b/activerecord/test/cases/modules_test.rb index a2041af16a..a03c4f552e 100644 --- a/activerecord/test/cases/modules_test.rb +++ b/activerecord/test/cases/modules_test.rb @@ -27,19 +27,19 @@ class ModulesTest < ActiveRecord::TestCase end def test_module_spanning_associations - firm = MyApplication::Business::Firm.find(:first) + firm = MyApplication::Business::Firm.first assert !firm.clients.empty?, "Firm should have clients" assert_nil firm.class.table_name.match('::'), "Firm shouldn't have the module appear in its table name" end def test_module_spanning_has_and_belongs_to_many_associations - project = MyApplication::Business::Project.find(:first) + project = MyApplication::Business::Project.first project.developers << MyApplication::Business::Developer.create("name" => "John") assert_equal "John", project.developers.last.name end def test_associations_spanning_cross_modules - account = MyApplication::Billing::Account.find(:first, :order => 'id') + account = MyApplication::Billing::Account.scoped(:order => 'id').first assert_kind_of MyApplication::Business::Firm, account.firm assert_kind_of MyApplication::Billing::Firm, account.qualified_billing_firm assert_kind_of MyApplication::Billing::Firm, account.unqualified_billing_firm @@ -48,7 +48,7 @@ class ModulesTest < ActiveRecord::TestCase end def test_find_account_and_include_company - account = MyApplication::Billing::Account.find(1, :include => :firm) + account = MyApplication::Billing::Account.scoped(:includes => :firm).find(1) assert_kind_of MyApplication::Business::Firm, account.firm end @@ -72,8 +72,8 @@ class ModulesTest < ActiveRecord::TestCase clients = [] assert_nothing_raised NameError, "Should be able to resolve all class constants via reflection" do - clients << MyApplication::Business::Client.find(3, :include => {:firm => :account}, :conditions => 'accounts.id IS NOT NULL') - clients << MyApplication::Business::Client.find(3, :include => {:firm => :account}) + clients << MyApplication::Business::Client.references(:accounts).scoped(:includes => {:firm => :account}, :where => 'accounts.id IS NOT NULL').find(3) + clients << MyApplication::Business::Client.scoped(:includes => {:firm => :account}).find(3) end clients.each do |client| @@ -123,7 +123,7 @@ class ModulesTest < ActiveRecord::TestCase old = ActiveRecord::Base.store_full_sti_class ActiveRecord::Base.store_full_sti_class = true - collection = Shop::Collection.find(:first) + collection = Shop::Collection.first assert !collection.products.empty?, "Collection should have products" assert_nothing_raised { collection.destroy } ensure @@ -134,7 +134,7 @@ class ModulesTest < ActiveRecord::TestCase old = ActiveRecord::Base.store_full_sti_class ActiveRecord::Base.store_full_sti_class = true - product = Shop::Product.find(:first) + product = Shop::Product.first assert !product.variants.empty?, "Product should have variants" assert_nothing_raised { product.destroy } ensure diff --git a/activerecord/test/cases/multiple_db_test.rb b/activerecord/test/cases/multiple_db_test.rb index bd51388e05..06d6596725 100644 --- a/activerecord/test/cases/multiple_db_test.rb +++ b/activerecord/test/cases/multiple_db_test.rb @@ -10,6 +10,7 @@ class MultipleDbTest < ActiveRecord::TestCase def setup @courses = create_fixtures("courses") { Course.retrieve_connection } + @colleges = create_fixtures("colleges") { College.retrieve_connection } @entrants = create_fixtures("entrants") end @@ -85,7 +86,25 @@ class MultipleDbTest < ActiveRecord::TestCase end def test_arel_table_engines + assert_not_equal Entrant.arel_engine, Bird.arel_engine assert_not_equal Entrant.arel_engine, Course.arel_engine - assert_equal Entrant.arel_engine, Bird.arel_engine + end + + def test_connection + assert_equal Entrant.arel_engine.connection, Bird.arel_engine.connection + assert_not_equal Entrant.arel_engine.connection, Course.arel_engine.connection + end + + unless in_memory_db? + def test_associations_should_work_when_model_has_no_connection + begin + ActiveRecord::Model.remove_connection + assert_nothing_raised ActiveRecord::ConnectionNotEstablished do + College.first.courses.first + end + ensure + ActiveRecord::Model.establish_connection 'arunit' + end + end end end diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb index 4a09a87322..2e87c5be2f 100644 --- a/activerecord/test/cases/named_scope_test.rb +++ b/activerecord/test/cases/named_scope_test.rb @@ -1,5 +1,4 @@ require "cases/helper" -require 'active_support/core_ext/array/random_access' require 'models/post' require 'models/topic' require 'models/comment' @@ -11,12 +10,12 @@ class NamedScopeTest < ActiveRecord::TestCase fixtures :posts, :authors, :topics, :comments, :author_addresses def test_implements_enumerable - assert !Topic.find(:all).empty? + assert !Topic.all.empty? - assert_equal Topic.find(:all), Topic.base - assert_equal Topic.find(:all), Topic.base.to_a - assert_equal Topic.find(:first), Topic.base.first - assert_equal Topic.find(:all), Topic.base.map { |i| i } + assert_equal Topic.all, Topic.base + assert_equal Topic.all, Topic.base.to_a + assert_equal Topic.first, Topic.base.first + assert_equal Topic.all, Topic.base.map { |i| i } end def test_found_items_are_cached @@ -39,10 +38,10 @@ class NamedScopeTest < ActiveRecord::TestCase end def test_delegates_finds_and_calculations_to_the_base_class - assert !Topic.find(:all).empty? + assert !Topic.all.empty? - assert_equal Topic.find(:all), Topic.base.find(:all) - assert_equal Topic.find(:first), Topic.base.find(:first) + assert_equal Topic.all, Topic.base.all + assert_equal Topic.first, Topic.base.first assert_equal Topic.count, Topic.base.count assert_equal Topic.average(:replies_count), Topic.base.average(:replies_count) end @@ -59,10 +58,10 @@ class NamedScopeTest < ActiveRecord::TestCase end def test_scopes_with_options_limit_finds_to_those_matching_the_criteria_specified - assert !Topic.find(:all, :conditions => {:approved => true}).empty? + assert !Topic.scoped(:where => {:approved => true}).all.empty? - assert_equal Topic.find(:all, :conditions => {:approved => true}), Topic.approved - assert_equal Topic.count(:conditions => {:approved => true}), Topic.approved.count + assert_equal Topic.scoped(:where => {:approved => true}).all, Topic.approved + assert_equal Topic.where(:approved => true).count, Topic.approved.count end def test_scopes_with_string_name_can_be_composed @@ -71,13 +70,9 @@ class NamedScopeTest < ActiveRecord::TestCase assert_equal Topic.replied.approved, Topic.replied.approved_as_string end - def test_scopes_can_be_specified_with_deep_hash_conditions - assert_equal Topic.replied.approved, Topic.replied.approved_as_hash_condition - end - def test_scopes_are_composable - assert_equal((approved = Topic.find(:all, :conditions => {:approved => true})), Topic.approved) - assert_equal((replied = Topic.find(:all, :conditions => 'replies_count > 0')), Topic.replied) + assert_equal((approved = Topic.scoped(:where => {:approved => true}).all), Topic.approved) + assert_equal((replied = Topic.scoped(:where => 'replies_count > 0').all), Topic.replied) assert !(approved == replied) assert !(approved & replied).empty? @@ -85,8 +80,8 @@ class NamedScopeTest < ActiveRecord::TestCase end def test_procedural_scopes - topics_written_before_the_third = Topic.find(:all, :conditions => ['written_on < ?', topics(:third).written_on]) - topics_written_before_the_second = Topic.find(:all, :conditions => ['written_on < ?', topics(:second).written_on]) + topics_written_before_the_third = Topic.where('written_on < ?', topics(:third).written_on) + topics_written_before_the_second = Topic.where('written_on < ?', topics(:second).written_on) assert_not_equal topics_written_before_the_second, topics_written_before_the_third assert_equal topics_written_before_the_third, Topic.written_before(topics(:third).written_on) @@ -94,46 +89,17 @@ class NamedScopeTest < ActiveRecord::TestCase end def test_procedural_scopes_returning_nil - all_topics = Topic.find(:all) + all_topics = Topic.all assert_equal all_topics, Topic.written_before(nil) end - def test_scopes_with_joins - address = author_addresses(:david_address) - posts_with_authors_at_address = Post.find( - :all, :joins => 'JOIN authors ON authors.id = posts.author_id', - :conditions => [ 'authors.author_address_id = ?', address.id ] - ) - assert_equal posts_with_authors_at_address, Post.with_authors_at_address(address) - end - - def test_scopes_with_joins_respects_custom_select - address = author_addresses(:david_address) - posts_with_authors_at_address_titles = Post.find(:all, - :select => 'title', - :joins => 'JOIN authors ON authors.id = posts.author_id', - :conditions => [ 'authors.author_address_id = ?', address.id ] - ) - assert_equal posts_with_authors_at_address_titles.map(&:title), Post.with_authors_at_address(address).find(:all, :select => 'title').map(&:title) - end - def test_scope_with_object objects = Topic.with_object assert_operator objects.length, :>, 0 assert objects.all?(&:approved?), 'all objects should be approved' end - def test_extensions - assert_equal 1, Topic.anonymous_extension.one - assert_equal 2, Topic.named_extension.two - end - - def test_multiple_extensions - assert_equal 2, Topic.multiple_extensions.extension_two - assert_equal 1, Topic.multiple_extensions.extension_one - end - def test_has_many_associations_have_access_to_scopes assert_not_equal Post.containing_the_letter_a, authors(:david).posts assert !Post.containing_the_letter_a.empty? @@ -164,20 +130,16 @@ class NamedScopeTest < ActiveRecord::TestCase end def test_active_records_have_scope_named__all__ - assert !Topic.find(:all).empty? + assert !Topic.all.empty? - assert_equal Topic.find(:all), Topic.base + assert_equal Topic.all, Topic.base end def test_active_records_have_scope_named__scoped__ - assert !Topic.find(:all, scope = {:conditions => "content LIKE '%Have%'"}).empty? - - assert_equal Topic.find(:all, scope), Topic.scoped(scope) - end + scope = Topic.where("content LIKE '%Have%'") + assert !scope.empty? - def test_first_and_last_should_support_find_options - assert_equal Topic.base.first(:order => 'title'), Topic.base.find(:first, :order => 'title') - assert_equal Topic.base.last(:order => 'title'), Topic.base.find(:last, :order => 'title') + assert_equal scope, Topic.scoped(where: "content LIKE '%Have%'") end def test_first_and_last_should_allow_integers_for_limit @@ -194,15 +156,6 @@ class NamedScopeTest < ActiveRecord::TestCase end end - def test_first_and_last_find_options_should_use_query_when_results_are_loaded - topics = Topic.base - topics.reload # force load - assert_queries(2) do - topics.first(:order => 'title') - topics.last(:order => 'title') - end - end - def test_empty_should_not_load_results topics = Topic.base assert_queries(2) do @@ -265,9 +218,9 @@ class NamedScopeTest < ActiveRecord::TestCase end def test_many_should_return_false_if_none_or_one - topics = Topic.base.scoped(:conditions => {:id => 0}) + topics = Topic.base.where(:id => 0) assert !topics.many? - topics = Topic.base.scoped(:conditions => {:id => 1}) + topics = Topic.base.where(:id => 1) assert !topics.many? end @@ -337,10 +290,15 @@ class NamedScopeTest < ActiveRecord::TestCase end end + def test_should_not_duplicates_where_values + where_values = Topic.where("1=1").scope_with_lambda.where_values + assert_equal ["1=1"], where_values + end + def test_chaining_with_duplicate_joins join = "INNER JOIN comments ON comments.post_id = posts.id" post = Post.find(1) - assert_equal post.comments.size, Post.scoped(:joins => join).scoped(:joins => join, :conditions => "posts.id = #{post.id}").size + assert_equal post.comments.size, Post.joins(join).joins(join).where("posts.id = #{post.id}").size end def test_chaining_should_use_latest_conditions_when_creating @@ -389,25 +347,6 @@ class NamedScopeTest < ActiveRecord::TestCase end end - def test_scopes_with_reserved_names - class << Topic - def public_method; end - public :public_method - - def protected_method; end - protected :protected_method - - def private_method; end - private :private_method - end - - [:public_method, :protected_method, :private_method].each do |reserved_method| - assert Topic.respond_to?(reserved_method, true) - ActiveRecord::Base.logger.expects(:warn) - Topic.scope(reserved_method) - end - end - def test_scopes_on_relations # Topic.replied approved_topics = Topic.scoped.approved.order('id DESC') @@ -479,17 +418,36 @@ class NamedScopeTest < ActiveRecord::TestCase require "models/without_table" end end + + def test_eager_scopes_are_deprecated + klass = Class.new(ActiveRecord::Base) + klass.table_name = 'posts' + + assert_deprecated do + klass.scope :welcome_2, klass.where(:id => posts(:welcome).id) + end + assert_equal [posts(:welcome).title], klass.welcome_2.map(&:title) + end + + def test_eager_default_scope_relations_are_deprecated + klass = Class.new(ActiveRecord::Base) + klass.table_name = 'posts' + + assert_deprecated do + klass.send(:default_scope, klass.where(:id => posts(:welcome).id)) + end + assert_equal [posts(:welcome).title], klass.all.map(&:title) + end end class DynamicScopeMatchTest < ActiveRecord::TestCase def test_scoped_by_no_match - assert_nil ActiveRecord::DynamicScopeMatch.match("not_scoped_at_all") + assert_nil ActiveRecord::DynamicMatchers::Method.match(nil, "not_scoped_at_all") end def test_scoped_by - match = ActiveRecord::DynamicScopeMatch.match("scoped_by_age_and_sex_and_location") + match = ActiveRecord::DynamicMatchers::Method.match(nil, "scoped_by_age_and_sex_and_location") assert_not_nil match - assert match.scope? assert_equal %w(age sex location), match.attribute_names end end @@ -505,7 +463,7 @@ class DynamicScopeTest < ActiveRecord::TestCase def test_dynamic_scope assert_equal @test_klass.scoped_by_author_id(1).find(1), @test_klass.find(1) - assert_equal @test_klass.scoped_by_author_id_and_title(1, "Welcome to the weblog").first, @test_klass.find(:first, :conditions => { :author_id => 1, :title => "Welcome to the weblog"}) + assert_equal @test_klass.scoped_by_author_id_and_title(1, "Welcome to the weblog").first, @test_klass.scoped(:where => { :author_id => 1, :title => "Welcome to the weblog"}).first end def test_dynamic_scope_should_create_methods_after_hitting_method_missing diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb index 2ae9cb4888..0559bbbe9a 100644 --- a/activerecord/test/cases/nested_attributes_test.rb +++ b/activerecord/test/cases/nested_attributes_test.rb @@ -172,6 +172,19 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase man.interests_attributes = [{:id => interest.id, :topic => 'gardening'}] assert_equal man.interests.first.topic, man.interests[0].topic end + + def test_allows_class_to_override_setter_and_call_super + mean_pirate_class = Class.new(Pirate) do + accepts_nested_attributes_for :parrot + def parrot_attributes=(attrs) + super(attrs.merge(:color => "blue")) + end + end + mean_pirate = mean_pirate_class.new + mean_pirate.parrot_attributes = { :name => "James" } + assert_equal "James", mean_pirate.parrot.name + assert_equal "blue", mean_pirate.parrot.color + end end class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase @@ -663,7 +676,7 @@ module NestedAttributesOnACollectionAssociationTests end def test_should_sort_the_hash_by_the_keys_before_building_new_associated_models - attributes = ActiveSupport::OrderedHash.new + attributes = {} attributes['123726353'] = { :name => 'Grace OMalley' } attributes['2'] = { :name => 'Privateers Greed' } # 2 is lower then 123726353 @pirate.send(association_setter, attributes) @@ -673,7 +686,7 @@ module NestedAttributesOnACollectionAssociationTests def test_should_raise_an_argument_error_if_something_else_than_a_hash_is_passed assert_nothing_raised(ArgumentError) { @pirate.send(association_setter, {}) } - assert_nothing_raised(ArgumentError) { @pirate.send(association_setter, ActiveSupport::OrderedHash.new) } + assert_nothing_raised(ArgumentError) { @pirate.send(association_setter, Hash.new) } assert_raise_with_message ArgumentError, 'Hash or Array expected, got String ("foo")' do @pirate.send(association_setter, "foo") diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb index adfd8e83a1..0933a4ff3d 100644 --- a/activerecord/test/cases/persistence_test.rb +++ b/activerecord/test/cases/persistence_test.rb @@ -13,12 +13,14 @@ require 'models/warehouse_thing' require 'models/parrot' require 'models/minivan' require 'models/person' +require 'models/pet' +require 'models/toy' require 'rexml/document' require 'active_support/core_ext/exception' class PersistencesTest < ActiveRecord::TestCase - fixtures :topics, :companies, :developers, :projects, :computers, :accounts, :minimalistics, 'warehouse-things', :authors, :categorizations, :categories, :posts, :minivans + fixtures :topics, :companies, :developers, :projects, :computers, :accounts, :minimalistics, 'warehouse-things', :authors, :categorizations, :categories, :posts, :minivans, :pets, :toys # Oracle UPDATE does not support ORDER BY unless current_adapter?(:OracleAdapter) @@ -53,7 +55,7 @@ class PersistencesTest < ActiveRecord::TestCase author = authors(:david) assert_nothing_raised do assert_equal 1, author.posts_sorted_by_id_limited.size - assert_equal 2, author.posts_sorted_by_id_limited.find(:all, :limit => 2).size + assert_equal 2, author.posts_sorted_by_id_limited.scoped(:limit => 2).all.size assert_equal 1, author.posts_sorted_by_id_limited.update_all([ "body = ?", "bulk update!" ]) assert_equal "bulk update!", posts(:welcome).body assert_not_equal "bulk update!", posts(:thinking).body @@ -76,10 +78,20 @@ class PersistencesTest < ActiveRecord::TestCase assert_equal Topic.count, Topic.delete_all end - def test_update_by_condition - Topic.update_all "content = 'bulk updated!'", ["approved = ?", true] - assert_equal "Have a nice day", Topic.find(1).content - assert_equal "bulk updated!", Topic.find(2).content + def test_delete_all_with_joins_and_where_part_is_hash + where_args = {:toys => {:name => 'Bone'}} + count = Pet.joins(:toys).where(where_args).count + + assert_equal count, 1 + assert_equal count, Pet.joins(:toys).where(where_args).delete_all + end + + def test_delete_all_with_joins_and_where_part_is_not_hash + where_args = ['toys.name = ?', 'Bone'] + count = Pet.joins(:toys).where(where_args).count + + assert_equal count, 1 + assert_equal count, Pet.joins(:toys).where(where_args).delete_all end def test_increment_attribute @@ -108,7 +120,7 @@ class PersistencesTest < ActiveRecord::TestCase def test_destroy_all conditions = "author_name = 'Mary'" - topics_by_mary = Topic.all(:conditions => conditions, :order => 'id') + topics_by_mary = Topic.scoped(:where => conditions, :order => 'id').to_a assert ! topics_by_mary.empty? assert_difference('Topic.count', -topics_by_mary.size) do @@ -119,7 +131,7 @@ class PersistencesTest < ActiveRecord::TestCase end def test_destroy_many - clients = Client.find([2, 3], :order => 'id') + clients = Client.scoped(:order => 'id').find([2, 3]) assert_difference('Client.count', -2) do destroyed = Client.destroy([2, 3]).sort_by(&:id) @@ -320,7 +332,7 @@ class PersistencesTest < ActiveRecord::TestCase end def test_update_all_with_non_standard_table_name - assert_equal 1, WarehouseThing.update_all(['value = ?', 0], ['id = ?', 1]) + assert_equal 1, WarehouseThing.where(id: 1).update_all(['value = ?', 0]) assert_equal 0, WarehouseThing.find(1).value end @@ -393,7 +405,6 @@ class PersistencesTest < ActiveRecord::TestCase def test_update_attribute_with_one_updated t = Topic.first - title = t.title t.update_attribute(:title, 'super_title') assert_equal 'super_title', t.title assert !t.changed?, "topic should not have changed" diff --git a/activerecord/test/cases/pooled_connections_test.rb b/activerecord/test/cases/pooled_connections_test.rb index bc3dfb1078..fba3006ebe 100644 --- a/activerecord/test/cases/pooled_connections_test.rb +++ b/activerecord/test/cases/pooled_connections_test.rb @@ -7,17 +7,17 @@ class PooledConnectionsTest < ActiveRecord::TestCase def setup @per_test_teardown = [] - @connection = ActiveRecord::Base.remove_connection + @connection = ActiveRecord::Model.remove_connection end def teardown - ActiveRecord::Base.clear_all_connections! - ActiveRecord::Base.establish_connection(@connection) + ActiveRecord::Model.clear_all_connections! + ActiveRecord::Model.establish_connection(@connection) @per_test_teardown.each {|td| td.call } end def checkout_connections - ActiveRecord::Base.establish_connection(@connection.merge({:pool => 2, :wait_timeout => 0.3})) + ActiveRecord::Model.establish_connection(@connection.merge({:pool => 2, :wait_timeout => 0.3})) @connections = [] @timed_out = 0 @@ -33,24 +33,16 @@ class PooledConnectionsTest < ActiveRecord::TestCase end # Will deadlock due to lack of Monitor timeouts in 1.9 - if RUBY_VERSION < '1.9' - def test_pooled_connection_checkout - checkout_connections - assert_equal 2, @connections.length - assert_equal 2, @timed_out - end - end - def checkout_checkin_connections(pool_size, threads) - ActiveRecord::Base.establish_connection(@connection.merge({:pool => pool_size, :wait_timeout => 0.5})) + ActiveRecord::Model.establish_connection(@connection.merge({:pool => pool_size, :wait_timeout => 0.5})) @connection_count = 0 @timed_out = 0 threads.times do Thread.new do begin - conn = ActiveRecord::Base.connection_pool.checkout + conn = ActiveRecord::Model.connection_pool.checkout sleep 0.1 - ActiveRecord::Base.connection_pool.checkin conn + ActiveRecord::Model.connection_pool.checkin conn @connection_count += 1 rescue ActiveRecord::ConnectionTimeoutError @timed_out += 1 @@ -63,13 +55,13 @@ class PooledConnectionsTest < ActiveRecord::TestCase checkout_checkin_connections 1, 2 assert_equal 2, @connection_count assert_equal 0, @timed_out - assert_equal 1, ActiveRecord::Base.connection_pool.connections.size + assert_equal 1, ActiveRecord::Model.connection_pool.connections.size end private def add_record(name) - ActiveRecord::Base.connection_pool.with_connection { Project.create! :name => name } + ActiveRecord::Model.connection_pool.with_connection { Project.create! :name => name } end end unless current_adapter?(:FrontBase) || in_memory_db? diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb index 4bb5752096..e6e50a4cd4 100644 --- a/activerecord/test/cases/primary_keys_test.rb +++ b/activerecord/test/cases/primary_keys_test.rb @@ -23,6 +23,11 @@ class PrimaryKeysTest < ActiveRecord::TestCase assert_equal keyboard.to_key, [keyboard.id] end + def test_read_attribute_with_custom_primary_key + keyboard = Keyboard.create! + assert_equal keyboard.key_number, keyboard.read_attribute(:id) + end + def test_to_key_with_primary_key_after_destroy topic = Topic.find(1) topic.destroy @@ -142,8 +147,38 @@ class PrimaryKeysTest < ActiveRecord::TestCase assert_equal k.connection.quote_column_name("id"), k.quoted_primary_key k.primary_key = "foo" assert_equal k.connection.quote_column_name("foo"), k.quoted_primary_key - k.set_primary_key "bar" - assert_equal k.connection.quote_column_name("bar"), k.quoted_primary_key + end + + def test_two_models_with_same_table_but_different_primary_key + k1 = Class.new(ActiveRecord::Base) + k1.table_name = 'posts' + k1.primary_key = 'id' + + k2 = Class.new(ActiveRecord::Base) + k2.table_name = 'posts' + k2.primary_key = 'title' + + assert k1.columns.find { |c| c.name == 'id' }.primary + assert !k1.columns.find { |c| c.name == 'title' }.primary + assert k1.columns_hash['id'].primary + assert !k1.columns_hash['title'].primary + + assert !k2.columns.find { |c| c.name == 'id' }.primary + assert k2.columns.find { |c| c.name == 'title' }.primary + assert !k2.columns_hash['id'].primary + assert k2.columns_hash['title'].primary + end + + def test_models_with_same_table_have_different_columns + k1 = Class.new(ActiveRecord::Base) + k1.table_name = 'posts' + + k2 = Class.new(ActiveRecord::Base) + k2.table_name = 'posts' + + k1.columns.zip(k2.columns).each do |col1, col2| + assert !col1.equal?(col2) + end end end @@ -153,16 +188,31 @@ class PrimaryKeyWithNoConnectionTest < ActiveRecord::TestCase def test_set_primary_key_with_no_connection return skip("disconnect wipes in-memory db") if in_memory_db? - connection = ActiveRecord::Base.remove_connection + connection = ActiveRecord::Model.remove_connection - model = Class.new(ActiveRecord::Base) do - set_primary_key 'foo' - end + model = Class.new(ActiveRecord::Base) + model.primary_key = 'foo' assert_equal 'foo', model.primary_key - ActiveRecord::Base.establish_connection(connection) + ActiveRecord::Model.establish_connection(connection) assert_equal 'foo', model.primary_key end end + +if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter) + class PrimaryKeyWithAnsiQuotesTest < ActiveRecord::TestCase + self.use_transactional_fixtures = false + + def test_primaery_key_method_with_ansi_quotes + con = ActiveRecord::Base.connection + con.execute("SET SESSION sql_mode='ANSI_QUOTES'") + assert_equal "id", con.primary_key("topics") + ensure + con.reconnect! + end + + end +end + diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb index 9554386dcf..a712e5f689 100644 --- a/activerecord/test/cases/query_cache_test.rb +++ b/activerecord/test/cases/query_cache_test.rb @@ -3,7 +3,7 @@ require 'models/topic' require 'models/task' require 'models/category' require 'models/post' - +require 'rack' class QueryCacheTest < ActiveRecord::TestCase fixtures :tasks, :topics, :categories, :posts, :categories_posts @@ -43,6 +43,7 @@ class QueryCacheTest < ActiveRecord::TestCase called = false mw = ActiveRecord::QueryCache.new lambda { |env| called = true + [200, {}, nil] } mw.call({}) assert called, 'middleware should delegate' @@ -53,6 +54,7 @@ class QueryCacheTest < ActiveRecord::TestCase Task.find 1 Task.find 1 assert_equal 1, ActiveRecord::Base.connection.query_cache.length + [200, {}, nil] } mw.call({}) end @@ -62,6 +64,7 @@ class QueryCacheTest < ActiveRecord::TestCase mw = ActiveRecord::QueryCache.new lambda { |env| assert ActiveRecord::Base.connection.query_cache_enabled, 'cache on' + [200, {}, nil] } mw.call({}) end @@ -83,7 +86,7 @@ class QueryCacheTest < ActiveRecord::TestCase end def test_cache_off_after_close - mw = ActiveRecord::QueryCache.new lambda { |env| } + mw = ActiveRecord::QueryCache.new lambda { |env| [200, {}, nil] } body = mw.call({}).last assert ActiveRecord::Base.connection.query_cache_enabled, 'cache enabled' @@ -93,7 +96,8 @@ class QueryCacheTest < ActiveRecord::TestCase def test_cache_clear_after_close mw = ActiveRecord::QueryCache.new lambda { |env| - Post.find(:first) + Post.first + [200, {}, nil] } body = mw.call({}).last @@ -103,7 +107,7 @@ class QueryCacheTest < ActiveRecord::TestCase end def test_find_queries - assert_queries(ActiveRecord::IdentityMap.enabled? ? 1 : 2) { Task.find(1); Task.find(1) } + assert_queries(2) { Task.find(1); Task.find(1) } end def test_find_queries_with_cache @@ -229,8 +233,8 @@ class QueryCacheExpiryTest < ActiveRecord::TestCase def test_cache_is_expired_by_habtm_update ActiveRecord::Base.connection.expects(:clear_query_cache).times(2) ActiveRecord::Base.cache do - c = Category.find(:first) - p = Post.find(:first) + c = Category.first + p = Post.first p.categories << c end end @@ -244,14 +248,3 @@ class QueryCacheExpiryTest < ActiveRecord::TestCase end end end - -class QueryCacheBodyProxyTest < ActiveRecord::TestCase - - test "is polite to it's body and responds to it" do - body = Class.new(String) { def to_path; "/path"; end }.new - proxy = ActiveRecord::QueryCache::BodyProxy.new(nil, body, ActiveRecord::Base.connection_id) - assert proxy.respond_to?(:to_path) - assert_equal proxy.to_path, "/path" - end - -end diff --git a/activerecord/test/cases/readonly_test.rb b/activerecord/test/cases/readonly_test.rb index e21109baae..39b66b3399 100644 --- a/activerecord/test/cases/readonly_test.rb +++ b/activerecord/test/cases/readonly_test.rb @@ -27,7 +27,7 @@ class ReadOnlyTest < ActiveRecord::TestCase def test_find_with_readonly_option - Developer.find(:all).each { |d| assert !d.readonly? } + Developer.all.each { |d| assert !d.readonly? } Developer.readonly(false).each { |d| assert !d.readonly? } Developer.readonly(true).each { |d| assert d.readonly? } Developer.readonly.each { |d| assert d.readonly? } @@ -48,7 +48,7 @@ class ReadOnlyTest < ActiveRecord::TestCase post = Post.find(1) assert !post.comments.empty? assert !post.comments.any?(&:readonly?) - assert !post.comments.find(:all).any?(&:readonly?) + assert !post.comments.all.any?(&:readonly?) assert post.comments.readonly(true).all?(&:readonly?) end @@ -70,13 +70,13 @@ class ReadOnlyTest < ActiveRecord::TestCase end def test_readonly_scoping - Post.send(:with_scope, :find => { :conditions => '1=1' }) do + Post.where('1=1').scoped do assert !Post.find(1).readonly? assert Post.readonly(true).find(1).readonly? assert !Post.readonly(false).find(1).readonly? end - Post.send(:with_scope, :find => { :joins => ' ' }) do + Post.joins(' ').scoped do assert !Post.find(1).readonly? assert Post.readonly.find(1).readonly? assert !Post.readonly(false).find(1).readonly? @@ -85,14 +85,14 @@ class ReadOnlyTest < ActiveRecord::TestCase # Oracle barfs on this because the join includes unqualified and # conflicting column names unless current_adapter?(:OracleAdapter) - Post.send(:with_scope, :find => { :joins => ', developers' }) do + Post.joins(', developers').scoped do assert Post.find(1).readonly? assert Post.readonly.find(1).readonly? assert !Post.readonly(false).find(1).readonly? end end - Post.send(:with_scope, :find => { :readonly => true }) do + Post.readonly(true).scoped do assert Post.find(1).readonly? assert Post.readonly.find(1).readonly? assert !Post.readonly(false).find(1).readonly? diff --git a/activerecord/test/cases/reaper_test.rb b/activerecord/test/cases/reaper_test.rb new file mode 100644 index 0000000000..576ab60090 --- /dev/null +++ b/activerecord/test/cases/reaper_test.rb @@ -0,0 +1,81 @@ +require "cases/helper" + +module ActiveRecord + module ConnectionAdapters + class ReaperTest < ActiveRecord::TestCase + attr_reader :pool + + def setup + super + @pool = ConnectionPool.new ActiveRecord::Base.connection_pool.spec + end + + def teardown + super + @pool.connections.each(&:close) + end + + class FakePool + attr_reader :reaped + + def initialize + @reaped = false + end + + def reap + @reaped = true + end + end + + # A reaper with nil time should never reap connections + def test_nil_time + fp = FakePool.new + assert !fp.reaped + reaper = ConnectionPool::Reaper.new(fp, nil) + reaper.run + assert !fp.reaped + end + + def test_some_time + fp = FakePool.new + assert !fp.reaped + + reaper = ConnectionPool::Reaper.new(fp, 0.0001) + reaper.run + until fp.reaped + Thread.pass + end + assert fp.reaped + end + + def test_pool_has_reaper + assert pool.reaper + end + + def test_reaping_frequency_configuration + spec = ActiveRecord::Base.connection_pool.spec.dup + spec.config[:reaping_frequency] = 100 + pool = ConnectionPool.new spec + assert_equal 100, pool.reaper.frequency + end + + def test_connection_pool_starts_reaper + spec = ActiveRecord::Base.connection_pool.spec.dup + spec.config[:reaping_frequency] = 0.0001 + + pool = ConnectionPool.new spec + pool.timeout = 0 + + conn = pool.checkout + count = pool.connections.length + + conn.extend(Module.new { def active?; false; end; }) + + while count == pool.connections.length + Thread.pass + end + assert_equal(count - 1, pool.connections.length) + end + end + end +end diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb index b30db542a7..7dd5698dcf 100644 --- a/activerecord/test/cases/reflection_test.rb +++ b/activerecord/test/cases/reflection_test.rb @@ -36,25 +36,25 @@ class ReflectionTest < ActiveRecord::TestCase def test_read_attribute_names assert_equal( - %w( id title author_name author_email_address bonus_time written_on last_read content group approved replies_count parent_id parent_title type created_at updated_at ).sort, + %w( id title author_name author_email_address bonus_time written_on last_read content important group approved replies_count parent_id parent_title type created_at updated_at ).sort, @first.attribute_names.sort ) end def test_columns - assert_equal 16, Topic.columns.length + assert_equal 17, Topic.columns.length end def test_columns_are_returned_in_the_order_they_were_declared column_names = Topic.columns.map { |column| column.name } - assert_equal %w(id title author_name author_email_address written_on bonus_time last_read content approved replies_count parent_id parent_title type group created_at updated_at), column_names + assert_equal %w(id title author_name author_email_address written_on bonus_time last_read content important approved replies_count parent_id parent_title type group created_at updated_at), column_names end def test_content_columns content_columns = Topic.content_columns content_column_names = content_columns.map {|column| column.name} - assert_equal 12, content_columns.length - assert_equal %w(title author_name author_email_address written_on bonus_time last_read content group approved parent_title created_at updated_at).sort, content_column_names.sort + assert_equal 13, content_columns.length + assert_equal %w(title author_name author_email_address written_on bonus_time last_read content important group approved parent_title created_at updated_at).sort, content_column_names.sort end def test_column_string_type_and_limit @@ -63,7 +63,7 @@ class ReflectionTest < ActiveRecord::TestCase end def test_column_null_not_null - subscriber = Subscriber.find(:first) + subscriber = Subscriber.first assert subscriber.column_for_attribute("name").null assert !subscriber.column_for_attribute("nick").null end @@ -189,8 +189,8 @@ class ReflectionTest < ActiveRecord::TestCase def test_reflection_of_all_associations # FIXME these assertions bust a lot - assert_equal 37, Firm.reflect_on_all_associations.size - assert_equal 27, Firm.reflect_on_all_associations(:has_many).size + assert_equal 39, Firm.reflect_on_all_associations.size + assert_equal 29, Firm.reflect_on_all_associations(:has_many).size assert_equal 10, Firm.reflect_on_all_associations(:has_one).size assert_equal 0, Firm.reflect_on_all_associations(:belongs_to).size end diff --git a/activerecord/test/cases/relation_scoping_test.rb b/activerecord/test/cases/relation_scoping_test.rb index 1e2093273e..342f7a86fa 100644 --- a/activerecord/test/cases/relation_scoping_test.rb +++ b/activerecord/test/cases/relation_scoping_test.rb @@ -53,8 +53,8 @@ class RelationScopingTest < ActiveRecord::TestCase end def test_scoped_find_last_preserves_scope - lowest_salary = Developer.first :order => "salary ASC" - highest_salary = Developer.first :order => "salary DESC" + lowest_salary = Developer.order("salary ASC").first + highest_salary = Developer.order("salary DESC").first Developer.order("salary").scoping do assert_equal highest_salary, Developer.last @@ -106,7 +106,7 @@ class RelationScopingTest < ActiveRecord::TestCase def test_scoped_find_include # with the include, will retrieve only developers for the given project scoped_developers = Developer.includes(:projects).scoping do - Developer.where('projects.id = 2').all + Developer.where('projects.id' => 2).all end assert scoped_developers.include?(developers(:david)) assert !scoped_developers.include?(developers(:jamis)) @@ -323,8 +323,8 @@ class DefaultScopingTest < ActiveRecord::TestCase fixtures :developers, :posts def test_default_scope - expected = Developer.find(:all, :order => 'salary DESC').collect { |dev| dev.salary } - received = DeveloperOrderedBySalary.find(:all).collect { |dev| dev.salary } + expected = Developer.scoped(:order => 'salary DESC').all.collect { |dev| dev.salary } + received = DeveloperOrderedBySalary.all.collect { |dev| dev.salary } assert_equal expected, received end @@ -362,12 +362,12 @@ class DefaultScopingTest < ActiveRecord::TestCase end def test_default_scope_with_conditions_string - assert_equal Developer.find_all_by_name('David').map(&:id).sort, DeveloperCalledDavid.find(:all).map(&:id).sort + assert_equal Developer.find_all_by_name('David').map(&:id).sort, DeveloperCalledDavid.all.map(&:id).sort assert_equal nil, DeveloperCalledDavid.create!.name end def test_default_scope_with_conditions_hash - assert_equal Developer.find_all_by_name('Jamis').map(&:id).sort, DeveloperCalledJamis.find(:all).map(&:id).sort + assert_equal Developer.find_all_by_name('Jamis').map(&:id).sort, DeveloperCalledJamis.all.map(&:id).sort assert_equal 'Jamis', DeveloperCalledJamis.create!.name end @@ -395,23 +395,9 @@ class DefaultScopingTest < ActiveRecord::TestCase assert_equal 50000, wheres[:salary] end - def test_method_scope - expected = Developer.find(:all, :order => 'salary DESC, name DESC').collect { |dev| dev.salary } - received = DeveloperOrderedBySalary.all_ordered_by_name.collect { |dev| dev.salary } - assert_equal expected, received - end - - def test_nested_scope - expected = Developer.find(:all, :order => 'salary DESC, name DESC').collect { |dev| dev.salary } - received = DeveloperOrderedBySalary.send(:with_scope, :find => { :order => 'name DESC'}) do - DeveloperOrderedBySalary.find(:all).collect { |dev| dev.salary } - end - assert_equal expected, received - end - def test_scope_overwrites_default - expected = Developer.find(:all, :order => 'salary DESC, name DESC').collect { |dev| dev.name } - received = DeveloperOrderedBySalary.by_name.find(:all).collect { |dev| dev.name } + expected = Developer.scoped(:order => 'salary DESC, name DESC').all.collect { |dev| dev.name } + received = DeveloperOrderedBySalary.by_name.all.collect { |dev| dev.name } assert_equal expected, received end @@ -421,17 +407,15 @@ class DefaultScopingTest < ActiveRecord::TestCase assert_equal expected, received end - def test_nested_exclusive_scope - expected = Developer.find(:all, :limit => 100).collect { |dev| dev.salary } - received = DeveloperOrderedBySalary.send(:with_exclusive_scope, :find => { :limit => 100 }) do - DeveloperOrderedBySalary.find(:all).collect { |dev| dev.salary } - end + def test_order_after_reorder_combines_orders + expected = Developer.order('name DESC, id DESC').collect { |dev| [dev.name, dev.id] } + received = Developer.order('name ASC').reorder('name DESC').order('id DESC').collect { |dev| [dev.name, dev.id] } assert_equal expected, received end def test_order_in_default_scope_should_prevail - expected = Developer.find(:all, :order => 'salary desc').collect { |dev| dev.salary } - received = DeveloperOrderedBySalary.find(:all, :order => 'salary').collect { |dev| dev.salary } + expected = Developer.scoped(:order => 'salary desc').all.collect { |dev| dev.salary } + received = DeveloperOrderedBySalary.scoped(:order => 'salary').all.collect { |dev| dev.salary } assert_equal expected, received end diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb index 715a378431..8a7a2441d4 100644 --- a/activerecord/test/cases/relation_test.rb +++ b/activerecord/test/cases/relation_test.rb @@ -19,33 +19,12 @@ module ActiveRecord assert !relation.loaded, 'relation is not loaded' end - def test_single_values - assert_equal [:limit, :offset, :lock, :readonly, :from, :reorder, :reverse_order, :uniq].map(&:to_s).sort, - Relation::SINGLE_VALUE_METHODS.map(&:to_s).sort - end - def test_initialize_single_values relation = Relation.new :a, :b - Relation::SINGLE_VALUE_METHODS.each do |method| + (Relation::SINGLE_VALUE_METHODS - [:create_with]).each do |method| assert_nil relation.send("#{method}_value"), method.to_s end - end - - def test_association_methods - assert_equal [:includes, :eager_load, :preload].map(&:to_s).sort, - Relation::ASSOCIATION_METHODS.map(&:to_s).sort - end - - def test_initialize_association_methods - relation = Relation.new :a, :b - Relation::ASSOCIATION_METHODS.each do |method| - assert_equal [], relation.send("#{method}_values"), method.to_s - end - end - - def test_multi_value_methods - assert_equal [:select, :group, :order, :joins, :where, :having, :bind].map(&:to_s).sort, - Relation::MULTI_VALUE_METHODS.map(&:to_s).sort + assert_equal({}, relation.create_with_value) end def test_multi_value_initialize @@ -64,19 +43,19 @@ module ActiveRecord relation = Relation.new :a, :b assert_equal({}, relation.where_values_hash) - relation.where_values << :hello + relation.where! :hello assert_equal({}, relation.where_values_hash) end def test_has_values relation = Relation.new Post, Post.arel_table - relation.where_values << relation.table[:id].eq(10) + relation.where! relation.table[:id].eq(10) assert_equal({:id => 10}, relation.where_values_hash) end def test_values_wrong_table relation = Relation.new Post, Post.arel_table - relation.where_values << Comment.arel_table[:id].eq(10) + relation.where! Comment.arel_table[:id].eq(10) assert_equal({}, relation.where_values_hash) end @@ -85,7 +64,7 @@ module ActiveRecord left = relation.table[:id].eq(10) right = relation.table[:id].eq(10) combine = left.and right - relation.where_values << combine + relation.where! combine assert_equal({}, relation.where_values_hash) end @@ -108,7 +87,7 @@ module ActiveRecord def test_create_with_value_with_wheres relation = Relation.new Post, Post.arel_table - relation.where_values << relation.table[:id].eq(10) + relation.where! relation.table[:id].eq(10) relation.create_with_value = {:hello => 'world'} assert_equal({:hello => 'world', :id => 10}, relation.scope_for_create) end @@ -118,7 +97,7 @@ module ActiveRecord relation = Relation.new Post, Post.arel_table assert_equal({}, relation.scope_for_create) - relation.where_values << relation.table[:id].eq(10) + relation.where! relation.table[:id].eq(10) assert_equal({}, relation.scope_for_create) relation.create_with_value = {:hello => 'world'} @@ -132,8 +111,133 @@ module ActiveRecord def test_eager_load_values relation = Relation.new :a, :b - relation.eager_load_values << :b + relation.eager_load! :b assert relation.eager_loading? end + + def test_references_values + relation = Relation.new :a, :b + assert_equal [], relation.references_values + relation = relation.references(:foo).references(:omg, :lol) + assert_equal ['foo', 'omg', 'lol'], relation.references_values + end + + def test_references_values_dont_duplicate + relation = Relation.new :a, :b + relation = relation.references(:foo).references(:foo) + assert_equal ['foo'], relation.references_values + end + + test 'merging a hash into a relation' do + relation = Relation.new :a, :b + relation = relation.merge where: :lol, readonly: true + + assert_equal [:lol], relation.where_values + assert_equal true, relation.readonly_value + end + + test 'merging an empty hash into a relation' do + assert_equal [], Relation.new(:a, :b).merge({}).where_values + end + + test 'merging a hash with unknown keys raises' do + assert_raises(ArgumentError) { Relation::HashMerger.new(nil, omg: 'lol') } + end + + test '#values returns a dup of the values' do + relation = Relation.new(:a, :b).where! :foo + values = relation.values + + values[:where] = nil + assert_not_nil relation.where_values + end + + test 'relations can be created with a values hash' do + relation = Relation.new(:a, :b, where: [:foo]) + assert_equal [:foo], relation.where_values + end + + test 'merging a single where value' do + relation = Relation.new(:a, :b) + relation.merge!(where: :foo) + assert_equal [:foo], relation.where_values + end + + test 'merging a hash interpolates conditions' do + klass = stub + klass.stubs(:sanitize_sql).with(['foo = ?', 'bar']).returns('foo = bar') + + relation = Relation.new(klass, :b) + relation.merge!(where: ['foo = ?', 'bar']) + assert_equal ['foo = bar'], relation.where_values + end + end + + class RelationMutationTest < ActiveSupport::TestCase + def relation + @relation ||= Relation.new :a, :b + end + + (Relation::MULTI_VALUE_METHODS - [:references, :extending]).each do |method| + test "##{method}!" do + assert relation.public_send("#{method}!", :foo).equal?(relation) + assert_equal [:foo], relation.public_send("#{method}_values") + end + end + + test '#references!' do + assert relation.references!(:foo).equal?(relation) + assert relation.references_values.include?('foo') + end + + test 'extending!' do + mod = Module.new + + assert relation.extending!(mod).equal?(relation) + assert [mod], relation.extending_values + assert relation.is_a?(mod) + end + + test 'extending! with empty args' do + relation.extending! + assert_equal [], relation.extending_values + end + + (Relation::SINGLE_VALUE_METHODS - [:lock, :reordering, :reverse_order, :create_with]).each do |method| + test "##{method}!" do + assert relation.public_send("#{method}!", :foo).equal?(relation) + assert_equal :foo, relation.public_send("#{method}_value") + end + end + + test '#lock!' do + assert relation.lock!('foo').equal?(relation) + assert_equal 'foo', relation.lock_value + end + + test '#reorder!' do + relation = self.relation.order('foo') + + assert relation.reorder!('bar').equal?(relation) + assert_equal ['bar'], relation.order_values + assert relation.reordering_value + end + + test 'reverse_order!' do + assert relation.reverse_order!.equal?(relation) + assert relation.reverse_order_value + relation.reverse_order! + assert !relation.reverse_order_value + end + + test 'create_with!' do + assert relation.create_with!(foo: 'bar').equal?(relation) + assert_equal({foo: 'bar'}, relation.create_with_value) + end + + test 'merge!' do + assert relation.merge!(where: :foo).equal?(relation) + assert_equal [:foo], relation.where_values + end end end diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index bf1eb6386a..3ef357e297 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -4,11 +4,11 @@ require 'models/tagging' require 'models/post' require 'models/topic' require 'models/comment' -require 'models/reply' require 'models/author' require 'models/comment' require 'models/entrant' require 'models/developer' +require 'models/reply' require 'models/company' require 'models/bird' require 'models/car' @@ -184,12 +184,12 @@ class RelationTest < ActiveRecord::TestCase end def test_finding_with_complex_order_and_limit - tags = Tag.includes(:taggings).order("REPLACE('abc', taggings.taggable_type, taggings.taggable_type)").limit(1).to_a + tags = Tag.includes(:taggings).references(:taggings).order("REPLACE('abc', taggings.taggable_type, taggings.taggable_type)").limit(1).to_a assert_equal 1, tags.length end def test_finding_with_complex_order - tags = Tag.includes(:taggings).order("REPLACE('abc', taggings.taggable_type, taggings.taggable_type)").to_a + tags = Tag.includes(:taggings).references(:taggings).order("REPLACE('abc', taggings.taggable_type, taggings.taggable_type)").to_a assert_equal 3, tags.length end @@ -215,6 +215,52 @@ class RelationTest < ActiveRecord::TestCase assert_equal [2, 4, 6, 8, 10], even_ids.sort end + def test_none + assert_no_queries do + assert_equal [], Developer.none + assert_equal [], Developer.scoped.none + assert Developer.none.is_a?(ActiveRecord::NullRelation) + end + end + + def test_none_chainable + assert_no_queries do + assert_equal [], Developer.none.where(:name => 'David') + end + end + + def test_none_chained_to_methods_firing_queries_straight_to_db + assert_no_queries do + assert_equal [], Developer.none.pluck(:id) # => uses select_all + assert_equal 0, Developer.none.delete_all + assert_equal 0, Developer.none.update_all(:name => 'David') + assert_equal 0, Developer.none.delete(1) + assert_equal false, Developer.none.exists?(1) + end + end + + def test_null_relation_content_size_methods + assert_no_queries do + assert_equal 0, Developer.none.size + assert_equal 0, Developer.none.count + assert_equal true, Developer.none.empty? + assert_equal false, Developer.none.any? + assert_equal false, Developer.none.many? + end + end + + def test_null_relation_calculations_methods + assert_no_queries do + assert_equal 0, Developer.none.count + assert_equal nil, Developer.none.calculate(:average, 'salary') + end + end + + def test_null_relation_metadata_methods + assert_equal "", Developer.none.to_sql + assert_equal({}, Developer.none.where_values_hash) + end + def test_joins_with_nil_argument assert_nothing_raised { DependentFirm.joins(nil).first } end @@ -236,7 +282,7 @@ class RelationTest < ActiveRecord::TestCase end def test_find_on_hash_conditions - assert_equal Topic.find(:all, :conditions => {:approved => false}), Topic.where({ :approved => false }).to_a + assert_equal Topic.scoped(:where => {:approved => false}).all, Topic.where({ :approved => false }).to_a end def test_joins_with_string_array @@ -284,7 +330,6 @@ class RelationTest < ActiveRecord::TestCase end def test_respond_to_class_methods_and_scopes - assert DeveloperOrderedBySalary.scoped.respond_to?(:all_ordered_by_name) assert Topic.scoped.respond_to?(:by_lifo) end @@ -310,7 +355,7 @@ class RelationTest < ActiveRecord::TestCase assert posts.first.comments.first end - assert_queries(ActiveRecord::IdentityMap.enabled? ? 1 : 2) do + assert_queries(2) do posts = Post.preload(:comments).order('posts.id') assert posts.first.comments.first end @@ -320,12 +365,12 @@ class RelationTest < ActiveRecord::TestCase assert posts.first.author end - assert_queries(ActiveRecord::IdentityMap.enabled? ? 1 : 2) do + assert_queries(2) do posts = Post.preload(:author).order('posts.id') assert posts.first.author end - assert_queries(ActiveRecord::IdentityMap.enabled? ? 1 : 3) do + assert_queries(3) do posts = Post.preload(:author, :comments).order('posts.id') assert posts.first.author assert posts.first.comments.first @@ -338,7 +383,7 @@ class RelationTest < ActiveRecord::TestCase assert posts.first.comments.first end - assert_queries(ActiveRecord::IdentityMap.enabled? ? 1 : 2) do + assert_queries(2) do posts = Post.scoped.includes(:comments).order('posts.id') assert posts.first.comments.first end @@ -348,7 +393,7 @@ class RelationTest < ActiveRecord::TestCase assert posts.first.author end - assert_queries(ActiveRecord::IdentityMap.enabled? ? 1 : 3) do + assert_queries(3) do posts = Post.includes(:author, :comments).order('posts.id') assert posts.first.author assert posts.first.comments.first @@ -449,6 +494,18 @@ class RelationTest < ActiveRecord::TestCase assert_equal authors(:david), authors.find_or_create_by_name(:name => 'David') end + def test_dynamic_find_or_create_by_attributes_bang + authors = Author.scoped + + assert_raises(ActiveRecord::RecordInvalid) { authors.find_or_create_by_name!('') } + + lifo = authors.find_or_create_by_name!('Lifo') + assert_equal "Lifo", lifo.name + assert lifo.persisted? + + assert_equal authors(:david), authors.find_or_create_by_name!(:name => 'David') + end + def test_find_id authors = Author.scoped @@ -631,6 +688,10 @@ class RelationTest < ActiveRecord::TestCase assert davids.loaded? end + def test_delete_all_limit_error + assert_raises(ActiveRecord::ActiveRecordError) { Author.limit(10).delete_all } + end + def test_select_argument_error assert_raises(ArgumentError) { Developer.select } end @@ -660,10 +721,8 @@ class RelationTest < ActiveRecord::TestCase end def test_relation_merging_with_preload - ActiveRecord::IdentityMap.without do - [Post.scoped.merge(Post.preload(:author)), Post.preload(:author).merge(Post.scoped)].each do |posts| - assert_queries(2) { assert posts.first.author } - end + [Post.scoped.merge(Post.preload(:author)), Post.preload(:author).merge(Post.scoped)].each do |posts| + assert_queries(2) { assert posts.first.author } end end @@ -1004,10 +1063,6 @@ class RelationTest < ActiveRecord::TestCase assert_equal Post.all, all_posts.all end - def test_extensions_with_except - assert_equal 2, Topic.named_extension.order(:author_name).except(:order).two - end - def test_only relation = Post.where(:author_id => 1).order('id ASC').limit(1) assert_equal [posts(:welcome)], relation.all @@ -1019,10 +1074,6 @@ class RelationTest < ActiveRecord::TestCase assert_equal Post.limit(1).all.first, all_posts.first end - def test_extensions_with_only - assert_equal 2, Topic.named_extension.order(:author_name).only(:order).two - end - def test_anonymous_extension relation = Post.where(:author_id => 1).order('id ASC').extending do def author @@ -1044,36 +1095,26 @@ class RelationTest < ActiveRecord::TestCase assert_equal Post.order(Post.arel_table[:title]).all, Post.order("title").all end - def test_order_with_find_with_order - assert_equal 'zyke', CoolCar.order('name desc').find(:first, :order => 'id').name - assert_equal 'zyke', FastCar.order('name desc').find(:first, :order => 'id').name - end - def test_default_scope_order_with_scope_order assert_equal 'zyke', CoolCar.order_using_new_style.limit(1).first.name - assert_equal 'zyke', CoolCar.order_using_old_style.limit(1).first.name assert_equal 'zyke', FastCar.order_using_new_style.limit(1).first.name - assert_equal 'zyke', FastCar.order_using_old_style.limit(1).first.name end def test_order_using_scoping car1 = CoolCar.order('id DESC').scoping do - CoolCar.find(:first, :order => 'id asc') + CoolCar.scoped(:order => 'id asc').first end assert_equal 'zyke', car1.name car2 = FastCar.order('id DESC').scoping do - FastCar.find(:first, :order => 'id asc') + FastCar.scoped(:order => 'id asc').first end assert_equal 'zyke', car2.name end def test_unscoped_block_style assert_equal 'honda', CoolCar.unscoped { CoolCar.order_using_new_style.limit(1).first.name} - assert_equal 'honda', CoolCar.unscoped { CoolCar.order_using_old_style.limit(1).first.name} - assert_equal 'honda', FastCar.unscoped { FastCar.order_using_new_style.limit(1).first.name} - assert_equal 'honda', FastCar.unscoped { FastCar.order_using_old_style.limit(1).first.name} end def test_intersection_with_array @@ -1084,10 +1125,6 @@ class RelationTest < ActiveRecord::TestCase assert_equal [rails_author], relation & [rails_author] end - def test_removing_limit_with_options - assert_not_equal 1, Post.limit(1).all(:limit => nil).count - end - def test_primary_key assert_equal "id", Post.scoped.primary_key end @@ -1104,7 +1141,9 @@ class RelationTest < ActiveRecord::TestCase ) ) - assert scope.eager_loading? + assert_deprecated do + assert scope.eager_loading? + end end def test_ordering_with_extra_spaces @@ -1164,4 +1203,105 @@ class RelationTest < ActiveRecord::TestCase end assert_equal ['Foo', 'Foo'], query.uniq(true).uniq(false).map(&:name) end + + def test_references_triggers_eager_loading + scope = Post.includes(:comments) + assert !scope.eager_loading? + assert scope.references(:comments).eager_loading? + end + + def test_references_doesnt_trigger_eager_loading_if_reference_not_included + scope = Post.references(:comments) + assert !scope.eager_loading? + end + + def test_automatically_added_where_references + scope = Post.where(:comments => { :body => "Bla" }) + assert_equal ['comments'], scope.references_values + + scope = Post.where('comments.body' => 'Bla') + assert_equal ['comments'], scope.references_values + end + + def test_automatically_added_having_references + scope = Post.having(:comments => { :body => "Bla" }) + assert_equal ['comments'], scope.references_values + + scope = Post.having('comments.body' => 'Bla') + assert_equal ['comments'], scope.references_values + end + + def test_automatically_added_order_references + scope = Post.order('comments.body') + assert_equal ['comments'], scope.references_values + + scope = Post.order('comments.body', 'yaks.body') + assert_equal ['comments', 'yaks'], scope.references_values + + # Don't infer yaks, let's not go down that road again... + scope = Post.order('comments.body, yaks.body') + assert_equal ['comments'], scope.references_values + + scope = Post.order('comments.body asc') + assert_equal ['comments'], scope.references_values + + scope = Post.order('foo(comments.body)') + assert_equal [], scope.references_values + end + + def test_presence + topics = Topic.scoped + + # the first query is triggered because there are no topics yet. + assert_queries(1) { assert topics.present? } + + # checking if there are topics is used before you actually display them, + # thus it shouldn't invoke an extra count query. + assert_no_queries { assert topics.present? } + assert_no_queries { assert !topics.blank? } + + # shows count of topics and loops after loading the query should not trigger extra queries either. + assert_no_queries { topics.size } + assert_no_queries { topics.length } + assert_no_queries { topics.each } + + # count always trigger the COUNT query. + assert_queries(1) { topics.count } + + assert topics.loaded? + end + + test "find_by with hash conditions returns the first matching record" do + assert_equal posts(:eager_other), Post.order(:id).find_by(author_id: 2) + end + + test "find_by with non-hash conditions returns the first matching record" do + assert_equal posts(:eager_other), Post.order(:id).find_by("author_id = 2") + end + + test "find_by with multi-arg conditions returns the first matching record" do + assert_equal posts(:eager_other), Post.order(:id).find_by('author_id = ?', 2) + end + + test "find_by returns nil if the record is missing" do + assert_equal nil, Post.scoped.find_by("1 = 0") + end + + test "find_by! with hash conditions returns the first matching record" do + assert_equal posts(:eager_other), Post.order(:id).find_by!(author_id: 2) + end + + test "find_by! with non-hash conditions returns the first matching record" do + assert_equal posts(:eager_other), Post.order(:id).find_by!("author_id = 2") + end + + test "find_by! with multi-arg conditions returns the first matching record" do + assert_equal posts(:eager_other), Post.order(:id).find_by!('author_id = ?', 2) + end + + test "find_by! raises RecordNotFound if the record is missing" do + assert_raises(ActiveRecord::RecordNotFound) do + Post.scoped.find_by!("1 = 0") + end + end end diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb index 5c3a78688e..15ceaa1fcc 100644 --- a/activerecord/test/cases/schema_dumper_test.rb +++ b/activerecord/test/cases/schema_dumper_test.rb @@ -2,7 +2,13 @@ require "cases/helper" class SchemaDumperTest < ActiveRecord::TestCase + def initialize(*) + super + ActiveRecord::SchemaMigration.create_table + end + def setup + super @stream = StringIO.new end @@ -13,10 +19,18 @@ class SchemaDumperTest < ActiveRecord::TestCase @stream.string end - if "string".encoding_aware? - def test_magic_comment - assert_match "# encoding: #{@stream.external_encoding.name}", standard_dump + def test_dump_schema_information_outputs_lexically_ordered_versions + versions = %w{ 20100101010101 20100201010101 20100301010101 } + versions.reverse.each do |v| + ActiveRecord::SchemaMigration.create!(:version => v) end + + schema_info = ActiveRecord::Base.connection.dump_schema_information + assert_match(/20100201010101.*20100301010101/m, schema_info) + end + + def test_magic_comment + assert_match "# encoding: #{@stream.external_encoding.name}", standard_dump end def test_schema_dump @@ -171,6 +185,15 @@ class SchemaDumperTest < ActiveRecord::TestCase assert_equal 'add_index "companies", ["firm_id", "type", "rating", "ruby_type"], :name => "company_index"', index_definition end + def test_schema_dumps_partial_indices + 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)"', index_definition + else + assert_equal 'add_index "companies", ["firm_id", "type"], :name => "company_partial_index"', index_definition + end + end + def test_schema_dump_should_honor_nonstandard_primary_keys output = standard_dump match = output.match(%r{create_table "movies"(.*)do}) @@ -213,6 +236,13 @@ class SchemaDumperTest < ActiveRecord::TestCase end end + def test_schema_dump_includes_hstores_shorthand_definition + output = standard_dump + if %r{create_table "postgresql_hstores"} =~ output + assert_match %r[t.hstore "hash_store", :default => {}], output + end + end + def test_schema_dump_includes_tsvector_shorthand_definition output = standard_dump if %r{create_table "postgresql_tsvectors"} =~ output diff --git a/activerecord/test/cases/serialization_test.rb b/activerecord/test/cases/serialization_test.rb index 61b04b3e37..a4c065e667 100644 --- a/activerecord/test/cases/serialization_test.rb +++ b/activerecord/test/cases/serialization_test.rb @@ -13,7 +13,8 @@ class SerializationTest < ActiveRecord::TestCase :created_at => Time.utc(2006, 8, 1), :awesome => false, :preferences => { :gem => '<strong>ruby</strong>' }, - :alternative_id => nil + :alternative_id => nil, + :id => nil } end @@ -24,7 +25,7 @@ class SerializationTest < ActiveRecord::TestCase end def test_serialize_should_be_reversible - for format in FORMATS + FORMATS.each do |format| @serialized = Contact.new.send("to_#{format}") contact = Contact.new.send("from_#{format}", @serialized) @@ -33,7 +34,7 @@ class SerializationTest < ActiveRecord::TestCase end def test_serialize_should_allow_attribute_only_filtering - for format in FORMATS + FORMATS.each do |format| @serialized = Contact.new(@contact_attributes).send("to_#{format}", :only => [ :age, :name ]) contact = Contact.new.send("from_#{format}", @serialized) assert_equal @contact_attributes[:name], contact.name, "For #{format}" @@ -42,7 +43,7 @@ class SerializationTest < ActiveRecord::TestCase end def test_serialize_should_allow_attribute_except_filtering - for format in FORMATS + FORMATS.each do |format| @serialized = Contact.new(@contact_attributes).send("to_#{format}", :except => [ :age, :name ]) contact = Contact.new.send("from_#{format}", @serialized) assert_nil contact.name, "For #{format}" diff --git a/activerecord/test/cases/session_store/session_test.rb b/activerecord/test/cases/session_store/session_test.rb index 258cee7aba..a3b8ab74d9 100644 --- a/activerecord/test/cases/session_store/session_test.rb +++ b/activerecord/test/cases/session_store/session_test.rb @@ -5,11 +5,15 @@ require 'active_record/session_store' module ActiveRecord class SessionStore class SessionTest < ActiveRecord::TestCase - self.use_transactional_fixtures = false unless supports_savepoints? && ActiveRecord::Base.connection.supports_ddl_transactions? + self.use_transactional_fixtures = false + + attr_reader :session_klass def setup super + ActiveRecord::Base.connection.schema_cache.clear! Session.drop_table! if Session.table_exists? + @session_klass = Class.new(Session) end def test_data_column_name @@ -60,8 +64,8 @@ module ActiveRecord def test_find_by_session_id Session.create_table! session_id = "10" - s = Session.create!(:data => 'world', :session_id => session_id) - t = Session.find_by_session_id(session_id) + s = session_klass.create!(:data => 'world', :session_id => session_id) + t = session_klass.find_by_session_id(session_id) assert_equal s, t assert_equal s.data, t.data Session.drop_table! diff --git a/activerecord/test/cases/store_test.rb b/activerecord/test/cases/store_test.rb index 074fd39e65..40520d6da2 100644 --- a/activerecord/test/cases/store_test.rb +++ b/activerecord/test/cases/store_test.rb @@ -4,14 +4,14 @@ require 'models/admin/user' class StoreTest < ActiveRecord::TestCase setup do - @john = Admin::User.create(:name => 'John Doe', :color => 'black') + @john = Admin::User.create(:name => 'John Doe', :color => 'black', :remember_login => true) end test "reading store attributes through accessors" do assert_equal 'black', @john.color assert_nil @john.homepage end - + test "writing store attributes through accessors" do @john.color = 'red' @john.homepage = '37signals.com' @@ -24,11 +24,20 @@ class StoreTest < ActiveRecord::TestCase @john.settings[:icecream] = 'graeters' @john.save - assert 'graeters', @john.reload.settings[:icecream] + assert_equal 'graeters', @john.reload.settings[:icecream] end - + test "updating the store will mark it as changed" do @john.color = 'red' assert @john.settings_changed? end + + test "object initialization with not nullable column" do + assert_equal true, @john.remember_login + end + + test "writing with not nullable column" do + @john.remember_login = false + assert_equal false, @john.remember_login + end end diff --git a/activerecord/test/cases/test_case.rb b/activerecord/test/cases/test_case.rb new file mode 100644 index 0000000000..94a13d386c --- /dev/null +++ b/activerecord/test/cases/test_case.rb @@ -0,0 +1,10 @@ +require 'active_support/deprecation' +ActiveSupport::Deprecation.silence do + require 'active_record/test_case' +end + +ActiveRecord::TestCase.class_eval do + def sqlite3? connection + connection.class.name.split('::').last == "SQLite3Adapter" + end +end diff --git a/activerecord/test/cases/transaction_callbacks_test.rb b/activerecord/test/cases/transaction_callbacks_test.rb index 85f222bca2..f8b3e01a49 100644 --- a/activerecord/test/cases/transaction_callbacks_test.rb +++ b/activerecord/test/cases/transaction_callbacks_test.rb @@ -6,7 +6,7 @@ class TransactionCallbacksTest < ActiveRecord::TestCase fixtures :topics class TopicWithCallbacks < ActiveRecord::Base - set_table_name :topics + self.table_name = :topics after_commit{|record| record.send(:do_after_commit, nil)} after_commit(:on => :create){|record| record.send(:do_after_commit, :create)} @@ -252,7 +252,7 @@ class TransactionObserverCallbacksTest < ActiveRecord::TestCase fixtures :topics class TopicWithObserverAttached < ActiveRecord::Base - set_table_name :topics + self.table_name = :topics def history @history ||= [] end diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb index 110a18772f..203dd054f1 100644 --- a/activerecord/test/cases/transactions_test.rb +++ b/activerecord/test/cases/transactions_test.rb @@ -563,6 +563,7 @@ if current_adapter?(:PostgreSQLAdapter) topic.approved = !topic.approved? topic.save! end + Topic.connection.close end end @@ -598,6 +599,7 @@ if current_adapter?(:PostgreSQLAdapter) dev = Developer.find(1) assert_equal original_salary, dev.salary end + Developer.connection.close end end @@ -610,6 +612,7 @@ if current_adapter?(:PostgreSQLAdapter) assert_equal original_salary, Developer.find(1).salary end end + Developer.connection.close end threads.each { |t| t.join } diff --git a/activerecord/test/cases/unconnected_test.rb b/activerecord/test/cases/unconnected_test.rb index e82ca3f93d..5a69054445 100644 --- a/activerecord/test/cases/unconnected_test.rb +++ b/activerecord/test/cases/unconnected_test.rb @@ -7,13 +7,13 @@ class TestUnconnectedAdapter < ActiveRecord::TestCase self.use_transactional_fixtures = false def setup - @underlying = ActiveRecord::Base.connection - @specification = ActiveRecord::Base.remove_connection + @underlying = ActiveRecord::Model.connection + @specification = ActiveRecord::Model.remove_connection end def teardown @underlying = nil - ActiveRecord::Base.establish_connection(@specification) + ActiveRecord::Model.establish_connection(@specification) load_schema if in_memory_db? end diff --git a/activerecord/test/cases/validations/association_validation_test.rb b/activerecord/test/cases/validations/association_validation_test.rb index 56e345990f..7ac34bc71e 100644 --- a/activerecord/test/cases/validations/association_validation_test.rb +++ b/activerecord/test/cases/validations/association_validation_test.rb @@ -61,6 +61,16 @@ class AssociationValidationTest < ActiveRecord::TestCase assert r.valid? end + def test_validates_associated_marked_for_destruction + Topic.validates_associated(:replies) + Reply.validates_presence_of(:content) + t = Topic.new + t.replies << Reply.new + assert t.invalid? + t.replies.first.mark_for_destruction + assert t.valid? + end + def test_validates_associated_with_custom_message_using_quotes Reply.validates_associated :topic, :message=> "This string contains 'single' and \"double\" quotes" Topic.validates_presence_of :content @@ -76,19 +86,17 @@ class AssociationValidationTest < ActiveRecord::TestCase assert !r.valid? assert r.errors[:topic].any? - r.topic = Topic.find :first + r.topic = Topic.first assert r.valid? end def test_validates_size_of_association_utf8 - with_kcode('UTF8') do - assert_nothing_raised { Owner.validates_size_of :pets, :minimum => 1 } - o = Owner.new('name' => 'あいうえおかきくけこ') - assert !o.save - assert o.errors[:pets].any? - o.pets.build('name' => 'あいうえおかきくけこ') - assert o.valid? - end + assert_nothing_raised { Owner.validates_size_of :pets, :minimum => 1 } + o = Owner.new('name' => 'あいうえおかきくけこ') + assert !o.save + assert o.errors[:pets].any? + o.pets.build('name' => 'あいうえおかきくけこ') + assert o.valid? end def test_validates_presence_of_belongs_to_association__parent_is_new_record @@ -110,4 +118,21 @@ class AssociationValidationTest < ActiveRecord::TestCase end end + def test_validates_associated_models_in_the_same_context + Topic.validates_presence_of :title, :on => :custom_context + Topic.validates_associated :replies + Reply.validates_presence_of :title, :on => :custom_context + + t = Topic.new('title' => '') + r = t.replies.new('title' => '') + + assert t.valid? + assert !t.valid?(:custom_context) + + t.title = "Longer" + assert !t.valid?(:custom_context), "Should NOT be valid if the associated object is not valid in the same context." + + r.title = "Longer" + assert t.valid?(:custom_context), "Should be valid if the associated object is not valid in the same context." + end end 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 628029f8df..a8e513d81f 100644 --- a/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb +++ b/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb @@ -8,6 +8,16 @@ class I18nGenerateMessageValidationTest < ActiveRecord::TestCase I18n.backend = I18n::Backend::Simple.new end + def reset_i18n_load_path + @old_load_path, @old_backend = I18n.load_path.dup, I18n.backend + I18n.load_path.clear + I18n.backend = I18n::Backend::Simple.new + yield + ensure + I18n.load_path.replace @old_load_path + I18n.backend = @old_backend + end + # validates_associated: generate_message(attr_name, :invalid, :message => custom_message, :value => value) def test_generate_message_invalid_with_default_message assert_equal 'is invalid', @topic.errors.generate_message(:title, :invalid, :value => 'title') @@ -35,4 +45,13 @@ class I18nGenerateMessageValidationTest < ActiveRecord::TestCase assert_equal "Validation failed: Title is invalid, Title can't be blank", ActiveRecord::RecordInvalid.new(topic).message end + test "RecordInvalid exception translation falls back to the :errors namespace" do + reset_i18n_load_path do + I18n.backend.store_translations 'en', :errors => {:messages => {:record_invalid => 'fallback message'}} + topic = Topic.new + topic.errors.add(:title, :blank) + assert_equal "fallback message", ActiveRecord::RecordInvalid.new(topic).message + end + end + end diff --git a/activerecord/test/cases/validations/uniqueness_validation_test.rb b/activerecord/test/cases/validations/uniqueness_validation_test.rb index 0f1b3667cc..c173ee9a15 100644 --- a/activerecord/test/cases/validations/uniqueness_validation_test.rb +++ b/activerecord/test/cases/validations/uniqueness_validation_test.rb @@ -45,6 +45,18 @@ class UniquenessValidationTest < ActiveRecord::TestCase assert t2.save, "Should now save t2 as unique" end + def test_validates_uniqueness_with_nil_value + Topic.validates_uniqueness_of(:title) + + t = Topic.new("title" => nil) + assert t.save, "Should save t as unique" + + t2 = Topic.new("title" => nil) + assert !t2.valid?, "Shouldn't be valid" + assert !t2.save, "Shouldn't save t2 as unique" + assert_equal ["has already been taken"], t2.errors[:title] + end + def test_validates_uniqueness_with_validates Topic.validates :title, :uniqueness => true Topic.create!('title' => 'abc') @@ -80,6 +92,30 @@ class UniquenessValidationTest < ActiveRecord::TestCase assert r3.valid?, "Saving r3" end + def test_validate_uniqueness_with_object_scope + Reply.validates_uniqueness_of(:content, :scope => :topic) + + t = Topic.create("title" => "I'm unique!") + + r1 = t.replies.create "title" => "r1", "content" => "hello world" + assert r1.valid?, "Saving r1" + + r2 = t.replies.create "title" => "r2", "content" => "hello world" + assert !r2.valid?, "Saving r2 first time" + end + + def test_validate_uniqueness_with_object_arg + Reply.validates_uniqueness_of(:topic) + + t = Topic.create("title" => "I'm unique!") + + r1 = t.replies.create "title" => "r1", "content" => "hello world" + assert r1.valid?, "Saving r1" + + r2 = t.replies.create "title" => "r2", "content" => "hello world" + assert !r2.valid?, "Saving r2 first time" + end + def test_validate_uniqueness_scoped_to_defining_class t = Topic.create("title" => "What, me worry?") @@ -149,16 +185,14 @@ class UniquenessValidationTest < ActiveRecord::TestCase assert t2.valid?, "should validate with nil" assert t2.save, "should save with nil" - with_kcode('UTF8') do - t_utf8 = Topic.new("title" => "Я тоже уникальный!") - assert t_utf8.save, "Should save t_utf8 as unique" + t_utf8 = Topic.new("title" => "Я тоже уникальный!") + assert t_utf8.save, "Should save t_utf8 as unique" - # If database hasn't UTF-8 character set, this test fails - if Topic.find(t_utf8, :select => 'LOWER(title) AS title').title == "я тоже уникальный!" - t2_utf8 = Topic.new("title" => "я тоже УНИКАЛЬНЫЙ!") - assert !t2_utf8.valid?, "Shouldn't be valid" - assert !t2_utf8.save, "Shouldn't save t2_utf8 as unique" - end + # If database hasn't UTF-8 character set, this test fails + if Topic.scoped(:select => 'LOWER(title) AS title').find(t_utf8).title == "я тоже уникальный!" + t2_utf8 = Topic.new("title" => "я тоже УНИКАЛЬНЫЙ!") + assert !t2_utf8.valid?, "Shouldn't be valid" + assert !t2_utf8.save, "Shouldn't save t2_utf8 as unique" end end @@ -227,10 +261,10 @@ class UniquenessValidationTest < ActiveRecord::TestCase assert i1.errors[:value].any?, "Should not be empty" end - def test_validates_uniqueness_inside_with_scope + def test_validates_uniqueness_inside_scoping Topic.validates_uniqueness_of(:title) - Topic.send(:with_scope, :find => { :conditions => { :author_name => "David" } }) do + Topic.where(:author_name => "David").scoping do t1 = Topic.new("title" => "I'm unique!", "author_name" => "Mary") assert t1.save t2 = Topic.new("title" => "I'm unique!", "author_name" => "David") @@ -256,13 +290,11 @@ class UniquenessValidationTest < ActiveRecord::TestCase end def test_validate_uniqueness_with_limit_and_utf8 - with_kcode('UTF8') do - # Event.title is limited to 5 characters - e1 = Event.create(:title => "一二三四五") - assert e1.valid?, "Could not create an event with a unique, 5 character title" - e2 = Event.create(:title => "一二三四五六七八") - assert !e2.valid?, "Created an event whose title, with limit taken into account, is not unique" - end + # Event.title is limited to 5 characters + e1 = Event.create(:title => "一二三四五") + assert e1.valid?, "Could not create an event with a unique, 5 character title" + e2 = Event.create(:title => "一二三四五六七八") + assert !e2.valid?, "Created an event whose title, with limit taken into account, is not unique" end def test_validate_straight_inheritance_uniqueness @@ -293,4 +325,16 @@ class UniquenessValidationTest < ActiveRecord::TestCase assert w6.errors[:city].any?, "Should have errors for city" assert_equal ["has already been taken"], w6.errors[:city], "Should have uniqueness message for city" end + + def test_validate_uniqueness_with_conditions + Topic.validates_uniqueness_of(:title, :conditions => Topic.where('approved = ?', true)) + Topic.create("title" => "I'm a topic", "approved" => true) + Topic.create("title" => "I'm an unapproved topic", "approved" => false) + + t3 = Topic.new("title" => "I'm a topic", "approved" => true) + assert !t3.valid?, "t3 shouldn't be valid" + + t4 = Topic.new("title" => "I'm an unapproved topic", "approved" => false) + assert t4.valid?, "t4 should be valid" + end end diff --git a/activerecord/test/cases/validations_test.rb b/activerecord/test/cases/validations_test.rb index c3e494866b..b11b330374 100644 --- a/activerecord/test/cases/validations_test.rb +++ b/activerecord/test/cases/validations_test.rb @@ -8,7 +8,7 @@ require 'models/parrot' require 'models/company' class ProtectedPerson < ActiveRecord::Base - set_table_name 'people' + self.table_name = 'people' attr_accessor :addon attr_protected :first_name end @@ -93,30 +93,6 @@ class ValidationsTest < ActiveRecord::TestCase end end - def test_scoped_create_without_attributes - WrongReply.send(:with_scope, :create => {}) do - assert_raise(ActiveRecord::RecordInvalid) { WrongReply.create! } - end - end - - def test_create_with_exceptions_using_scope_for_protected_attributes - assert_nothing_raised do - ProtectedPerson.send(:with_scope, :create => { :first_name => "Mary" } ) do - person = ProtectedPerson.create! :addon => "Addon" - assert_equal person.first_name, "Mary", "scope should ignore attr_protected" - end - end - end - - def test_create_with_exceptions_using_scope_and_empty_attributes - assert_nothing_raised do - ProtectedPerson.send(:with_scope, :create => { :first_name => "Mary" } ) do - person = ProtectedPerson.create! - assert_equal person.first_name, "Mary", "should be ok when no attributes are passed to create!" - end - end - end - def test_create_without_validation reply = WrongReply.new assert !reply.save diff --git a/activerecord/test/cases/yaml_serialization_test.rb b/activerecord/test/cases/yaml_serialization_test.rb index 0b54c309d1..302913e095 100644 --- a/activerecord/test/cases/yaml_serialization_test.rb +++ b/activerecord/test/cases/yaml_serialization_test.rb @@ -1,14 +1,20 @@ -require "cases/helper" +require 'cases/helper' require 'models/topic' class YamlSerializationTest < ActiveRecord::TestCase fixtures :topics def test_to_yaml_with_time_with_zone_should_not_raise_exception + tz = Time.zone Time.zone = ActiveSupport::TimeZone["Pacific Time (US & Canada)"] ActiveRecord::Base.time_zone_aware_attributes = true + topic = Topic.new(:written_on => DateTime.now) assert_nothing_raised { topic.to_yaml } + + ensure + Time.zone = tz + ActiveRecord::Base.time_zone_aware_attributes = false end def test_roundtrip @@ -18,6 +24,11 @@ class YamlSerializationTest < ActiveRecord::TestCase assert_equal topic, t end + def test_roundtrip_serialized_column + topic = Topic.new(:content => {:omg=>:lol}) + assert_equal({:omg=>:lol}, YAML.load(YAML.dump(topic)).content) + end + def test_encode_with_coder topic = Topic.first coder = {} @@ -25,22 +36,17 @@ class YamlSerializationTest < ActiveRecord::TestCase assert_equal({'attributes' => topic.attributes}, coder) end - begin - require 'psych' - - def test_psych_roundtrip - topic = Topic.first - assert topic - t = Psych.load Psych.dump topic - assert_equal topic, t - end - - def test_psych_roundtrip_new_object - topic = Topic.new - assert topic - t = Psych.load Psych.dump topic - assert_equal topic.attributes, t.attributes - end - rescue LoadError + def test_psych_roundtrip + topic = Topic.first + assert topic + t = Psych.load Psych.dump topic + assert_equal topic, t + end + + def test_psych_roundtrip_new_object + topic = Topic.new + assert topic + t = Psych.load Psych.dump topic + assert_equal topic.attributes, t.attributes end end |