diff options
Diffstat (limited to 'activerecord/test/cases')
138 files changed, 8503 insertions, 5892 deletions
diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb index 94497e37c7..852fc0e26e 100644 --- a/activerecord/test/cases/adapter_test.rb +++ b/activerecord/test/cases/adapter_test.rb @@ -1,170 +1,163 @@ 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 - - def test_deprecated_visitor_for - visitor_klass = Class.new(Arel::Visitors::ToSql) - Arel::Visitors::VISITORS['fuuu'] = visitor_klass - pool = stub(:spec => stub(:config => { :adapter => 'fuuu' })) - visitor = assert_deprecated { - ActiveRecord::ConnectionAdapters::AbstractAdapter.visitor_for(pool) - } - assert visitor.is_a?(visitor_klass) - end end diff --git a/activerecord/test/cases/adapters/mysql/active_schema_test.rb b/activerecord/test/cases/adapters/mysql/active_schema_test.rb index 509baacaef..94fc3564df 100644 --- a/activerecord/test/cases/adapters/mysql/active_schema_test.rb +++ b/activerecord/test/cases/adapters/mysql/active_schema_test.rb @@ -2,7 +2,7 @@ require "cases/helper" class ActiveSchemaTest < ActiveRecord::TestCase def setup - ActiveRecord::ConnectionAdapters::MysqlAdapter.class_eval do + ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter.class_eval do alias_method :execute_without_stub, :execute remove_method :execute def execute(sql, name = nil) return sql end @@ -10,7 +10,7 @@ class ActiveSchemaTest < ActiveRecord::TestCase end def teardown - ActiveRecord::ConnectionAdapters::MysqlAdapter.class_eval do + ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter.class_eval do remove_method :execute alias_method :execute, :execute_without_stub end @@ -99,7 +99,7 @@ class ActiveSchemaTest < ActiveRecord::TestCase private def with_real_execute #we need to actually modify some data, so we make execute point to the original method - ActiveRecord::ConnectionAdapters::MysqlAdapter.class_eval do + ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter.class_eval do alias_method :execute_with_stub, :execute remove_method :execute alias_method :execute, :execute_without_stub @@ -107,7 +107,7 @@ class ActiveSchemaTest < ActiveRecord::TestCase yield ensure #before finishing, we restore the alias to the mock-up method - ActiveRecord::ConnectionAdapters::MysqlAdapter.class_eval do + ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter.class_eval do remove_method :execute alias_method :execute, :execute_with_stub end diff --git a/activerecord/test/cases/adapters/mysql/case_sensitivity_test.rb b/activerecord/test/cases/adapters/mysql/case_sensitivity_test.rb new file mode 100644 index 0000000000..97adb6b297 --- /dev/null +++ b/activerecord/test/cases/adapters/mysql/case_sensitivity_test.rb @@ -0,0 +1,35 @@ +require "cases/helper" +require 'models/person' + +class MysqlCaseSensitivityTest < ActiveRecord::TestCase + class CollationTest < ActiveRecord::Base + validates_uniqueness_of :string_cs_column, :case_sensitive => false + validates_uniqueness_of :string_ci_column, :case_sensitive => false + end + + def test_columns_include_collation_different_from_table + assert_equal 'utf8_bin', CollationTest.columns_hash['string_cs_column'].collation + assert_equal 'utf8_general_ci', CollationTest.columns_hash['string_ci_column'].collation + end + + def test_case_sensitive + assert !CollationTest.columns_hash['string_ci_column'].case_sensitive? + assert CollationTest.columns_hash['string_cs_column'].case_sensitive? + end + + def test_case_insensitive_comparison_for_ci_column + CollationTest.create!(:string_ci_column => 'A') + invalid = CollationTest.new(:string_ci_column => 'a') + queries = assert_sql { invalid.save } + ci_uniqueness_query = queries.detect { |q| q.match(/string_ci_column/) } + assert_no_match(/lower/i, ci_uniqueness_query) + end + + def test_case_insensitive_comparison_for_cs_column + CollationTest.create!(:string_cs_column => 'A') + invalid = CollationTest.new(:string_cs_column => 'a') + queries = assert_sql { invalid.save } + cs_uniqueness_query = queries.detect { |q| q.match(/string_cs_column/) } + assert_match(/lower/i, cs_uniqueness_query) + end +end diff --git a/activerecord/test/cases/adapters/mysql/connection_test.rb b/activerecord/test/cases/adapters/mysql/connection_test.rb index 2a89430da9..5e1c52c9ba 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,20 +114,33 @@ 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 end + def test_mysql_default_in_strict_mode + result = @connection.exec_query "SELECT @@SESSION.sql_mode" + assert_equal [["STRICT_ALL_TABLES"]], result.rows + end + + def test_mysql_strict_mode_disabled + run_without_connection do |orig_connection| + ActiveRecord::Model.establish_connection(orig_connection.merge({:strict => false})) + result = ActiveRecord::Model.connection.exec_query "SELECT @@SESSION.sql_mode" + assert_equal [['']], result.rows + end + end + 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 a2155d1dd1..d94bb629a7 100644 --- a/activerecord/test/cases/adapters/mysql/schema_test.rb +++ b/activerecord/test/cases/adapters/mysql/schema_test.rb @@ -13,14 +13,18 @@ module ActiveRecord table = Post.table_name @db_name = db - @omgpost = Class.new(Post) do - set_table_name "#{db}.#{table}" + @omgpost = Class.new(ActiveRecord::Base) do + 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 + assert_equal 'id', @omgpost.primary_key end def test_table_exists? diff --git a/activerecord/test/cases/adapters/mysql/statement_pool_test.rb b/activerecord/test/cases/adapters/mysql/statement_pool_test.rb new file mode 100644 index 0000000000..83de90f179 --- /dev/null +++ b/activerecord/test/cases/adapters/mysql/statement_pool_test.rb @@ -0,0 +1,23 @@ +require 'cases/helper' + +module ActiveRecord::ConnectionAdapters + class MysqlAdapter + class StatementPoolTest < ActiveRecord::TestCase + def test_cache_is_per_pid + return skip('must support fork') unless Process.respond_to?(:fork) + + cache = StatementPool.new nil, 10 + cache['foo'] = 'bar' + assert_equal 'bar', cache['foo'] + + pid = fork { + lookup = cache['foo']; + exit!(!lookup) + } + + Process.waitpid pid + assert $?.success?, 'process should exit successfully' + end + end + end +end 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/case_sensitivity_test.rb b/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb new file mode 100644 index 0000000000..6bcc113482 --- /dev/null +++ b/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb @@ -0,0 +1,35 @@ +require "cases/helper" +require 'models/person' + +class Mysql2CaseSensitivityTest < ActiveRecord::TestCase + class CollationTest < ActiveRecord::Base + validates_uniqueness_of :string_cs_column, :case_sensitive => false + validates_uniqueness_of :string_ci_column, :case_sensitive => false + end + + def test_columns_include_collation_different_from_table + assert_equal 'utf8_bin', CollationTest.columns_hash['string_cs_column'].collation + assert_equal 'utf8_general_ci', CollationTest.columns_hash['string_ci_column'].collation + end + + def test_case_sensitive + assert !CollationTest.columns_hash['string_ci_column'].case_sensitive? + assert CollationTest.columns_hash['string_cs_column'].case_sensitive? + end + + def test_case_insensitive_comparison_for_ci_column + CollationTest.create!(:string_ci_column => 'A') + invalid = CollationTest.new(:string_ci_column => 'a') + queries = assert_sql { invalid.save } + ci_uniqueness_query = queries.detect { |q| q.match(/string_ci_column/) } + assert_no_match(/lower/i, ci_uniqueness_query) + end + + def test_case_insensitive_comparison_for_cs_column + CollationTest.create!(:string_cs_column => 'A') + invalid = CollationTest.new(:string_cs_column => 'a') + queries = assert_sql { invalid.save } + cs_uniqueness_query = queries.detect { |q| q.match(/string_cs_column/)} + assert_match(/lower/i, cs_uniqueness_query) + end +end diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb index 26091c713b..684c7f5929 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 @@ -29,14 +29,30 @@ class MysqlConnectionTest < ActiveRecord::TestCase assert @connection.active? end + # TODO: Below is a straight up copy/paste from mysql/connection_test.rb + # I'm not sure what the correct way is to share these tests between + # adapters in minitest. + def test_mysql_default_in_strict_mode + result = @connection.exec_query "SELECT @@SESSION.sql_mode" + assert_equal [["STRICT_ALL_TABLES"]], result.rows + end + + def test_mysql_strict_mode_disabled + run_without_connection do |orig_connection| + ActiveRecord::Model.establish_connection(orig_connection.merge({:strict => false})) + result = ActiveRecord::Model.connection.exec_query "SELECT @@SESSION.sql_mode" + assert_equal [['']], result.rows + end + end + 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 new file mode 100644 index 0000000000..68ed361aeb --- /dev/null +++ b/activerecord/test/cases/adapters/mysql2/explain_test.rb @@ -0,0 +1,26 @@ +require "cases/helper" +require 'models/developer' + +module ActiveRecord + module ConnectionAdapters + class Mysql2Adapter + class ExplainTest < ActiveRecord::TestCase + fixtures :developers + + def test_explain_for_one_query + explain = Developer.where(:id => 1).explain + assert_match %(EXPLAIN for: SELECT `developers`.* FROM `developers` WHERE `developers`.`id` = 1), explain + assert_match %(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 + end + 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 858d1da2dd..2c0ed73c92 100644 --- a/activerecord/test/cases/adapters/mysql2/schema_test.rb +++ b/activerecord/test/cases/adapters/mysql2/schema_test.rb @@ -13,14 +13,18 @@ module ActiveRecord table = Post.table_name @db_name = db - @omgpost = Class.new(Post) do - set_table_name "#{db}.#{table}" + @omgpost = Class.new(ActiveRecord::Base) do + 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 + assert_equal 'id', @omgpost.primary_key end def test_table_exists? @@ -31,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/datatype_test.rb b/activerecord/test/cases/adapters/postgresql/datatype_test.rb index ce08e4c6a7..34660577da 100644 --- a/activerecord/test/cases/adapters/postgresql/datatype_test.rb +++ b/activerecord/test/cases/adapters/postgresql/datatype_test.rb @@ -86,9 +86,9 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase end def test_data_type_of_network_address_types - assert_equal :string, @first_network_address.column_for_attribute(:cidr_address).type - assert_equal :string, @first_network_address.column_for_attribute(:inet_address).type - assert_equal :string, @first_network_address.column_for_attribute(:mac_address).type + assert_equal :cidr, @first_network_address.column_for_attribute(:cidr_address).type + assert_equal :inet, @first_network_address.column_for_attribute(:inet_address).type + assert_equal :macaddr, @first_network_address.column_for_attribute(:mac_address).type end def test_data_type_of_bit_string_types @@ -134,9 +134,12 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase assert_equal '-1 years -2 days', @first_time.time_interval end - def test_network_address_values - assert_equal '192.168.0.0/24', @first_network_address.cidr_address - assert_equal '172.16.1.254', @first_network_address.inet_address + def test_network_address_values_ipaddr + cidr_address = IPAddr.new '192.168.0.0/24' + inet_address = IPAddr.new '172.16.1.254' + + assert_equal cidr_address, @first_network_address.cidr_address + assert_equal inet_address, @first_network_address.inet_address assert_equal '01:23:45:67:89:0a', @first_network_address.mac_address end @@ -200,8 +203,8 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase end def test_update_network_address - new_cidr_address = '10.1.2.3/32' - new_inet_address = '10.0.0.0/8' + new_inet_address = '10.1.2.3/32' + new_cidr_address = '10.0.0.0/8' new_mac_address = 'bc:de:f0:12:34:56' assert @first_network_address.cidr_address = new_cidr_address assert @first_network_address.inet_address = new_inet_address diff --git a/activerecord/test/cases/adapters/postgresql/explain_test.rb b/activerecord/test/cases/adapters/postgresql/explain_test.rb new file mode 100644 index 0000000000..619d581d5f --- /dev/null +++ b/activerecord/test/cases/adapters/postgresql/explain_test.rb @@ -0,0 +1,35 @@ +require "cases/helper" +require 'models/developer' + +module ActiveRecord + module ConnectionAdapters + class PostgreSQLAdapter + class ExplainTest < ActiveRecord::TestCase + fixtures :developers + + def test_explain_for_one_query + explain = Developer.where(:id => 1).explain + assert_match %(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = 1), explain + assert_match %(QUERY PLAN), explain + assert_match %(Index Scan using developers_pkey on developers), explain + end + + 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 +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/quoting_test.rb b/activerecord/test/cases/adapters/postgresql/quoting_test.rb index 172055f15c..f8a605b67c 100644 --- a/activerecord/test/cases/adapters/postgresql/quoting_test.rb +++ b/activerecord/test/cases/adapters/postgresql/quoting_test.rb @@ -19,6 +19,18 @@ module ActiveRecord assert_equal 'f', @conn.type_cast(false, nil) assert_equal 'f', @conn.type_cast(false, c) end + + def test_quote_float_nan + nan = 0.0/0 + c = Column.new(nil, 1, 'float') + assert_equal "'NaN'", @conn.quote(nan, c) + end + + def test_quote_float_infinity + infinity = 1.0/0 + c = Column.new(nil, 1, 'float') + assert_equal "'Infinity'", @conn.quote(infinity, c) + 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 3a7f1badf0..9208f53997 100644 --- a/activerecord/test/cases/adapters/postgresql/schema_test.rb +++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb @@ -10,32 +10,41 @@ class SchemaTest < ActiveRecord::TestCase INDEX_A_NAME = 'a_index_things_on_name' INDEX_B_NAME = 'b_index_things_on_different_columns_in_each_schema' INDEX_C_NAME = 'c_index_full_text_search' + INDEX_D_NAME = 'd_index_things_on_description_desc' INDEX_A_COLUMN = 'name' INDEX_B_COLUMN_S1 = 'email' INDEX_B_COLUMN_S2 = 'moment' INDEX_C_COLUMN = %q{(to_tsvector('english', coalesce(things.name, '')))} + INDEX_D_COLUMN = 'description' COLUMNS = [ 'id integer', 'name character varying(50)', 'email character varying(50)', + 'description character varying(100)', '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 + self.table_name = 'things' end def setup @@ -50,7 +59,11 @@ class SchemaTest < ActiveRecord::TestCase @connection.execute "CREATE INDEX #{INDEX_B_NAME} ON #{SCHEMA2_NAME}.#{TABLE_NAME} USING btree (#{INDEX_B_COLUMN_S2});" @connection.execute "CREATE INDEX #{INDEX_C_NAME} ON #{SCHEMA_NAME}.#{TABLE_NAME} USING gin (#{INDEX_C_COLUMN});" @connection.execute "CREATE INDEX #{INDEX_C_NAME} ON #{SCHEMA2_NAME}.#{TABLE_NAME} USING gin (#{INDEX_C_COLUMN});" + @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 @@ -58,6 +71,57 @@ 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 + # 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? [Thing1, Thing2, Thing3, Thing4].each do |klass| name = klass.table_name @@ -91,6 +155,12 @@ class SchemaTest < ActiveRecord::TestCase end end + def test_table_exists_quoted_table + with_schema_search_path(SCHEMA_NAME) do + assert(@connection.table_exists?('"things.table"'), "table should exist") + end + end + def test_with_schema_prefixed_table_name assert_nothing_raised do assert_equal COLUMNS, columns("#{SCHEMA_NAME}.#{TABLE_NAME}") @@ -152,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 @@ -166,11 +236,15 @@ class SchemaTest < ActiveRecord::TestCase end def test_dump_indexes_for_schema_one - do_dump_index_tests_for_schema(SCHEMA_NAME, INDEX_A_COLUMN, INDEX_B_COLUMN_S1) + do_dump_index_tests_for_schema(SCHEMA_NAME, INDEX_A_COLUMN, INDEX_B_COLUMN_S1, INDEX_D_COLUMN) end def test_dump_indexes_for_schema_two - do_dump_index_tests_for_schema(SCHEMA2_NAME, INDEX_A_COLUMN, INDEX_B_COLUMN_S2) + do_dump_index_tests_for_schema(SCHEMA2_NAME, INDEX_A_COLUMN, INDEX_B_COLUMN_S2, INDEX_D_COLUMN) + end + + def test_dump_indexes_for_schema_multiple_schemas_in_search_path + do_dump_index_tests_for_schema("public, #{SCHEMA_NAME}", INDEX_A_COLUMN, INDEX_B_COLUMN_S1, INDEX_D_COLUMN) end def test_with_uppercase_index_name @@ -210,27 +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}" - end - end - - def test_extract_schema_and_table - { - %(table_name) => [nil,'table_name'], - %("table.name") => [nil,'table.name'], - %(schema.table_name) => %w{schema table_name}, - %("schema".table_name) => %w{schema table_name}, - %(schema."table_name") => %w{schema table_name}, - %("schema"."table_name") => %w{schema table_name}, - %("even spaces".table) => ['even spaces','table'], - %(schema."table.name") => ['schema', 'table.name'] - }.each do |given, expect| - assert_equal expect, ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::Utils.extract_schema_and_table(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 @@ -245,6 +304,21 @@ class SchemaTest < ActiveRecord::TestCase end end + def test_prepared_statements_with_multiple_schemas + + @connection.schema_search_path = SCHEMA_NAME + Thing5.create(:id => 1, :name => "thing inside #{SCHEMA_NAME}", :email => "thing1@localhost", :moment => Time.now) + + @connection.schema_search_path = SCHEMA2_NAME + Thing5.create(:id => 1, :name => "thing inside #{SCHEMA2_NAME}", :email => "thing1@localhost", :moment => Time.now) + + @connection.schema_search_path = SCHEMA_NAME + assert_equal 1, Thing5.count + + @connection.schema_search_path = SCHEMA2_NAME + assert_equal 1, Thing5.count + end + def test_schema_exists? { 'public' => true, @@ -270,13 +344,16 @@ class SchemaTest < ActiveRecord::TestCase @connection.schema_search_path = "'$user', public" end - def do_dump_index_tests_for_schema(this_schema_name, first_index_column_name, second_index_column_name) + def do_dump_index_tests_for_schema(this_schema_name, first_index_column_name, second_index_column_name, third_index_column_name) with_schema_search_path(this_schema_name) do indexes = @connection.indexes(TABLE_NAME).sort_by {|i| i.name} - assert_equal 2,indexes.size + assert_equal 3,indexes.size do_dump_index_assertions_for_one_index(indexes[0], INDEX_A_NAME, first_index_column_name) do_dump_index_assertions_for_one_index(indexes[1], INDEX_B_NAME, second_index_column_name) + do_dump_index_assertions_for_one_index(indexes[2], INDEX_D_NAME, third_index_column_name) + + assert_equal :desc, indexes.select{|i| i.name == INDEX_D_NAME}[0].orders[INDEX_D_COLUMN] end end diff --git a/activerecord/test/cases/adapters/postgresql/statement_pool_test.rb b/activerecord/test/cases/adapters/postgresql/statement_pool_test.rb new file mode 100644 index 0000000000..f1c4b85126 --- /dev/null +++ b/activerecord/test/cases/adapters/postgresql/statement_pool_test.rb @@ -0,0 +1,39 @@ +require 'cases/helper' + +module ActiveRecord::ConnectionAdapters + class PostgreSQLAdapter < AbstractAdapter + class InactivePGconn + def query(*args) + raise PGError + end + + def status + PGconn::CONNECTION_BAD + end + end + + class StatementPoolTest < ActiveRecord::TestCase + def test_cache_is_per_pid + return skip('must support fork') unless Process.respond_to?(:fork) + + cache = StatementPool.new nil, 10 + cache['foo'] = 'bar' + assert_equal 'bar', cache['foo'] + + pid = fork { + lookup = cache['foo']; + exit!(!lookup) + } + + Process.waitpid pid + assert $?.success?, 'process should exit successfully' + end + + def test_dealloc_does_not_raise_on_inactive_connection + cache = StatementPool.new InactivePGconn.new, 10 + cache['foo'] = 'bar' + assert_nothing_raised { cache.clear } + end + end + end +end diff --git a/activerecord/test/cases/adapters/postgresql/utils_test.rb b/activerecord/test/cases/adapters/postgresql/utils_test.rb new file mode 100644 index 0000000000..9e7b08ef34 --- /dev/null +++ b/activerecord/test/cases/adapters/postgresql/utils_test.rb @@ -0,0 +1,20 @@ +require 'cases/helper' + +class PostgreSQLUtilsTest < ActiveSupport::TestCase + include ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::Utils + + def test_extract_schema_and_table + { + %(table_name) => [nil,'table_name'], + %("table.name") => [nil,'table.name'], + %(schema.table_name) => %w{schema table_name}, + %("schema".table_name) => %w{schema table_name}, + %(schema."table_name") => %w{schema table_name}, + %("schema"."table_name") => %w{schema table_name}, + %("even spaces".table) => ['even spaces','table'], + %(schema."table.name") => ['schema', 'table.name'] + }.each do |given, expect| + assert_equal expect, extract_schema_and_table(given) + end + end +end 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/copy_table_test.rb b/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb index 575b4806c1..7eef4ace81 100644 --- a/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb @@ -57,6 +57,14 @@ class CopyTableTest < ActiveRecord::TestCase end end + def test_copy_table_with_unconventional_primary_key + test_copy_table('owners', 'owners_unconventional') do |from, to, options| + original_pk = @connection.primary_key('owners') + copied_pk = @connection.primary_key('owners_unconventional') + assert_equal original_pk, copied_pk + end + end + protected def copy_table(from, to, options = {}) @connection.copy_table(from, to, {:temporary => true}.merge(options)) diff --git a/activerecord/test/cases/adapters/sqlite3/explain_test.rb b/activerecord/test/cases/adapters/sqlite3/explain_test.rb new file mode 100644 index 0000000000..b227bce680 --- /dev/null +++ b/activerecord/test/cases/adapters/sqlite3/explain_test.rb @@ -0,0 +1,26 @@ +require "cases/helper" +require 'models/developer' + +module ActiveRecord + module ConnectionAdapters + class SQLite3Adapter + class ExplainTest < ActiveRecord::TestCase + fixtures :developers + + def test_explain_for_one_query + explain = Developer.where(:id => 1).explain + assert_match %(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = 1), explain + assert_match(/(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 + end + 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 6ff04e3eb3..8a7f44d0a3 100644 --- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb @@ -1,9 +1,12 @@ # encoding: utf-8 require "cases/helper" +require 'models/owner' module ActiveRecord module ConnectionAdapters class SQLite3AdapterTest < ActiveRecord::TestCase + self.use_transactional_fixtures = false + class DualEncoding < ActiveRecord::Base end @@ -19,6 +22,24 @@ module ActiveRecord eosql end + def test_column_types + owner = Owner.create!(:name => "hello".encode('ascii-8bit')) + owner.reload + select = Owner.columns.map { |c| "typeof(#{c.name})" }.join ', ' + result = Owner.connection.exec_query <<-esql + SELECT #{select} + FROM #{Owner.table_name} + WHERE #{Owner.primary_key} = #{owner.id} + esql + + assert(!result.rows.first.include?("blob"), "should not store blobs") + end + + def test_time_column + owner = Owner.create!(:eats_at => Time.utc(1995,1,1,6,0)) + assert_match(/1995-01-01/, owner.reload.eats_at.to_s) + end + def test_exec_insert column = @conn.columns('items').find { |col| col.name == 'number' } vals = [[column, 10]] @@ -42,18 +63,6 @@ module ActiveRecord end end - def test_connection_no_adapter - assert_raises(ArgumentError) do - Base.sqlite3_connection :database => ':memory:' - end - end - - def test_connection_wrong_adapter - assert_raises(ArgumentError) do - Base.sqlite3_connection :database => ':memory:',:adapter => 'vuvuzela' - end - end - def test_bad_timeout assert_raises(TypeError) do Base.sqlite3_connection :database => ':memory:', @@ -126,8 +135,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, @@ -139,6 +146,9 @@ module ActiveRecord binary = DualEncoding.new :name => 'いただきます!', :data => str binary.save! assert_equal str, binary.data + + ensure + 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 new file mode 100644 index 0000000000..2f04c60a9a --- /dev/null +++ b/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb @@ -0,0 +1,24 @@ +require 'cases/helper' + +module ActiveRecord::ConnectionAdapters + class SQLite3Adapter + class StatementPoolTest < ActiveRecord::TestCase + def test_cache_is_per_pid + return skip('must support fork') unless Process.respond_to?(:fork) + + cache = StatementPool.new nil, 10 + cache['foo'] = 'bar' + assert_equal 'bar', cache['foo'] + + pid = fork { + lookup = cache['foo']; + exit!(!lookup) + } + + Process.waitpid pid + assert $?.success?, 'process should exit successfully' + end + end + end +end + diff --git a/activerecord/test/cases/aggregations_test.rb b/activerecord/test/cases/aggregations_test.rb index 3e0e6dce2c..5bd8f76ba2 100644 --- a/activerecord/test/cases/aggregations_test.rb +++ b/activerecord/test/cases/aggregations_test.rb @@ -109,6 +109,24 @@ class AggregationsTest < ActiveRecord::TestCase assert_nil customers(:david).gps_location end + def test_nil_return_from_converter_is_respected_when_allow_nil_is_true + customers(:david).non_blank_gps_location = "" + customers(:david).save + customers(:david).reload + assert_nil customers(:david).non_blank_gps_location + end + + def test_nil_return_from_converter_results_in_failure_when_allow_nil_is_false + assert_raises(NoMethodError) do + customers(:barney).gps_location = "" + end + end + + def test_do_not_run_the_converter_when_nil_was_set + customers(:david).non_blank_gps_location = nil + assert_nil Customer.gps_conversion_was_run + end + def test_custom_constructor assert_equal 'Barney GUMBLE', customers(:barney).fullname.to_s assert_kind_of Fullname, customers(:barney).fullname diff --git a/activerecord/test/cases/ar_schema_test.rb b/activerecord/test/cases/ar_schema_test.rb index 588adc38e3..b2eac0349b 100644 --- a/activerecord/test/cases/ar_schema_test.rb +++ b/activerecord/test/cases/ar_schema_test.rb @@ -37,6 +37,13 @@ if ActiveRecord::Base.connection.supports_migrations? end end end + + def test_schema_subclass + Class.new(ActiveRecord::Schema).define(:version => 9) do + create_table :fruits + end + assert_nothing_raised { @connection.select_all "SELECT * FROM fruits" } + end end end diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb index 818902beb5..2c7a240915 100644 --- a/activerecord/test/cases/associations/belongs_to_associations_test.rb +++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb @@ -13,6 +13,7 @@ require 'models/comment' require 'models/sponsor' require 'models/member' require 'models/essay' +require 'models/toy' class BelongsToAssociationsTest < ActiveRecord::TestCase fixtures :accounts, :companies, :developers, :projects, :topics, @@ -72,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 @@ -167,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 @@ -180,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 @@ -332,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 @@ -352,6 +354,12 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase assert_equal members(:groucho), sponsor.sponsorable end + def test_dont_find_target_when_foreign_key_is_null + tagging = taggings(:thinking_general) + queries = assert_sql { tagging.super_tag } + assert_equal 0, queries.length + end + def test_field_name_same_as_foreign_key computer = Computer.find(1) assert_not_nil computer.developer, ":foreign key == attribute didn't lock up" # ' @@ -386,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 @@ -490,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 @@ -510,7 +518,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase authors(:david).destroy end - assert_equal [], AuthorAddress.find_all_by_id([author_address.id, author_address_extra.id]) + assert_equal [], AuthorAddress.where(id: [author_address.id, author_address_extra.id]) assert_equal [author_address.id], AuthorAddress.destroyed_author_address_ids end @@ -690,4 +698,34 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase assert_equal nil, comment.reload.parent assert_equal 0, comments(:greetings).reload.children_count end + + def test_polymorphic_with_custom_primary_key + toy = Toy.create! + sponsor = Sponsor.create!(:sponsorable => toy) + + 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..08467900f9 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,10 +186,10 @@ 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! + assert_nil post_with_comments.comments.to_a.uniq! end end @@ -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 490fc5177e..d7c489c2b5 100644 --- a/activerecord/test/cases/associations/extension_test.rb +++ b/activerecord/test/cases/associations/extension_test.rb @@ -61,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 d8d2a113ff..ed1caa2ef5 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') @@ -123,11 +123,6 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase assert active_record.developers.include?(david) end - def test_triple_equality - assert !(Array === Developer.find(1).projects) - assert Developer.find(1).projects === Array - end - def test_adding_single jamis = Developer.find(2) jamis.projects.reload # causing the collection to load @@ -338,6 +333,12 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase assert_equal 3, project.developers.size end + def test_uniq_when_association_already_loaded + project = projects(:active_record) + project.developers << [ developers(:jamis), developers(:david), developers(:jamis), developers(:david) ] + assert_equal 3, Project.includes(:developers).find(project.id).developers.size + end + def test_deleting david = Developer.find(1) active_record = Project.find(1) @@ -355,7 +356,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,10 +376,16 @@ 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 + def test_deleting_all_with_sql + project = Project.find(1) + project.developers_by_sql.delete_all + assert_equal 0, project.developers_by_sql.size + end + def test_deleting_all david = Developer.find(1) david.projects.reload @@ -445,6 +452,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 @@ -477,7 +504,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase def test_include_uses_array_include_after_loaded project = projects(:active_record) - project.developers.class # force load target + project.developers.load_target developer = project.developers.first @@ -528,43 +555,23 @@ 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 - def test_dynamic_find_all_should_respect_association_order - # Developers are ordered 'name DESC, id DESC' - low_id_jamis = developers(:jamis) - 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.find_all_by_name('Jamis') - end - def test_find_should_append_to_association_order ordered_developers = projects(:active_record).developers.order('projects.id') assert_equal ['developers.name desc, developers.id desc', 'projects.id'], ordered_developers.order_values 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.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.find_all_by_name('Jamis', :limit => 9_000).length - end - def test_dynamic_find_all_should_respect_readonly_access projects(:active_record).readonly_developers.each { |d| assert_raise(ActiveRecord::ReadOnlyRecord) { d.save! } if d.valid?} projects(:active_record).readonly_developers.each { |d| d.readonly? } @@ -612,7 +619,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,11 +654,25 @@ 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 + assert_equal Project.column_names.sort, developers(:david).projects.first.attributes.keys.sort + end + + def test_habtm_respects_select_query_method + assert_equal ['id'], developers(:david).projects.select(:id).first.attributes.keys 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 @@ -661,12 +682,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 @@ -740,8 +767,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 @@ -758,13 +785,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 @@ -787,7 +807,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 @@ -797,12 +817,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 a2764f3e3b..8b384c2513 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' @@ -17,6 +18,7 @@ require 'models/invoice' require 'models/line_item' require 'models/car' require 'models/bulb' +require 'models/engine' class HasManyAssociationsTestForCountWithFinderSql < ActiveRecord::TestCase class Invoice < ActiveRecord::Base @@ -40,12 +42,37 @@ class HasManyAssociationsTestForCountWithCountSql < ActiveRecord::TestCase end end +class HasManyAssociationsTestForCountDistinctWithFinderSql < ActiveRecord::TestCase + class Invoice < ActiveRecord::Base + has_many :custom_line_items, :class_name => 'LineItem', :finder_sql => "SELECT DISTINCT line_items.amount from line_items" + end + + def test_should_count_distinct_results + invoice = Invoice.new + invoice.custom_line_items << LineItem.new(:amount => 0) + invoice.custom_line_items << LineItem.new(:amount => 0) + invoice.save! + + assert_equal 1, invoice.custom_line_items.count + 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 @@ -103,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') @@ -172,48 +221,25 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal person, person.readers.first.person end - def test_find_or_create_by_resets_cached_counters - person = Person.create! :first_name => 'tenderlove' - post = Post.first - - assert_equal [], person.readers - assert_nil person.readers.find_by_post_id(post.id) - - person.readers.find_or_create_by_post_id(post.id) - - assert_equal 1, person.readers.count - assert_equal 1, person.readers.length - assert_equal post, person.readers.first.post - assert_equal person, person.readers.first.person - end - def force_signal37_to_load_all_clients_of_firm companies(:first_firm).clients_of_firm.each {|f| } end # 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 @@ -223,7 +249,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 @@ -232,14 +258,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 @@ -247,97 +273,65 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal ['id DESC', 'companies.id'], ordered_clients.order_values end - def test_dynamic_find_last_without_specified_order - assert_equal companies(:second_client), companies(:first_firm).unsorted_clients.find_last_by_type('Client') - 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.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.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.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 - another = author.posts.find_or_create_by_title_and_body("Another Post", "This is the Body") - assert_equal number_of_posts + 1, Post.count - assert_equal another, author.posts.find_or_create_by_title_and_body("Another Post", "This is the Body") - assert another.persisted? - end - def test_cant_save_has_many_readonly_association authors(:david).readonly_comments.each { |c| assert_raise(ActiveRecord::ReadOnlyRecord) { c.save! } } authors(:david).readonly_comments.each { |c| assert c.readonly? } end - 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 - 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 @@ -352,7 +346,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 } @@ -372,7 +366,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 @@ -388,9 +382,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 @@ -409,7 +403,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 @@ -434,29 +428,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 @@ -466,8 +460,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 @@ -484,6 +478,14 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal 0, authors(:mary).popular_grouped_posts.length end + def test_default_select + assert_equal Comment.column_names.sort, posts(:welcome).comments.first.attributes.keys.sort + end + + def test_select_query_method + assert_equal ['id'], posts(:welcome).comments.select(:id).first.attributes.keys + end + def test_adding force_signal37_to_load_all_clients_of_firm natural = Client.new("name" => "Natural Company") @@ -517,7 +519,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 @@ -682,45 +684,6 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert !companies(:first_firm).clients_of_firm.loaded? end - def test_find_or_initialize - the_client = companies(:first_firm).clients.find_or_initialize_by_name("Yet another client") - assert_equal companies(:first_firm).id, the_client.firm_id - assert_equal "Yet another client", the_client.name - assert !the_client.persisted? - end - - def test_find_or_create_updates_size - number_of_clients = companies(:first_firm).clients.size - the_client = companies(:first_firm).clients.find_or_create_by_name("Yet another client") - assert_equal number_of_clients + 1, companies(:first_firm, :reload).clients.size - assert_equal the_client, companies(:first_firm).clients.find_or_create_by_name("Yet another client") - assert_equal number_of_clients + 1, companies(:first_firm, :reload).clients.size - end - - def test_find_or_initialize_updates_collection_size - number_of_clients = companies(:first_firm).clients_of_firm.size - companies(:first_firm).clients_of_firm.find_or_initialize_by_name("name" => "Another Client") - assert_equal number_of_clients + 1, companies(:first_firm).clients_of_firm.size - 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') - assert post.persisted? - end - - def test_find_or_create_with_one_attribute_followed_by_hash - post = authors(:david).posts.find_or_create_by_title('Yet another post', :body => 'somebody') - assert_equal post, authors(:david).posts.find_or_create_by_title('Yet another post', :body => 'somebody') - assert post.persisted? - end - - def test_find_or_create_should_work_with_block - post = authors(:david).posts.find_or_create_by_title('Yet another post') {|p| p.body = 'somebody'} - assert_equal post, authors(:david).posts.find_or_create_by_title('Yet another post') {|p| p.body = 'somebody'} - assert post.persisted? - end - def test_deleting force_signal37_to_load_all_clients_of_firm companies(:first_firm).clients_of_firm.delete(companies(:first_firm).clients_of_firm.first) @@ -737,7 +700,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) @@ -829,12 +792,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase client_id = firm.clients_of_firm.first.id assert_equal 1, firm.clients_of_firm.size - cleared = firm.clients_of_firm.clear + firm.clients_of_firm.clear assert_equal 0, firm.clients_of_firm.size assert_equal 0, firm.clients_of_firm(true).size assert_equal [], Client.destroyed_client_ids[firm.id] - assert_equal firm.clients_of_firm.object_id, cleared.object_id # Should not be destroyed since the association is not dependent. assert_nothing_raised do @@ -850,6 +812,15 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end end + def test_clearing_updates_counter_cache_when_inverse_counter_cache_is_a_symbol_with_dependent_destroy + car = Car.first + car.engines.create! + + assert_difference 'car.reload.engines_count', -1 do + car.engines.clear + end + end + def test_clearing_a_dependent_association_collection firm = companies(:first_firm) client_id = firm.dependent_clients_of_firm.first.id @@ -891,11 +862,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase Client.create(:client_of => firm.id, :name => "BigShot Inc.") Client.create(:client_of => firm.id, :name => "SmallTime Inc.") # only one of two clients is included in the association due to the :conditions key - assert_equal 2, Client.find_all_by_client_of(firm.id).size + assert_equal 2, Client.where(client_of: firm.id).size assert_equal 1, firm.dependent_conditional_clients_of_firm.size firm.destroy # only the correctly associated client should have been deleted - assert_equal 1, Client.find_all_by_client_of(firm.id).size + assert_equal 1, Client.where(client_of: firm.id).size end def test_dependent_association_respects_optional_sanitized_conditions_on_delete @@ -903,11 +874,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase Client.create(:client_of => firm.id, :name => "BigShot Inc.") Client.create(:client_of => firm.id, :name => "SmallTime Inc.") # only one of two clients is included in the association due to the :conditions key - assert_equal 2, Client.find_all_by_client_of(firm.id).size + assert_equal 2, Client.where(client_of: firm.id).size assert_equal 1, firm.dependent_sanitized_conditional_clients_of_firm.size firm.destroy # only the correctly associated client should have been deleted - assert_equal 1, Client.find_all_by_client_of(firm.id).size + assert_equal 1, Client.where(client_of: firm.id).size end def test_dependent_association_respects_optional_hash_conditions_on_delete @@ -915,22 +886,22 @@ class HasManyAssociationsTest < ActiveRecord::TestCase Client.create(:client_of => firm.id, :name => "BigShot Inc.") Client.create(:client_of => firm.id, :name => "SmallTime Inc.") # only one of two clients is included in the association due to the :conditions key - assert_equal 2, Client.find_all_by_client_of(firm.id).size + assert_equal 2, Client.where(client_of: firm.id).size assert_equal 1, firm.dependent_sanitized_conditional_clients_of_firm.size firm.destroy # only the correctly associated client should have been deleted - assert_equal 1, Client.find_all_by_client_of(firm.id).size + assert_equal 1, Client.where(client_of: firm.id).size 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 @@ -1038,7 +1009,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 @@ -1048,7 +1019,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 @@ -1076,7 +1047,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 @@ -1096,16 +1067,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 @@ -1113,16 +1110,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 @@ -1136,7 +1128,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 @@ -1209,6 +1201,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, ''] @@ -1232,29 +1228,10 @@ 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.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.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.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 - end - def test_has_many_through_respects_hash_conditions assert_equal authors(:david).hello_posts, authors(:david).hello_posts_with_hash_conditions assert_equal authors(:david).hello_post_comments, authors(:david).hello_post_comments_with_hash_conditions @@ -1262,7 +1239,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_include_uses_array_include_after_loaded firm = companies(:first_firm) - firm.clients.class # force load target + firm.clients.load_target client = firm.clients.first @@ -1312,7 +1289,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_calling_first_or_last_on_loaded_association_should_not_fetch_with_query firm = companies(:first_firm) - firm.clients.class # force load target + firm.clients.load_target assert firm.clients.loaded? assert_no_queries do @@ -1358,14 +1335,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.where(writer_id: "David") + + end + + def test_has_many_custom_primary_key + david = authors(:david) + assert_equal david.essays, Essay.where(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 @@ -1426,11 +1418,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 @@ -1439,7 +1431,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 @@ -1456,7 +1448,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 @@ -1568,4 +1560,70 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal car.id, bulb.attributes_after_initialize['car_id'] end + + def test_replace + car = Car.create(:name => 'honda') + bulb1 = car.bulbs.create + bulb2 = Bulb.create + + assert_equal [bulb1], car.bulbs + car.bulbs.replace([bulb2]) + assert_equal [bulb2], car.bulbs + assert_equal [bulb2], car.reload.bulbs + end + + def test_replace_returns_target + car = Car.create(:name => 'honda') + bulb1 = car.bulbs.create + bulb2 = car.bulbs.create + bulb3 = Bulb.create + + assert_equal [bulb1, bulb2], car.bulbs + result = car.bulbs.replace([bulb3, bulb1]) + assert_equal [bulb1, bulb3], car.bulbs + assert_equal [bulb1, bulb3], result + 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 + + test "first_or_initialize adds the record to the association" do + firm = Firm.create! name: 'omg' + client = firm.clients_of_firm.first_or_initialize + assert_equal [client], firm.clients_of_firm + end + + test "first_or_create adds the record to the association" do + firm = Firm.create! name: 'omg' + firm.clients_of_firm.load_target + client = firm.clients_of_firm.first_or_create name: 'lol' + assert_equal [client], firm.clients_of_firm + assert_equal [client], firm.reload.clients_of_firm + end + + test "delete_all, when not loaded, doesn't load the records" do + post = posts(:welcome) + + assert post.taggings_with_delete_all.count > 0 + assert !post.taggings_with_delete_all.loaded? + + # 2 queries: one DELETE and another to update the counter cache + assert_queries(2) do + post.taggings_with_delete_all.delete_all + end + end end diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb index 5f2328ff95..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 @@ -67,6 +83,31 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase end end + def test_associate_existing_record_twice_should_add_records_twice + post = posts(:thinking) + person = people(:david) + + assert_difference 'post.people.count', 2 do + post.people << person + post.people << person + end + end + + def test_add_two_instance_and_then_deleting + post = posts(:thinking) + person = people(:david) + + post.people << person + post.people << person + + counts = ['post.people.count', 'post.people.to_a.count', 'post.readers.count', 'post.readers.to_a.count'] + assert_difference counts, -2 do + post.people.delete(person) + end + + assert !post.people.reload.include?(person) + end + def test_associating_new assert_queries(1) { posts(:thinking) } new_person = nil # so block binding catches it @@ -492,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 @@ -503,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) @@ -521,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 @@ -604,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 @@ -624,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 @@ -657,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 @@ -707,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 @@ -816,9 +874,18 @@ 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 end + + def test_explicitly_joining_join_table + assert_equal owners(:blackbeard).toys, owners(:blackbeard).toys.with_pet + end + + def test_has_many_through_with_polymorphic_source + post = tags(:general).tagged_posts.create! :title => "foo", :body => "bar" + assert_equal [tags(:general)], post.reload.tags + end end diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb index 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..ecc676f300 100644 --- a/activerecord/test/cases/associations/join_model_test.rb +++ b/activerecord/test/cases/associations/join_model_test.rb @@ -46,16 +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 - end - - def test_has_many_uniq_through_dynamic_find - assert_equal 1, authors(:mary).unique_categorized_posts.find_all_by_title("So I was thinking").size + assert_equal 1, authors(:mary).unique_categorized_posts.all.size end def test_polymorphic_has_many_going_through_join_model @@ -71,7 +67,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 +81,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 +233,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 +242,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 +250,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 +258,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 +267,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,25 +276,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'") - end - - def test_has_many_class_methods_called_by_method_missing - assert_equal categories(:general), authors(:david).categories.find_all_by_name('General').first - assert_nil authors(:david).categories.find_by_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_array_methods_called_by_method_missing @@ -327,14 +318,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 +345,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 +355,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 +365,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 +394,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 +402,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 +410,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 +418,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 +435,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 +605,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 +613,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 +632,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 +645,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 +659,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 @@ -684,7 +667,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase def test_has_many_through_include_uses_array_include_after_loaded david = authors(:david) - david.categories.class # force load target + david.categories.load_target category = david.categories.first @@ -733,7 +716,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 80c6e41169..03d99d19f6 100644 --- a/activerecord/test/cases/associations/nested_through_associations_test.rb +++ b/activerecord/test/cases/associations/nested_through_associations_test.rb @@ -356,6 +356,17 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase assert_equal categories(:general), members(:groucho).club_category end + def test_joins_and_includes_from_through_models_not_included_in_association + prev_default_scope = Club.default_scopes + + [:includes, :preload, :joins, :eager_load].each do |q| + Club.default_scopes = [Club.send(q, :category)] + assert_equal categories(:general), members(:groucho).reload.club_category + end + ensure + Club.default_scopes = prev_default_scope + end + def test_has_one_through_has_one_through_with_belongs_to_source_reflection_preload members = assert_queries(4) { Member.includes(:club_category).to_a.sort_by(&:id) } general = categories(:general) @@ -494,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 @@ -515,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 @@ -534,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 3641031d12..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,8 +40,6 @@ module ActiveRecord [name, FakeColumn.new(name)] }] end - - def self.serialized_attributes; {}; end end end diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index dbf5a1ba76..1093fedea1 100644 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -30,9 +30,36 @@ 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 + b1 = Boolean.new + b1.value = false + assert b1.attribute_present?(:value) + + b2 = Boolean.new + b2.value = true + assert b2.attribute_present?(:value) + + b3 = Boolean.new + assert !b3.attribute_present?(:value) + + b4 = Boolean.new + b4.value = false + b4.save! + 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 @@ -78,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 @@ -95,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 @@ -212,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 @@ -237,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 @@ -291,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 @@ -410,51 +481,27 @@ 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 - def test_kernel_methods_not_implemented_in_activerecord - %w(test name display y).each do |method| - assert !ActiveRecord::Base.instance_method_already_implemented?(method), "##{method} is defined" - end - end - - def test_defined_kernel_methods_implemented_in_model - %w(test name display y).each do |method| - klass = Class.new ActiveRecord::Base - klass.class_eval "def #{method}() 'defined #{method}' end" - assert klass.instance_method_already_implemented?(method), "##{method} is not defined" - end - end - - def test_defined_kernel_methods_implemented_in_model_abstract_subclass - %w(test name display y).each do |method| - abstract = Class.new ActiveRecord::Base - abstract.class_eval "def #{method}() 'defined #{method}' end" - abstract.abstract_class = true - klass = Class.new abstract - assert klass.instance_method_already_implemented?(method), "##{method} is not defined" - end - end - def test_raises_dangerous_attribute_error_when_defining_activerecord_method_in_model %w(save create_or_update).each do |method| klass = Class.new ActiveRecord::Base @@ -513,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) @@ -548,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| @@ -562,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 @@ -608,7 +685,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase topic = @target.new(:title => "The pros and cons of programming naked.") assert !topic.respond_to?(:title) exception = assert_raise(NoMethodError) { topic.title } - assert_match %r(^Attempt to call private method), exception.message + assert exception.message.include?("private method") assert_equal "I'm private", topic.send(:title) end @@ -618,7 +695,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase topic = @target.new assert !topic.respond_to?(:title=) exception = assert_raise(NoMethodError) { topic.title = "Pants"} - assert_match %r(^Attempt to call private method), exception.message + assert exception.message.include?("private method") topic.send(:title=, "Very large pants") end @@ -628,7 +705,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase topic = @target.new(:title => "Isaac Newton's pants") assert !topic.respond_to?(:title?) exception = assert_raise(NoMethodError) { topic.title? } - assert_match %r(^Attempt to call private method), exception.message + assert exception.message.include?("private method") assert topic.send(:title?) end @@ -659,17 +736,70 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert_equal %w(preferences), Contact.serialized_attributes.keys end + def test_instance_method_should_be_defined_on_the_base_class + subklass = Class.new(Topic) + + Topic.define_attribute_methods + + instance = subklass.new + instance.id = 5 + assert_equal 5, instance.id + assert subklass.method_defined?(:id), "subklass is missing id method" + + Topic.undefine_attribute_methods + + assert_equal 5, instance.id + assert subklass.method_defined?(:id), "subklass is missing id method" + end + + def test_dispatching_column_attributes_through_method_missing_deprecated + Topic.define_attribute_methods + + topic = Topic.new(:id => 5) + topic.id = 5 + + topic.method(:id).owner.send(:undef_method, :id) + + assert_deprecated do + assert_equal 5, topic.id + end + ensure + 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..8ef3bfef15 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 @@ -846,7 +872,7 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase 2.times { |i| @pirate.parrots.create!(:name => "parrots_#{i}") } before = @pirate.parrots.map { |c| c.mark_for_destruction ; c } - class << @pirate.parrots + class << @pirate.association(:parrots) def destroy(*args) super raise 'Oh noes!' @@ -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 @@ -1253,7 +1277,7 @@ module AutosaveAssociationOnACollectionAssociationTests def test_should_not_load_the_associated_models_if_they_were_not_loaded_yet assert_queries(1) { @pirate.catchphrase = 'Arr'; @pirate.save! } - @pirate.send(@association_name).class # hack to load the target + @pirate.send(@association_name).load_target assert_queries(3) do @pirate.catchphrase = 'Yarr' @@ -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 b8ebabfe70..619fb881fa 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -22,9 +22,19 @@ require 'models/person' 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 @@ -52,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 @@ -67,6 +81,39 @@ 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[/[^:]*$/] + badchar = { + 'SQLite3Adapter' => '"', + 'MysqlAdapter' => '`', + 'Mysql2Adapter' => '`', + 'PostgreSQLAdapter' => '"', + 'OracleAdapter' => '"', + }.fetch(classname) { + raise "need a bad char for #{classname}" + } + + quoted = conn.quote_column_name "foo#{badchar}bar" + if current_adapter?(:OracleAdapter) + # Oracle does not allow double quotes in table and column names at all + # therefore quoting removes them + assert_equal("#{badchar}foobar#{badchar}", quoted) + else + assert_equal("#{badchar}foo#{badchar * 2}bar#{badchar}", quoted) + end + end + def test_columns_should_obey_set_primary_key pk = Subscriber.columns.find { |x| x.name == 'nick' } assert pk.primary, 'nick should be primary key' @@ -78,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 @@ -120,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 @@ -144,6 +184,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, @@ -156,7 +221,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 @@ -254,14 +319,37 @@ class BasicsTest < ActiveRecord::TestCase assert_equal(true, cb.frickinawesome) end + def test_first_or_create + parrot = Bird.first_or_create(:color => 'green', :name => 'parrot') + assert parrot.persisted? + the_same_parrot = Bird.first_or_create(:color => 'yellow', :name => 'macaw') + assert_equal parrot, the_same_parrot + end + + def test_first_or_create_bang + assert_raises(ActiveRecord::RecordInvalid) { Bird.first_or_create! } + parrot = Bird.first_or_create!(:color => 'green', :name => 'parrot') + assert parrot.persisted? + the_same_parrot = Bird.first_or_create!(:color => 'yellow', :name => 'macaw') + assert_equal parrot, the_same_parrot + end + + def test_first_or_initialize + parrot = Bird.first_or_initialize(:color => 'green', :name => 'parrot') + assert_kind_of Bird, parrot + assert !parrot.persisted? + assert parrot.new_record? + assert parrot.valid? + 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) @@ -383,7 +471,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 @@ -416,7 +504,7 @@ class BasicsTest < ActiveRecord::TestCase end # Oracle, and Sybase do not have a TIME datatype. - unless current_adapter?(:OracleAdapter, :SybaseAdapter) + unless current_adapter?(:OracleAdapter, :SybaseAdapter, :SQLite3Adapter) def test_utc_as_time_zone Topic.default_timezone = :utc attributes = { "bonus_time" => "5:42:00AM" } @@ -513,10 +601,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 @@ -596,7 +686,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 @@ -656,6 +746,9 @@ class BasicsTest < ActiveRecord::TestCase end def test_multiparameter_attributes_on_time_will_ignore_hour_if_missing + ActiveRecord::Base.time_zone_aware_attributes = false + ActiveRecord::Base.default_timezone = :local + Time.zone = nil attributes = { "written_on(1i)" => "2004", "written_on(2i)" => "12", "written_on(3i)" => "12", "written_on(5i)" => "12", "written_on(6i)" => "02" @@ -762,7 +855,7 @@ class BasicsTest < ActiveRecord::TestCase end # Oracle, and Sybase do not have a TIME datatype. - unless current_adapter?(:OracleAdapter, :SybaseAdapter) + unless current_adapter?(:OracleAdapter, :SybaseAdapter, :SQLite3Adapter) def test_multiparameter_attributes_on_time_only_column_with_time_zone_aware_attributes_does_not_do_time_zone_conversion ActiveRecord::Base.time_zone_aware_attributes = true ActiveRecord::Base.default_timezone = :utc @@ -783,6 +876,9 @@ class BasicsTest < ActiveRecord::TestCase end def test_multiparameter_attributes_on_time_with_empty_seconds + ActiveRecord::Base.time_zone_aware_attributes = false + ActiveRecord::Base.default_timezone = :local + Time.zone = nil attributes = { "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24", "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "" @@ -838,14 +934,14 @@ class BasicsTest < ActiveRecord::TestCase def test_attributes_on_dummy_time # Oracle, and Sybase do not have a TIME datatype. - return true if current_adapter?(:OracleAdapter, :SybaseAdapter) + return true if current_adapter?(:OracleAdapter, :SybaseAdapter, :SQLite3Adapter) attributes = { "bonus_time" => "5:42:00AM" } 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 @@ -864,6 +960,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 @@ -899,10 +1005,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 @@ -913,8 +1018,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 @@ -1013,7 +1117,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 @@ -1045,7 +1152,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 @@ -1056,7 +1163,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 @@ -1073,7 +1181,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 @@ -1084,7 +1192,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 @@ -1137,19 +1246,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") @@ -1167,10 +1263,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 @@ -1187,6 +1283,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 @@ -1199,11 +1331,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) @@ -1311,22 +1456,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) @@ -1353,107 +1482,100 @@ 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 - end + def test_switching_between_table_name + assert_difference("GoodJoke.count") do + Joke.table_name = "cold_jokes" + Joke.create - def test_define_attr_method_with_block - k = Class.new( ActiveRecord::Base ) do - class << self - attr_accessor :foo_key - end + Joke.table_name = "funny_jokes" + Joke.create 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_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 + assert_not_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_sequence_name_when_setting_explicitly + Joke.sequence_name = "black_jokes_seq" + Joke.table_name = "cold_jokes" + before_seq = Joke.sequence_name - Joke.set_table_name "funny_jokes" - Joke.create - end + 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_dont_clear_inheritnce_column_when_setting_explicitly + Joke.inheritance_column = "my_type" + before_inherit = Joke.inheritance_column + + 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 - k = Class.new( ActiveRecord::Base ) - k.set_table_name { "ks" } - assert_equal "ks", 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 + def test_set_table_name_with_inheritance k = Class.new( ActiveRecord::Base ) - k.primary_key = 'id' - k.set_primary_key { "sys_" + original_primary_key } - assert_equal "sys_id", k.primary_key + def k.name; "Foo"; end + def k.table_name; super + "ks"; end + assert_equal "foosks", k.table_name 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 @@ -1461,145 +1583,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) - 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") + Developer.scoped(:offset => 2).all 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 @@ -1608,6 +1649,10 @@ class BasicsTest < ActiveRecord::TestCase assert !LooseDescendant.abstract_class? end + def test_abstract_class_table_name + assert_nil AbstractCompany.table_name + end + def test_base_class assert_equal LoosePerson, LoosePerson.base_class assert_equal LooseDescendant, LooseDescendant.base_class @@ -1621,10 +1666,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? @@ -1647,6 +1689,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 @@ -1669,7 +1715,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 @@ -1680,7 +1726,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 @@ -1688,8 +1734,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 @@ -1709,10 +1755,18 @@ class BasicsTest < ActiveRecord::TestCase assert_equal "The First Topic", topics(:first).becomes(Reply).title end + def test_becomes_includes_errors + company = Company.new(:name => nil) + assert !company.valid? + original_errors = company.errors + client = company.becomes(Client) + assert_equal original_errors, client.errors + end + 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" @@ -1726,7 +1780,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 @@ -1738,7 +1792,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 } @@ -1753,10 +1807,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 @@ -1788,9 +1840,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 @@ -1831,8 +1883,17 @@ 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"], + assert_equal ["id", "type", "ruby_type", "firm_id", "firm_name", "name", "client_of", "rating", "account_id", "description"], Company.attribute_names end @@ -1840,7 +1901,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 @@ -1860,7 +1921,7 @@ class BasicsTest < ActiveRecord::TestCase def test_cache_key_format_for_existing_record_with_updated_at dev = Developer.first - assert_equal "developers/#{dev.id}-#{dev.updated_at.utc.to_s(:number)}", dev.cache_key + assert_equal "developers/#{dev.id}-#{dev.updated_at.utc.to_s(:nsec)}", dev.cache_key end def test_cache_key_format_for_existing_record_with_nil_updated_at @@ -1868,4 +1929,84 @@ class BasicsTest < ActiveRecord::TestCase dev.update_attribute(:updated_at, nil) assert_match(/\/#{dev.id}$/, dev.cache_key) end + + def test_uniq_delegates_to_scoped + scope = stub + 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 + + def test_default_values_are_deeply_dupped + company = Company.new + company.description << "foo" + assert_equal "", Company.new.description + 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 a35baee4ed..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 } @@ -113,7 +101,27 @@ class EachTest < ActiveRecord::TestCase batch.map! { not_a_post } end end + end + + def test_find_in_batches_should_ignore_the_order_default_scope + # First post is with title scope + first_post = PostWithDefaultScope.first + posts = [] + PostWithDefaultScope.find_in_batches do |batch| + posts.concat(batch) + end + # posts.first will be ordered using id only. Title order scope should not apply here + assert_not_equal first_post, posts.first + assert_equal posts(:welcome), posts.first + end + def test_find_in_batches_should_not_ignore_the_default_scope_if_it_is_other_then_order + special_posts_ids = SpecialPostWithDefaultScope.all.map(&:id).sort + posts = [] + SpecialPostWithDefaultScope.find_in_batches do |batch| + posts.concat(batch) + end + assert_equal special_posts_ids, posts.map(&:id) end end 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/bind_parameter_test.rb b/activerecord/test/cases/bind_parameter_test.rb index 3652255c38..03aa9fdb27 100644 --- a/activerecord/test/cases/bind_parameter_test.rb +++ b/activerecord/test/cases/bind_parameter_test.rb @@ -23,6 +23,8 @@ module ActiveRecord @listener = LogListener.new @pk = Topic.columns.find { |c| c.primary } ActiveSupport::Notifications.subscribe('sql.active_record', @listener) + + skip_if_prepared_statement_caching_is_not_supported end def teardown @@ -30,9 +32,6 @@ module ActiveRecord end def test_binds_are_logged - # FIXME: use skip with minitest - return unless @connection.supports_statement_cache? - sub = @connection.substitute_at(@pk, 0) binds = [[@pk, 1]] sql = "select * from topics where id = #{sub}" @@ -44,9 +43,6 @@ module ActiveRecord end def test_find_one_uses_binds - # FIXME: use skip with minitest - return unless @connection.supports_statement_cache? - Topic.find(1) binds = [[@pk, 1]] message = @listener.calls.find { |args| args[4][:binds] == binds } @@ -54,9 +50,6 @@ module ActiveRecord end def test_logs_bind_vars - # FIXME: use skip with minitest - return unless @connection.supports_statement_cache? - pk = Topic.columns.find { |x| x.primary } payload = { @@ -86,5 +79,11 @@ module ActiveRecord logger.sql event assert_match([[pk.name, 10]].inspect, logger.debugs.first) end + + private + + def skip_if_prepared_statement_caching_is_not_supported + skip('prepared statement caching is not supported') unless @connection.supports_statement_cache? + end end end diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb index c38814713a..041f8ffb7c 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,64 @@ 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_pluck_with_selection_clause + assert_equal [50, 53, 55, 60], Account.pluck('DISTINCT credit_limit').sort + assert_equal [50, 53, 55, 60], Account.pluck('DISTINCT accounts.credit_limit').sort + assert_equal [50, 53, 55, 60], Account.pluck('DISTINCT(credit_limit)').sort + + # MySQL returns "SUM(DISTINCT(credit_limit))" as the column name unless + # an alias is provided. Without the alias, the column cannot be found + # and properly typecast. + assert_equal [50 + 53 + 55 + 60], Account.pluck('SUM(DISTINCT(credit_limit)) as credit_limit') + end + + def test_pluck_expects_a_single_selection + assert_raise(ArgumentError) { Account.pluck 'id, credit_limit' } + 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 d1dddd4c2c..a44b49466f 100644 --- a/activerecord/test/cases/column_definition_test.rb +++ b/activerecord/test/cases/column_definition_test.rb @@ -58,85 +58,88 @@ module ActiveRecord if current_adapter?(:MysqlAdapter) def test_should_set_default_for_mysql_binary_data_types - binary_column = MysqlColumn.new("title", "a", "binary(1)") + binary_column = MysqlAdapter::Column.new("title", "a", "binary(1)") assert_equal "a", binary_column.default - varbinary_column = MysqlColumn.new("title", "a", "varbinary(1)") + varbinary_column = MysqlAdapter::Column.new("title", "a", "varbinary(1)") assert_equal "a", varbinary_column.default end def test_should_not_set_default_for_blob_and_text_data_types assert_raise ArgumentError do - MysqlColumn.new("title", "a", "blob") + MysqlAdapter::Column.new("title", "a", "blob") end assert_raise ArgumentError do - MysqlColumn.new("title", "Hello", "text") + MysqlAdapter::Column.new("title", "Hello", "text") end - text_column = MysqlColumn.new("title", nil, "text") + text_column = MysqlAdapter::Column.new("title", nil, "text") assert_equal nil, text_column.default - not_null_text_column = MysqlColumn.new("title", nil, "text", false) + not_null_text_column = MysqlAdapter::Column.new("title", nil, "text", false) assert_equal "", not_null_text_column.default end def test_has_default_should_return_false_for_blog_and_test_data_types - blob_column = MysqlColumn.new("title", nil, "blob") + blob_column = MysqlAdapter::Column.new("title", nil, "blob") assert !blob_column.has_default? - text_column = MysqlColumn.new("title", nil, "text") + text_column = MysqlAdapter::Column.new("title", nil, "text") assert !text_column.has_default? end end if current_adapter?(:Mysql2Adapter) def test_should_set_default_for_mysql_binary_data_types - binary_column = Mysql2Column.new("title", "a", "binary(1)") + binary_column = Mysql2Adapter::Column.new("title", "a", "binary(1)") assert_equal "a", binary_column.default - varbinary_column = Mysql2Column.new("title", "a", "varbinary(1)") + varbinary_column = Mysql2Adapter::Column.new("title", "a", "varbinary(1)") assert_equal "a", varbinary_column.default end def test_should_not_set_default_for_blob_and_text_data_types assert_raise ArgumentError do - Mysql2Column.new("title", "a", "blob") + Mysql2Adapter::Column.new("title", "a", "blob") end assert_raise ArgumentError do - Mysql2Column.new("title", "Hello", "text") + Mysql2Adapter::Column.new("title", "Hello", "text") end - text_column = Mysql2Column.new("title", nil, "text") + text_column = Mysql2Adapter::Column.new("title", nil, "text") assert_equal nil, text_column.default - not_null_text_column = Mysql2Column.new("title", nil, "text", false) + not_null_text_column = Mysql2Adapter::Column.new("title", nil, "text", false) assert_equal "", not_null_text_column.default end def test_has_default_should_return_false_for_blog_and_test_data_types - blob_column = Mysql2Column.new("title", nil, "blob") + blob_column = Mysql2Adapter::Column.new("title", nil, "blob") assert !blob_column.has_default? - text_column = Mysql2Column.new("title", nil, "text") + text_column = Mysql2Adapter::Column.new("title", nil, "text") assert !text_column.has_default? end end 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..4111a5f808 --- /dev/null +++ b/activerecord/test/cases/column_test.rb @@ -0,0 +1,81 @@ +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 + + def test_type_cast_time + column = Column.new("field", nil, "time") + assert_equal nil, column.type_cast('') + assert_equal nil, column.type_cast(' ') + + time_string = Time.now.utc.strftime("%T") + assert_equal time_string, column.type_cast(time_string).strftime("%T") + end + + def test_type_cast_datetime_and_timestamp + [Column.new("field", nil, "datetime"), Column.new("field", nil, "timestamp")].each do |column| + assert_equal nil, column.type_cast('') + assert_equal nil, column.type_cast(' ') + + datetime_string = Time.now.utc.strftime("%FT%T") + assert_equal datetime_string, column.type_cast(datetime_string).strftime("%FT%T") + end + end + + def test_type_cast_date + column = Column.new("field", nil, "date") + assert_equal nil, column.type_cast('') + assert_equal nil, column.type_cast(' ') + + date_string = Time.now.utc.strftime("%F") + assert_equal date_string, column.type_cast(date_string).strftime("%F") + 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..3e3d6e2769 --- /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.insert_connection_for_test! 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 abf317768f..dc99ac665c 100644 --- a/activerecord/test/cases/connection_adapters/connection_handler_test.rb +++ b/activerecord/test/cases/connection_adapters/connection_handler_test.rb @@ -6,7 +6,15 @@ module ActiveRecord def setup @handler = ConnectionHandler.new @handler.establish_connection 'america', Base.connection_pool.spec - @klass = Struct.new(:name).new('america') + @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 + end end def test_retrieve_connection @@ -28,6 +36,18 @@ module ActiveRecord def test_retrieve_connection_pool assert_not_nil @handler.retrieve_connection_pool(@klass) end + + def test_retrieve_connection_pool_uses_superclass_when_no_subclass_connection + assert_not_nil @handler.retrieve_connection_pool(@subklass) + end + + def test_retrieve_connection_pool_uses_superclass_pool_after_subclass_establish_and_remove + @handler.establish_connection 'north america', Base.connection_pool.spec + + @handler.remove_connection @subklass + assert_same @handler.retrieve_connection_pool(@klass), + @handler.retrieve_connection_pool(@subklass) + end end end end 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/quoting_test.rb b/activerecord/test/cases/connection_adapters/quoting_test.rb new file mode 100644 index 0000000000..59dcb96ebc --- /dev/null +++ b/activerecord/test/cases/connection_adapters/quoting_test.rb @@ -0,0 +1,13 @@ +require "cases/helper" + +module ActiveRecord + module ConnectionAdapters + module Quoting + class QuotingTest < ActiveRecord::TestCase + def test_quoting_classes + assert_equal "'Object'", AbstractAdapter.new(nil).quote(Object) + end + 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 new file mode 100644 index 0000000000..541e983758 --- /dev/null +++ b/activerecord/test/cases/connection_adapters/schema_cache_test.rb @@ -0,0 +1,59 @@ +require "cases/helper" + +module ActiveRecord + module ConnectionAdapters + class SchemaCacheTest < ActiveRecord::TestCase + def setup + connection = ActiveRecord::Base.connection + @cache = SchemaCache.new connection + end + + def test_primary_key + assert_equal 'id', @cache.primary_keys['posts'] + end + + def test_primary_key_for_non_existent_table + assert_nil @cache.primary_keys['omgponies'] + end + + def test_caches_columns + columns = @cache.columns['posts'] + assert_equal columns, @cache.columns['posts'] + end + + def test_caches_columns_hash + columns_hash = @cache.columns_hash['posts'] + assert_equal columns_hash, @cache.columns_hash['posts'] + end + + 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 8a0f453127..6871e628aa 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,73 +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_pool_caches_columns - columns = @pool.columns['posts'] - assert_equal columns, @pool.columns['posts'] + def active_connections(pool) + pool.connections.find_all(&:in_use?) end - def test_pool_caches_columns_hash - columns_hash = @pool.columns_hash['posts'] - assert_equal columns_hash, @pool.columns_hash['posts'] + 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_clearing_column_cache - @pool.columns['posts'] - @pool.columns_hash['posts'] + def test_released_connection_moves_between_threads + thread_conn = nil - @pool.clear_cache! + Thread.new { + pool.with_connection do |conn| + thread_conn = conn + end + }.join - assert_equal 0, @pool.columns.size - assert_equal 0, @pool.columns_hash.size - end + assert thread_conn - def test_primary_key - assert_equal 'id', @pool.primary_keys['posts'] + Thread.new { + pool.with_connection do |conn| + assert_equal thread_conn, conn + end + }.join end - def test_primary_key_for_non_existent_table - assert_equal 'id', @pool.primary_keys['omgponies'] + def test_with_connection + assert_equal 0, active_connections(pool).size + + main_thread = pool.connection + assert_equal 1, active_connections(pool).size + + Thread.new { + pool.with_connection do |conn| + assert conn + assert_equal 2, active_connections(pool).size + end + assert_equal 1, active_connections(pool).size + }.join + + main_thread.close + assert_equal 0, active_connections(pool).size end - def test_primary_key_is_set_on_columns - posts_columns = @pool.columns_hash['posts'] - assert posts_columns['id'].primary + def test_active_connection_in_use + assert !pool.active_connection? + main_thread = pool.connection + + assert pool.active_connection? + + main_thread.close - (posts_columns.keys - ['id']).each do |key| - assert !posts_columns[key].primary + 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_clear_stale_cached_connections! - pool = ConnectionPool.new ActiveRecord::Base.connection_pool.spec + def test_full_pool_blocks + 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" - threads = [ - Thread.new { pool.connection }, - Thread.new { pool.connection }] + connection = cs.first + connection.close + assert_equal connection, t.join.value + end - threads.map { |t| t.join } + def test_removing_releases_latch + cs = @pool.size.times.map { @pool.checkout } + t = Thread.new { @pool.checkout } - pool.extend Module.new { - attr_accessor :checkins - def checkin conn - @checkins << conn - conn.object_id - end - } - pool.checkins = [] + # 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 - 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 + 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 @@ -96,24 +188,131 @@ 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 + assert pool.connection + pool.connection.close + end.join + end - pool.connection - threads.each do |t| - thread_ids = pool.instance_variable_get(:@reserved_connections).keys - assert !thread_ids.include?(t.object_id) + # The connection pool is "fair" if threads waiting for + # connections receive them the order in which they began + # waiting. This ensures that we don't timeout one HTTP request + # even while well under capacity in a multi-threaded environment + # such as a Java servlet container. + # + # We don't need strict fairness: if two connections become + # available at the same time, it's fine of two threads that were + # waiting acquire the connections out of order. + # + # Thus this test prepares waiting threads and then trickles in + # available connections slowly, ensuring the wakeup order is + # correct in this case. + # + # Try a few times since it might work out just by chance. + def test_checkout_fairness + 4.times { setup; do_checkout_fairness } + end + + def do_checkout_fairness + expected = (1..@pool.size).to_a.freeze + # check out all connections so our threads start out waiting + conns = expected.map { @pool.checkout } + mutex = Mutex.new + order = [] + errors = [] + + threads = expected.map do |i| + t = Thread.new { + begin + conn = @pool.checkout # never checked back in + mutex.synchronize { order << i } + rescue => e + mutex.synchronize { errors << e } + end + } + Thread.pass until t.status == "sleep" + t + end + + # this should wake up the waiting threads one by one in order + conns.each { |conn| @pool.checkin(conn); sleep 0.1 } + + threads.each(&:join) + + raise errors.first if errors.any? + + assert_equal(expected, order) + end + + # As mentioned in #test_checkout_fairness, we don't care about + # strict fairness. This test creates two groups of threads: + # group1 whose members all start waiting before any thread in + # group2. Enough connections are checked in to wakeup all + # group1 threads, and the fact that only group1 and no group2 + # threads acquired a connection is enforced. + # + # Try a few times since it might work out just by chance. + def test_checkout_fairness_by_group + 4.times { setup; do_checkout_fairness_by_group } + end + + def do_checkout_fairness_by_group + @pool.instance_variable_set(:@size, 10) + # take all the connections + conns = (1..10).map { @pool.checkout } + mutex = Mutex.new + successes = [] # threads that successfully got a connection + errors = [] + + make_thread = proc do |i| + t = Thread.new { + begin + conn = @pool.checkout # never checked back in + mutex.synchronize { successes << i } + rescue => e + mutex.synchronize { errors << e } + end + } + Thread.pass until t.status == "sleep" + t + end + + # all group1 threads start waiting before any in group2 + group1 = (1..5).map(&make_thread) + group2 = (6..10).map(&make_thread) + + # checkin n connections back to the pool + checkin = proc do |n| + n.times do + c = conns.pop + @pool.checkin(c) end - end.join() + end + + checkin.call(group1.size) # should wake up all group1 + + loop do + sleep 0.1 + break if mutex.synchronize { (successes.size + errors.size) == group1.size } + end + + winners = mutex.synchronize { successes.dup } + checkin.call(group2.size) # should wake up everyone remaining + + group1.each(&:join) + group2.each(&:join) + assert_equal((1..group1.size).to_a, winners.sort) + + if errors.any? + raise errors.first + end 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_dynamic_methods_test.rb b/activerecord/test/cases/deprecated_dynamic_methods_test.rb new file mode 100644 index 0000000000..77a609f49a --- /dev/null +++ b/activerecord/test/cases/deprecated_dynamic_methods_test.rb @@ -0,0 +1,607 @@ +# This file should be deleted when active_record_deprecated_finders is removed as +# a dependency. +# +# It is kept for now as there is some fairly nuanced behaviour in the dynamic +# finders so it is useful to keep this around to guard against regressions if +# we need to change the code. + +require 'cases/helper' +require 'models/topic' +require 'models/reply' +require 'models/customer' +require 'models/post' +require 'models/company' +require 'models/author' +require 'models/category' +require 'models/comment' +require 'models/person' +require 'models/reader' + +class DeprecatedDynamicMethodsTest < ActiveRecord::TestCase + fixtures :topics, :customers, :companies, :accounts, :posts, :categories, :categories_posts, :authors, :people, :comments, :readers + + def setup + @deprecation_behavior = ActiveSupport::Deprecation.behavior + ActiveSupport::Deprecation.behavior = :silence + end + + def teardown + ActiveSupport::Deprecation.behavior = @deprecation_behavior + end + + def test_find_all_by_one_attribute + topics = Topic.find_all_by_content("Have a nice day") + assert_equal 2, topics.size + assert topics.include?(topics(:first)) + + assert_equal [], Topic.find_all_by_title("The First Topic!!") + end + + def test_find_all_by_one_attribute_which_is_a_symbol + topics = Topic.find_all_by_content("Have a nice day".to_sym) + assert_equal 2, topics.size + assert topics.include?(topics(:first)) + + assert_equal [], Topic.find_all_by_title("The First Topic!!") + end + + def test_find_all_by_one_attribute_that_is_an_aggregate + balance = customers(:david).balance + assert_kind_of Money, balance + found_customers = Customer.find_all_by_balance(balance) + assert_equal 1, found_customers.size + assert_equal customers(:david), found_customers.first + end + + def test_find_all_by_two_attributes_that_are_both_aggregates + balance = customers(:david).balance + address = customers(:david).address + assert_kind_of Money, balance + assert_kind_of Address, address + found_customers = Customer.find_all_by_balance_and_address(balance, address) + assert_equal 1, found_customers.size + assert_equal customers(:david), found_customers.first + end + + def test_find_all_by_two_attributes_with_one_being_an_aggregate + balance = customers(:david).balance + assert_kind_of Money, balance + found_customers = Customer.find_all_by_balance_and_name(balance, customers(:david).name) + assert_equal 1, found_customers.size + assert_equal customers(:david), found_customers.first + end + + def test_find_all_by_one_attribute_with_options + topics = Topic.find_all_by_content("Have a nice day", :order => "id DESC") + assert_equal topics(:first), topics.last + + topics = Topic.find_all_by_content("Have a nice day", :order => "id") + assert_equal topics(:first), topics.first + end + + def test_find_all_by_array_attribute + assert_equal 2, Topic.find_all_by_title(["The First Topic", "The Second Topic of the day"]).size + end + + def test_find_all_by_boolean_attribute + topics = Topic.find_all_by_approved(false) + assert_equal 1, topics.size + assert topics.include?(topics(:first)) + + topics = Topic.find_all_by_approved(true) + assert_equal 3, topics.size + assert topics.include?(topics(:second)) + end + + def test_find_all_by_nil_and_not_nil_attributes + topics = Topic.find_all_by_last_read_and_author_name nil, "Mary" + assert_equal 1, topics.size + assert_equal "Mary", topics[0].author_name + end + + def test_find_or_create_from_one_attribute + 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 + number_of_topics = Topic.count + another = Topic.find_or_create_by_title_and_author_name("Another topic","John") + assert_equal number_of_topics + 1, Topic.count + assert_equal another, Topic.find_or_create_by_title_and_author_name("Another topic", "John") + 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") + assert_equal number_of_customers + 1, Customer.count + assert_equal created_customer, Customer.find_or_create_by_balance(Money.new(123), "Elizabeth") + assert created_customer.persisted? + end + + def test_find_or_create_from_one_attribute_and_hash + number_of_companies = Company.count + sig38 = Company.find_or_create_by_name({: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({: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_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)) + assert_equal number_of_customers + 1, Customer.count + assert_equal created_customer, Customer.find_or_create_by_balance(Money.new(123)) + assert created_customer.persisted? + end + + def test_find_or_create_from_one_aggregate_attribute_and_hash + number_of_customers = Customer.count + balance = Money.new(123) + name = "Elizabeth" + created_customer = Customer.find_or_create_by_balance({:balance => balance, :name => name}) + assert_equal number_of_customers + 1, Customer.count + assert_equal created_customer, Customer.find_or_create_by_balance({:balance => balance, :name => name}) + assert created_customer.persisted? + assert_equal balance, created_customer.balance + assert_equal name, created_customer.name + end + + def test_find_or_initialize_from_one_attribute + sig38 = Company.find_or_initialize_by_name("38signals") + assert_equal "38signals", sig38.name + assert !sig38.persisted? + end + + def test_find_or_initialize_from_one_aggregate_attribute + new_customer = Customer.find_or_initialize_by_balance(Money.new(123)) + assert_equal 123, new_customer.balance.amount + assert !new_customer.persisted? + end + + def test_find_or_initialize_from_one_attribute_should_not_set_attribute_even_when_protected + c = Company.find_or_initialize_by_name({:name => "Fortune 1000", :rating => 1000}) + assert_equal "Fortune 1000", c.name + assert_not_equal 1000, c.rating + assert c.valid? + assert !c.persisted? + end + + def test_find_or_create_from_one_attribute_should_not_set_attribute_even_when_protected + c = Company.find_or_create_by_name({:name => "Fortune 1000", :rating => 1000}) + assert_equal "Fortune 1000", c.name + assert_not_equal 1000, c.rating + assert c.valid? + assert c.persisted? + end + + def test_find_or_initialize_from_one_attribute_should_set_attribute_even_when_protected + c = Company.find_or_initialize_by_name_and_rating("Fortune 1000", 1000) + assert_equal "Fortune 1000", c.name + assert_equal 1000, c.rating + assert c.valid? + assert !c.persisted? + end + + def test_find_or_create_from_one_attribute_should_set_attribute_even_when_protected + c = Company.find_or_create_by_name_and_rating("Fortune 1000", 1000) + assert_equal "Fortune 1000", c.name + assert_equal 1000, c.rating + assert c.valid? + assert c.persisted? + end + + def test_find_or_initialize_from_one_attribute_should_set_attribute_even_when_protected_and_also_set_the_hash + c = Company.find_or_initialize_by_rating(1000, {:name => "Fortune 1000"}) + assert_equal "Fortune 1000", c.name + assert_equal 1000, c.rating + assert c.valid? + assert !c.persisted? + end + + def test_find_or_create_from_one_attribute_should_set_attribute_even_when_protected_and_also_set_the_hash + c = Company.find_or_create_by_rating(1000, {:name => "Fortune 1000"}) + assert_equal "Fortune 1000", c.name + assert_equal 1000, c.rating + assert c.valid? + assert c.persisted? + end + + def test_find_or_initialize_should_set_protected_attributes_if_given_as_block + c = Company.find_or_initialize_by_name(:name => "Fortune 1000") { |f| f.rating = 1000 } + assert_equal "Fortune 1000", c.name + assert_equal 1000.to_f, c.rating.to_f + assert c.valid? + assert !c.persisted? + end + + def test_find_or_create_should_set_protected_attributes_if_given_as_block + c = Company.find_or_create_by_name(:name => "Fortune 1000") { |f| f.rating = 1000 } + assert_equal "Fortune 1000", c.name + assert_equal 1000.to_f, c.rating.to_f + assert c.valid? + assert c.persisted? + end + + def test_find_or_create_should_work_with_block_on_first_call + class << Company + undef_method(:find_or_create_by_name) if method_defined?(:find_or_create_by_name) + end + c = Company.find_or_create_by_name(:name => "Fortune 1000") { |f| f.rating = 1000 } + assert_equal "Fortune 1000", c.name + assert_equal 1000.to_f, c.rating.to_f + assert c.valid? + assert c.persisted? + end + + def test_find_or_initialize_from_two_attributes + another = Topic.find_or_initialize_by_title_and_author_name("Another topic","John") + assert_equal "Another topic", another.title + assert_equal "John", another.author_name + assert !another.persisted? + end + + def test_find_or_initialize_from_two_attributes_but_passing_only_one + assert_raise(ArgumentError) { Topic.find_or_initialize_by_title_and_author_name("Another topic") } + end + + def test_find_or_initialize_from_one_aggregate_attribute_and_one_not + new_customer = Customer.find_or_initialize_by_balance_and_name(Money.new(123), "Elizabeth") + assert_equal 123, new_customer.balance.amount + assert_equal "Elizabeth", new_customer.name + assert !new_customer.persisted? + end + + def test_find_or_initialize_from_one_attribute_and_hash + sig38 = Company.find_or_initialize_by_name({:name => "38signals", :firm_id => 17, :client_of => 23}) + assert_equal "38signals", sig38.name + assert_equal 17, sig38.firm_id + assert_equal 23, sig38.client_of + assert !sig38.persisted? + end + + def test_find_or_initialize_from_one_aggregate_attribute_and_hash + balance = Money.new(123) + name = "Elizabeth" + new_customer = Customer.find_or_initialize_by_balance({:balance => balance, :name => name}) + assert_equal balance, new_customer.balance + assert_equal name, new_customer.name + assert !new_customer.persisted? + end + + def test_find_last_by_one_attribute + assert_equal Topic.last, Topic.find_last_by_title(Topic.last.title) + assert_nil Topic.find_last_by_title("A title with no matches") + end + + def test_find_last_by_invalid_method_syntax + assert_raise(NoMethodError) { Topic.fail_to_find_last_by_title("The First Topic") } + assert_raise(NoMethodError) { Topic.find_last_by_title?("The First Topic") } + end + + def test_find_last_by_one_attribute_with_several_options + 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 + assert_raise(NoMethodError) { Topic.find_last_by_undertitle("The Last Topic!") } + end + + def test_find_last_by_two_attributes + topic = Topic.last + assert_equal topic, Topic.find_last_by_title_and_author_name(topic.title, topic.author_name) + assert_nil Topic.find_last_by_title_and_author_name(topic.title, "Anonymous") + end + + def test_find_last_with_limit_gives_same_result_when_loaded_and_unloaded + scope = Topic.limit(2) + unloaded_last = scope.last + loaded_last = scope.all.last + assert_equal loaded_last, unloaded_last + end + + def test_find_last_with_limit_and_offset_gives_same_result_when_loaded_and_unloaded + scope = Topic.offset(2).limit(2) + unloaded_last = scope.last + loaded_last = scope.all.last + assert_equal loaded_last, unloaded_last + end + + def test_find_last_with_offset_gives_same_result_when_loaded_and_unloaded + scope = Topic.offset(3) + unloaded_last = scope.last + loaded_last = scope.all.last + assert_equal loaded_last, unloaded_last + end + + def test_find_all_by_nil_attribute + topics = Topic.find_all_by_last_read nil + assert_equal 3, topics.size + assert topics.collect(&:last_read).all?(&:nil?) + end + + def test_forwarding_to_dynamic_finders + welcome = Post.find(1) + assert_equal 4, Category.find_all_by_type('SpecialCategory').size + assert_equal 0, welcome.categories.find_all_by_type('SpecialCategory').size + assert_equal 2, welcome.categories.find_all_by_type('Category').size + end + + def test_dynamic_find_all_should_respect_association_order + 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.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.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_last_without_specified_order + assert_equal companies(:second_client), companies(:first_firm).unsorted_clients.find_last_by_type('Client') + end + + def test_dynamic_find_or_create_from_two_attributes_using_an_association + author = authors(:david) + number_of_posts = Post.count + another = author.posts.find_or_create_by_title_and_body("Another Post", "This is the Body") + assert_equal number_of_posts + 1, Post.count + assert_equal another, author.posts.find_or_create_by_title_and_body("Another Post", "This is the Body") + assert another.persisted? + 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.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.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.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.scoped(:includes => :people).all.length + end + + def test_find_or_create_by_resets_cached_counters + person = Person.create! :first_name => 'tenderlove' + post = Post.first + + assert_equal [], person.readers + assert_nil person.readers.find_by_post_id(post.id) + + person.readers.find_or_create_by_post_id(post.id) + + assert_equal 1, person.readers.count + assert_equal 1, person.readers.length + assert_equal post, person.readers.first.post + assert_equal person, person.readers.first.person + end + + def test_find_or_initialize + the_client = companies(:first_firm).clients.find_or_initialize_by_name("Yet another client") + assert_equal companies(:first_firm).id, the_client.firm_id + assert_equal "Yet another client", the_client.name + assert !the_client.persisted? + end + + def test_find_or_create_updates_size + number_of_clients = companies(:first_firm).clients.size + the_client = companies(:first_firm).clients.find_or_create_by_name("Yet another client") + assert_equal number_of_clients + 1, companies(:first_firm, :reload).clients.size + assert_equal the_client, companies(:first_firm).clients.find_or_create_by_name("Yet another client") + assert_equal number_of_clients + 1, companies(:first_firm, :reload).clients.size + end + + def test_find_or_initialize_updates_collection_size + number_of_clients = companies(:first_firm).clients_of_firm.size + companies(:first_firm).clients_of_firm.find_or_initialize_by_name("name" => "Another Client") + 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') + assert post.persisted? + end + + def test_find_or_create_with_one_attribute_followed_by_hash + post = authors(:david).posts.find_or_create_by_title('Yet another post', :body => 'somebody') + assert_equal post, authors(:david).posts.find_or_create_by_title('Yet another post', :body => 'somebody') + assert post.persisted? + end + + def test_find_or_create_should_work_with_block + post = authors(:david).posts.find_or_create_by_title('Yet another post') {|p| p.body = 'somebody'} + assert_equal post, authors(:david).posts.find_or_create_by_title('Yet another post') {|p| p.body = 'somebody'} + assert post.persisted? + end + + def test_forwarding_to_dynamic_finders_2 + welcome = Post.find(1) + assert_equal 4, Comment.find_all_by_type('Comment').size + assert_equal 2, welcome.comments.find_all_by_type('Comment').size + end + + def test_dynamic_find_all_by_attributes + authors = Author.scoped + + davids = authors.find_all_by_name('David') + assert_kind_of Array, davids + assert_equal [authors(:david)], davids + end + + def test_dynamic_find_or_initialize_by_attributes + authors = Author.scoped + + lifo = authors.find_or_initialize_by_name('Lifo') + assert_equal "Lifo", lifo.name + assert !lifo.persisted? + + assert_equal authors(:david), authors.find_or_initialize_by_name(:name => 'David') + end + + def test_dynamic_find_or_create_by_attributes + authors = Author.scoped + + 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_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_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_dynamic_finder_with_invalid_params + assert_raise(ArgumentError) { Topic.find_by_title 'No Title', :join => "It should be `joins'" } + end + + def test_find_by_one_attribute_with_order_option + assert_equal accounts(:signals37), Account.find_by_credit_limit(50, :order => 'id') + assert_equal accounts(:rails_core_account), Account.find_by_credit_limit(50, :order => 'id DESC') + end + + def test_dynamic_find_by_attributes_should_yield_found_object + david = authors(:david) + yielded_value = nil + Author.find_by_name(david.name) do |author| + yielded_value = author + end + assert_equal david, yielded_value + end +end + +class DynamicScopeTest < ActiveRecord::TestCase + fixtures :posts + + def setup + @test_klass = Class.new(Post) do + def self.name; "Post"; end + end + @deprecation_behavior = ActiveSupport::Deprecation.behavior + ActiveSupport::Deprecation.behavior = :silence + end + + def teardown + ActiveSupport::Deprecation.behavior = @deprecation_behavior + end + + 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.scoped(:where => { :author_id => 1, :title => "Welcome to the weblog"}).first + end + + def test_dynamic_scope_should_create_methods_after_hitting_method_missing + assert_blank @test_klass.methods.grep(/scoped_by_type/) + @test_klass.scoped_by_type(nil) + assert_present @test_klass.methods.grep(/scoped_by_type/) + end + + def test_dynamic_scope_with_less_number_of_arguments + assert_raise(ArgumentError){ @test_klass.scoped_by_author_id_and_title(1) } + end +end + +class DynamicScopeMatchTest < ActiveRecord::TestCase + def test_scoped_by_no_match + assert_nil ActiveRecord::DynamicMatchers::Method.match(nil, "not_scoped_at_all") + end + + def test_scoped_by + match = ActiveRecord::DynamicMatchers::Method.match(nil, "scoped_by_age_and_sex_and_location") + assert_not_nil match + assert_equal %w(age sex location), match.attribute_names + 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 5dc5f99582..f7ecab28ce 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -32,6 +32,7 @@ class FinderTest < ActiveRecord::TestCase assert Topic.exists?(:author_name => "Mary", :approved => true) assert Topic.exists?(["parent_id = ?", 1]) assert !Topic.exists?(45) + assert !Topic.exists?(Topic.new) begin assert !Topic.exists?("foo") @@ -48,6 +49,20 @@ class FinderTest < ActiveRecord::TestCase assert Topic.exists? end + # exists? should handle nil for id's that come from URLs and always return false + # (example: Topic.exists?(params[:id])) where params[:id] is nil + def test_exists_with_nil_arg + assert !Topic.exists?(nil) + assert Topic.exists? + assert !Topic.first.replies.exists?(nil) + assert Topic.first.replies.exists? + end + + # ensures +exists?+ runs valid SQL by excluding order value + def test_exists_with_order + assert Topic.order(:id).uniq.exists? + end + def test_does_not_exist_with_empty_table_and_no_args_given Topic.delete_all assert !Topic.exists? @@ -68,12 +83,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? @@ -90,14 +99,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 @@ -105,58 +114,12 @@ class FinderTest < ActiveRecord::TestCase assert_equal [], Topic.find([]) end - def test_find_by_ids_missing_one - 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 } + def test_find_doesnt_have_implicit_ordering + assert_sql(/^((?!ORDER).)*$/) { Topic.find(1) } 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 } + def test_find_by_ids_missing_one + assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1, 2, 45) } end def test_find_with_group_and_sanitized_having_method @@ -185,14 +148,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_find_first_failing - first = Topic.find(:first, :conditions => "title = 'The First Topic!'") - assert_nil(first) + 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_take_bang_missing + assert_raises ActiveRecord::RecordNotFound do + Topic.where("title = 'This title does not exist'").take! + end end def test_first @@ -215,6 +188,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 @@ -243,6 +222,34 @@ class FinderTest < ActiveRecord::TestCase end end + 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 + + def test_last_with_integer_and_order_should_keep_the_order + assert_equal Topic.order("title").to_a.last(2), Topic.order("title").last(2) + end + + def test_last_with_integer_and_order_should_not_use_sql_limit + query = assert_sql { Topic.order("title").last(5).entries } + assert_equal 1, query.length + assert_no_match(/LIMIT/, query.first) + end + + def test_last_with_integer_and_reorder_should_not_use_sql_limit + query = assert_sql { Topic.reorder("title").last(5).entries } + assert_equal 1, query.length + assert_no_match(/LIMIT/, query.first) + end + + 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 + def test_unexisting_record_exception_handling assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1).parent @@ -252,7 +259,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 @@ -262,36 +269,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 @@ -301,85 +296,89 @@ 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 def test_find_on_association_proxy_conditions - assert_equal [1, 2, 3, 5, 6, 7, 8, 9, 10, 12], Comment.find_all_by_post_id(authors(:david).posts).map(&:id).sort + assert_equal [1, 2, 3, 5, 6, 7, 8, 9, 10, 12], Comment.where(post_id: authors(:david).posts).map(&:id).sort 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.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 @@ -387,42 +386,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 @@ -430,7 +429,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 @@ -439,7 +438,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 @@ -448,7 +447,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 @@ -457,32 +456,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 @@ -500,10 +499,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 @@ -574,12 +573,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])) @@ -596,13 +589,8 @@ class FinderTest < ActiveRecord::TestCase assert_raise(ActiveRecord::RecordNotFound) { Topic.find_by_title!("The First Topic!") } end - def test_find_by_one_attribute_with_order_option - assert_equal accounts(:signals37), Account.find_by_credit_limit(50, :order => 'id') - assert_equal accounts(:rails_core_account), Account.find_by_credit_limit(50, :order => 'id DESC') - 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 @@ -642,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 @@ -670,334 +658,26 @@ class FinderTest < ActiveRecord::TestCase assert_raise(ArgumentError) { Topic.find_by_title_and_author_name("The First Topic") } end - def test_find_last_by_one_attribute - assert_equal Topic.last, Topic.find_last_by_title(Topic.last.title) - assert_nil Topic.find_last_by_title("A title with no matches") - end - - def test_find_last_by_invalid_method_syntax - assert_raise(NoMethodError) { Topic.fail_to_find_last_by_title("The First Topic") } - assert_raise(NoMethodError) { Topic.find_last_by_title?("The First Topic") } - 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]) - end - - def test_find_last_by_one_missing_attribute - assert_raise(NoMethodError) { Topic.find_last_by_undertitle("The Last Topic!") } - end - - def test_find_last_by_two_attributes - topic = Topic.last - assert_equal topic, Topic.find_last_by_title_and_author_name(topic.title, topic.author_name) - assert_nil Topic.find_last_by_title_and_author_name(topic.title, "Anonymous") - end - - def test_find_last_with_limit_gives_same_result_when_loaded_and_unloaded - scope = Topic.limit(2) - unloaded_last = scope.last - loaded_last = scope.all.last - assert_equal loaded_last, unloaded_last - end - - def test_find_last_with_limit_and_offset_gives_same_result_when_loaded_and_unloaded - scope = Topic.offset(2).limit(2) - unloaded_last = scope.last - loaded_last = scope.all.last - assert_equal loaded_last, unloaded_last - end - - def test_find_last_with_offset_gives_same_result_when_loaded_and_unloaded - scope = Topic.offset(3) - unloaded_last = scope.last - loaded_last = scope.all.last - assert_equal loaded_last, unloaded_last - end - - def test_find_all_by_one_attribute - topics = Topic.find_all_by_content("Have a nice day") - assert_equal 2, topics.size - assert topics.include?(topics(:first)) - - assert_equal [], Topic.find_all_by_title("The First Topic!!") - end - - def test_find_all_by_one_attribute_which_is_a_symbol - topics = Topic.find_all_by_content("Have a nice day".to_sym) - assert_equal 2, topics.size - assert topics.include?(topics(:first)) - - assert_equal [], Topic.find_all_by_title("The First Topic!!") - end - - def test_find_all_by_one_attribute_that_is_an_aggregate - balance = customers(:david).balance - assert_kind_of Money, balance - found_customers = Customer.find_all_by_balance(balance) - assert_equal 1, found_customers.size - assert_equal customers(:david), found_customers.first - end - - def test_find_all_by_two_attributes_that_are_both_aggregates - balance = customers(:david).balance - address = customers(:david).address - assert_kind_of Money, balance - assert_kind_of Address, address - found_customers = Customer.find_all_by_balance_and_address(balance, address) - assert_equal 1, found_customers.size - assert_equal customers(:david), found_customers.first - end - - def test_find_all_by_two_attributes_with_one_being_an_aggregate - balance = customers(:david).balance - assert_kind_of Money, balance - found_customers = Customer.find_all_by_balance_and_name(balance, customers(:david).name) - assert_equal 1, found_customers.size - assert_equal customers(:david), found_customers.first - end - - def test_find_all_by_one_attribute_with_options - topics = Topic.find_all_by_content("Have a nice day", :order => "id DESC") - assert_equal topics(:first), topics.last - - topics = Topic.find_all_by_content("Have a nice day", :order => "id") - assert_equal topics(:first), topics.first - end - - def test_find_all_by_array_attribute - assert_equal 2, Topic.find_all_by_title(["The First Topic", "The Second Topic of the day"]).size - end - - def test_find_all_by_boolean_attribute - topics = Topic.find_all_by_approved(false) - assert_equal 1, topics.size - assert topics.include?(topics(:first)) - - topics = Topic.find_all_by_approved(true) - assert_equal 3, topics.size - assert topics.include?(topics(:second)) - end - def test_find_by_nil_attribute topic = Topic.find_by_last_read nil assert_not_nil topic assert_nil topic.last_read end - def test_find_all_by_nil_attribute - topics = Topic.find_all_by_last_read nil - assert_equal 3, topics.size - assert topics.collect(&:last_read).all?(&:nil?) - end - def test_find_by_nil_and_not_nil_attributes topic = Topic.find_by_last_read_and_author_name nil, "Mary" assert_equal "Mary", topic.author_name end - def test_find_all_by_nil_and_not_nil_attributes - topics = Topic.find_all_by_last_read_and_author_name nil, "Mary" - assert_equal 1, topics.size - assert_equal "Mary", topics[0].author_name - end - - def test_find_or_create_from_one_attribute - 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 - number_of_topics = Topic.count - another = Topic.find_or_create_by_title_and_author_name("Another topic","John") - assert_equal number_of_topics + 1, Topic.count - assert_equal another, Topic.find_or_create_by_title_and_author_name("Another topic", "John") - assert another.persisted? - 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") - assert_equal number_of_customers + 1, Customer.count - assert_equal created_customer, Customer.find_or_create_by_balance(Money.new(123), "Elizabeth") - assert created_customer.persisted? - end - - def test_find_or_create_from_one_attribute_and_hash - number_of_companies = Company.count - sig38 = Company.find_or_create_by_name({: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({: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)) - assert_equal number_of_customers + 1, Customer.count - assert_equal created_customer, Customer.find_or_create_by_balance(Money.new(123)) - assert created_customer.persisted? - end - - def test_find_or_create_from_one_aggregate_attribute_and_hash - number_of_customers = Customer.count - balance = Money.new(123) - name = "Elizabeth" - created_customer = Customer.find_or_create_by_balance({:balance => balance, :name => name}) - assert_equal number_of_customers + 1, Customer.count - assert_equal created_customer, Customer.find_or_create_by_balance({:balance => balance, :name => name}) - assert created_customer.persisted? - assert_equal balance, created_customer.balance - assert_equal name, created_customer.name - end - - def test_find_or_initialize_from_one_attribute - sig38 = Company.find_or_initialize_by_name("38signals") - assert_equal "38signals", sig38.name - assert !sig38.persisted? - end - - def test_find_or_initialize_from_one_aggregate_attribute - new_customer = Customer.find_or_initialize_by_balance(Money.new(123)) - assert_equal 123, new_customer.balance.amount - assert !new_customer.persisted? - end - - def test_find_or_initialize_from_one_attribute_should_not_set_attribute_even_when_protected - c = Company.find_or_initialize_by_name({:name => "Fortune 1000", :rating => 1000}) - assert_equal "Fortune 1000", c.name - assert_not_equal 1000, c.rating - assert c.valid? - assert !c.persisted? - end - - def test_find_or_create_from_one_attribute_should_not_set_attribute_even_when_protected - c = Company.find_or_create_by_name({:name => "Fortune 1000", :rating => 1000}) - assert_equal "Fortune 1000", c.name - assert_not_equal 1000, c.rating - assert c.valid? - assert c.persisted? - end - - def test_find_or_initialize_from_one_attribute_should_set_attribute_even_when_protected - c = Company.find_or_initialize_by_name_and_rating("Fortune 1000", 1000) - assert_equal "Fortune 1000", c.name - assert_equal 1000, c.rating - assert c.valid? - assert !c.persisted? - end - - def test_find_or_create_from_one_attribute_should_set_attribute_even_when_protected - c = Company.find_or_create_by_name_and_rating("Fortune 1000", 1000) - assert_equal "Fortune 1000", c.name - assert_equal 1000, c.rating - assert c.valid? - assert c.persisted? - end - - def test_find_or_initialize_from_one_attribute_should_set_attribute_even_when_protected_and_also_set_the_hash - c = Company.find_or_initialize_by_rating(1000, {:name => "Fortune 1000"}) - assert_equal "Fortune 1000", c.name - assert_equal 1000, c.rating - assert c.valid? - assert !c.persisted? - end - - def test_find_or_create_from_one_attribute_should_set_attribute_even_when_protected_and_also_set_the_hash - c = Company.find_or_create_by_rating(1000, {:name => "Fortune 1000"}) - assert_equal "Fortune 1000", c.name - assert_equal 1000, c.rating - assert c.valid? - assert c.persisted? - end - - def test_find_or_initialize_should_set_protected_attributes_if_given_as_block - c = Company.find_or_initialize_by_name(:name => "Fortune 1000") { |f| f.rating = 1000 } - assert_equal "Fortune 1000", c.name - assert_equal 1000.to_f, c.rating.to_f - assert c.valid? - assert !c.persisted? - end - - def test_find_or_create_should_set_protected_attributes_if_given_as_block - c = Company.find_or_create_by_name(:name => "Fortune 1000") { |f| f.rating = 1000 } - assert_equal "Fortune 1000", c.name - assert_equal 1000.to_f, c.rating.to_f - assert c.valid? - assert c.persisted? - end - - def test_find_or_create_should_work_with_block_on_first_call - class << Company - undef_method(:find_or_create_by_name) if method_defined?(:find_or_create_by_name) - end - c = Company.find_or_create_by_name(:name => "Fortune 1000") { |f| f.rating = 1000 } - assert_equal "Fortune 1000", c.name - assert_equal 1000.to_f, c.rating.to_f - assert c.valid? - assert c.persisted? - end - - def test_find_or_initialize_from_two_attributes - another = Topic.find_or_initialize_by_title_and_author_name("Another topic","John") - assert_equal "Another topic", another.title - assert_equal "John", another.author_name - assert !another.persisted? - end - - def test_find_or_initialize_from_two_attributes_but_passing_only_one - assert_raise(ArgumentError) { Topic.find_or_initialize_by_title_and_author_name("Another topic") } - end - - def test_find_or_initialize_from_one_aggregate_attribute_and_one_not - new_customer = Customer.find_or_initialize_by_balance_and_name(Money.new(123), "Elizabeth") - assert_equal 123, new_customer.balance.amount - assert_equal "Elizabeth", new_customer.name - assert !new_customer.persisted? - end - - def test_find_or_initialize_from_one_attribute_and_hash - sig38 = Company.find_or_initialize_by_name({:name => "38signals", :firm_id => 17, :client_of => 23}) - assert_equal "38signals", sig38.name - assert_equal 17, sig38.firm_id - assert_equal 23, sig38.client_of - assert !sig38.persisted? - end - - def test_find_or_initialize_from_one_aggregate_attribute_and_hash - balance = Money.new(123) - name = "Elizabeth" - new_customer = Customer.find_or_initialize_by_balance({:balance => balance, :name => name}) - assert_equal balance, new_customer.balance - assert_equal name, new_customer.name - assert !new_customer.persisted? - end - def test_find_with_bad_sql 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') @@ -1005,17 +685,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'" @@ -1026,15 +704,14 @@ 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 # http://dev.rubyonrails.org/ticket/6778 def test_find_ignores_previously_inserted_record Post.create!(:title => 'test', :body => 'it out') - assert_equal [], Post.find_all_by_id(nil) + assert_equal [], Post.where(id: nil) end def test_find_by_empty_ids @@ -1042,13 +719,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 @@ -1075,16 +752,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' @@ -1095,9 +771,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 } @@ -1105,21 +780,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 @@ -1127,6 +797,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 913f6a3340..c28f8de682 100644 --- a/activerecord/test/cases/fixtures_test.rb +++ b/activerecord/test/cases/fixtures_test.rb @@ -1,30 +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 @@ -45,9 +49,25 @@ class FixturesTest < ActiveRecord::TestCase end end + def test_broken_yaml_exception + badyaml = Tempfile.new ['foo', '.yml'] + badyaml.write 'a: : ' + badyaml.flush + + dir = File.dirname badyaml.path + name = File.basename badyaml.path, '.yml' + assert_raises(ActiveRecord::Fixture::FormatError) do + ActiveRecord::Fixtures.create_fixtures(dir, name) + end + ensure + badyaml.close + badyaml.unlink + 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 @@ -57,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"]) @@ -77,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 @@ -99,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 @@ -163,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 @@ -197,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 @@ -435,14 +475,36 @@ end class CustomConnectionFixturesTest < ActiveRecord::TestCase set_fixture_class :courses => Course fixtures :courses - # Set to false to blow away fixtures cache and ensure our fixtures are loaded - # and thus takes into account our set_fixture_class self.use_transactional_fixtures = false def test_connection assert_kind_of Course, courses(:ruby) assert_equal Course.connection, courses(:ruby).connection end + + def test_leaky_destroy + assert_nothing_raised { courses(:ruby) } + courses(:ruby).destroy + end + + def test_it_twice_in_whatever_order_to_check_for_fixture_leakage + test_leaky_destroy + end +end + +class TransactionalFixturesOnCustomConnectionTest < ActiveRecord::TestCase + set_fixture_class :courses => Course + fixtures :courses + self.use_transactional_fixtures = true + + def test_leaky_destroy + assert_nothing_raised { courses(:ruby) } + courses(:ruby).destroy + end + + def test_it_twice_in_whatever_order_to_check_for_fixture_leakage + test_leaky_destroy + end end class InvalidTableNameFixturesTest < ActiveRecord::TestCase @@ -452,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 @@ -480,7 +542,9 @@ class ManyToManyFixturesWithClassDefined < ActiveRecord::TestCase end class FixturesBrokenRollbackTest < ActiveRecord::TestCase - def blank_setup; end + def blank_setup + @fixture_connections = [ActiveRecord::Base.connection] + end alias_method :ar_setup_fixtures, :setup_fixtures alias_method :setup_fixtures, :blank_setup alias_method :setup, :blank_setup @@ -533,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 @@ -652,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 @@ -691,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..37fa13f771 100644 --- a/activerecord/test/cases/helper.rb +++ b/activerecord/test/cases/helper.rb @@ -2,12 +2,15 @@ require File.expand_path('../../../../load_paths', __FILE__) require 'config' -require 'test/unit' +gem 'minitest' +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 +20,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 +37,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 +59,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 @@ -101,8 +81,8 @@ class ActiveSupport::TestCase self.use_instantiated_fixtures = false self.use_transactional_fixtures = true - def create_fixtures(*table_names, &block) - ActiveRecord::Fixtures.create_fixtures(ActiveSupport::TestCase.fixture_path, table_names, fixture_class_names, &block) + def create_fixtures(*fixture_set_names, &block) + ActiveRecord::Fixtures.create_fixtures(ActiveSupport::TestCase.fixture_path, fixture_set_names, fixture_class_names, &block) end end 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 61baa55027..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") @@ -125,6 +129,24 @@ class OptimisticLockingTest < ActiveRecord::TestCase assert_raise(ActiveRecord::StaleObjectError) { p2.save! } end + def test_lock_exception_record + p1 = Person.new(:first_name => 'mira') + assert_equal 0, p1.lock_version + + p1.first_name = 'mira2' + p1.save! + p2 = Person.find(p1.id) + assert_equal 0, p1.lock_version + assert_equal 0, p2.lock_version + + p1.first_name = 'mira3' + p1.save! + + p2.first_name = 'sue' + error = assert_raise(ActiveRecord::StaleObjectError) { p2.save! } + assert_equal(error.record.object_id, p2.object_id) + end + def test_lock_new_with_nil p1 = Person.new(:first_name => 'anika') p1.save! @@ -141,7 +163,6 @@ class OptimisticLockingTest < ActiveRecord::TestCase assert_equal 1, p1.lock_version end - def test_lock_column_name_existing t1 = LegacyThing.find(1) t2 = LegacyThing.find(1) @@ -207,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 @@ -261,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 @@ -272,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 @@ -314,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 @@ -336,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 @@ -354,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 ef35f3341e..2f98d3c646 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 @@ -231,13 +238,32 @@ class MassAssignmentSecurityTest < ActiveRecord::TestCase def test_protection_against_class_attribute_writers [:logger, :configurations, :primary_key_prefix_type, :table_name_prefix, :table_name_suffix, :pluralize_table_names, - :default_timezone, :schema_format, :lock_optimistically, :record_timestamps].each do |method| + :default_timezone, :schema_format, :lock_optimistically, :timestamped_migrations, :default_scopes, + :connection_handler, :nested_attributes_options, :_attr_readonly, :attribute_types_cached_by_default, + :attribute_method_matchers, :time_zone_aware_attributes, :skip_time_zone_conversion_for_attributes].each do |method| assert_respond_to Task, method assert_respond_to Task, "#{method}=" assert_respond_to Task.new, method assert !Task.new.respond_to?("#{method}=") end end +end + + +# This class should be deleted when we removed active_record_deprecated_finders as a +# dependency. +class MassAssignmentSecurityDeprecatedFindersTest < ActiveRecord::TestCase + include MassAssignmentTestHelpers + + def setup + super + @deprecation_behavior = ActiveSupport::Deprecation.behavior + ActiveSupport::Deprecation.behavior = :silence + end + + def teardown + ActiveSupport::Deprecation.behavior = @deprecation_behavior + end def test_find_or_initialize_by_with_attr_accessible_attributes p = TightPerson.find_or_initialize_by_first_name('Josh', attributes_hash) @@ -321,6 +347,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 @@ -348,6 +381,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 @@ -375,6 +415,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 @@ -436,6 +483,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 @@ -463,6 +517,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 @@ -497,6 +558,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 @@ -524,6 +592,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 @@ -551,6 +626,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..ab61a4dcef --- /dev/null +++ b/activerecord/test/cases/migration/change_schema_test.rb @@ -0,0 +1,324 @@ +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 + 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" } + + 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 + 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 + 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/change_table_test.rb b/activerecord/test/cases/migration/change_table_test.rb new file mode 100644 index 0000000000..063209389f --- /dev/null +++ b/activerecord/test/cases/migration/change_table_test.rb @@ -0,0 +1,221 @@ +require "cases/migration/helper" + +module ActiveRecord + class Migration + class TableTest < ActiveRecord::TestCase + class MockConnection < MiniTest::Mock + def native_database_types + { + :string => 'varchar(255)', + :integer => 'integer', + } + end + + def type_to_sql(type, limit, precision, scale) + native_database_types[type] + end + end + + def setup + @connection = MockConnection.new + end + + def teardown + assert @connection.verify + end + + def with_change_table + yield ConnectionAdapters::Table.new(:delete_me, @connection) + end + + def test_references_column_type_adds_id + with_change_table do |t| + @connection.expect :add_column, nil, [:delete_me, 'customer_id', :integer, {}] + t.references :customer + end + end + + def test_remove_references_column_type_removes_id + with_change_table do |t| + @connection.expect :remove_column, nil, [:delete_me, 'customer_id'] + t.remove_references :customer + end + end + + def test_add_belongs_to_works_like_add_references + with_change_table do |t| + @connection.expect :add_column, nil, [:delete_me, 'customer_id', :integer, {}] + t.belongs_to :customer + end + end + + def test_remove_belongs_to_works_like_remove_references + with_change_table do |t| + @connection.expect :remove_column, nil, [:delete_me, 'customer_id'] + t.remove_belongs_to :customer + end + end + + def test_references_column_type_with_polymorphic_adds_type + with_change_table do |t| + @connection.expect :add_column, nil, [:delete_me, 'taggable_id', :integer, {}] + @connection.expect :add_column, nil, [:delete_me, 'taggable_type', :string, {}] + t.references :taggable, :polymorphic => true + end + end + + def test_remove_references_column_type_with_polymorphic_removes_type + with_change_table do |t| + @connection.expect :remove_column, nil, [:delete_me, 'taggable_id'] + @connection.expect :remove_column, nil, [:delete_me, 'taggable_type'] + t.remove_references :taggable, :polymorphic => true + end + end + + def test_references_column_type_with_polymorphic_and_options_null_is_false_adds_table_flag + with_change_table do |t| + @connection.expect :add_column, nil, [:delete_me, 'taggable_id', :integer, {:null => false}] + @connection.expect :add_column, nil, [:delete_me, 'taggable_type', :string, {:null => false}] + t.references :taggable, :polymorphic => true, :null => false + end + end + + def test_remove_references_column_type_with_polymorphic_and_options_null_is_false_removes_table_flag + with_change_table do |t| + @connection.expect :remove_column, nil, [:delete_me, 'taggable_id'] + @connection.expect :remove_column, nil, [:delete_me, 'taggable_type'] + t.remove_references :taggable, :polymorphic => true, :null => false + end + end + + def test_timestamps_creates_updated_at_and_created_at + with_change_table do |t| + @connection.expect :add_timestamps, nil, [:delete_me] + t.timestamps + end + end + + def test_remove_timestamps_creates_updated_at_and_created_at + with_change_table do |t| + @connection.expect :remove_timestamps, nil, [:delete_me] + t.remove_timestamps + end + end + + def string_column + @connection.native_database_types[:string] + end + + def integer_column + @connection.native_database_types[:integer] + end + + def test_integer_creates_integer_column + with_change_table do |t| + @connection.expect :add_column, nil, [:delete_me, :foo, integer_column, {}] + @connection.expect :add_column, nil, [:delete_me, :bar, integer_column, {}] + t.integer :foo, :bar + end + end + + def test_string_creates_string_column + with_change_table do |t| + @connection.expect :add_column, nil, [:delete_me, :foo, string_column, {}] + @connection.expect :add_column, nil, [:delete_me, :bar, string_column, {}] + t.string :foo, :bar + end + end + + def test_column_creates_column + with_change_table do |t| + @connection.expect :add_column, nil, [:delete_me, :bar, :integer, {}] + t.column :bar, :integer + end + end + + def test_column_creates_column_with_options + with_change_table do |t| + @connection.expect :add_column, nil, [:delete_me, :bar, :integer, {:null => false}] + t.column :bar, :integer, :null => false + end + end + + def test_index_creates_index + with_change_table do |t| + @connection.expect :add_index, nil, [:delete_me, :bar, {}] + t.index :bar + end + end + + def test_index_creates_index_with_options + with_change_table do |t| + @connection.expect :add_index, nil, [:delete_me, :bar, {:unique => true}] + t.index :bar, :unique => true + end + end + + def test_index_exists + with_change_table do |t| + @connection.expect :index_exists?, nil, [:delete_me, :bar, {}] + t.index_exists?(:bar) + end + end + + def test_index_exists_with_options + with_change_table do |t| + @connection.expect :index_exists?, nil, [:delete_me, :bar, {:unique => true}] + t.index_exists?(:bar, :unique => true) + end + end + + def test_change_changes_column + with_change_table do |t| + @connection.expect :change_column, nil, [:delete_me, :bar, :string, {}] + t.change :bar, :string + end + end + + def test_change_changes_column_with_options + with_change_table do |t| + @connection.expect :change_column, nil, [:delete_me, :bar, :string, {:null => true}] + t.change :bar, :string, :null => true + end + end + + def test_change_default_changes_column + with_change_table do |t| + @connection.expect :change_column_default, nil, [:delete_me, :bar, :string] + t.change_default :bar, :string + end + end + + def test_remove_drops_single_column + with_change_table do |t| + @connection.expect :remove_column, nil, [:delete_me, :bar] + t.remove :bar + end + end + + def test_remove_drops_multiple_columns + with_change_table do |t| + @connection.expect :remove_column, nil, [:delete_me, :bar, :baz] + t.remove :bar, :baz + end + end + + def test_remove_index_removes_index_with_options + with_change_table do |t| + @connection.expect :remove_index, nil, [:delete_me, {:unique => true}] + t.remove_index :unique => true + end + end + + def test_rename_renames_column + with_change_table do |t| + @connection.expect :rename_column, nil, [:delete_me, :bar, :baz] + t.rename :bar, :baz + end + 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..18f8d82bfe --- /dev/null +++ b/activerecord/test/cases/migration/column_attributes_test.rb @@ -0,0 +1,198 @@ +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 + + def test_out_of_range_limit_should_raise + skip("MySQL and PostgreSQL only") unless current_adapter?(:MysqlAdapter, :Mysql2Adapter, :PostgreSQLAdapter) + + assert_raise(ActiveRecordError) { add_column :test_models, :integer_too_big, :integer, :limit => 10 } + + unless current_adapter?(:PostgreSQLAdapter) + assert_raise(ActiveRecordError) { add_column :test_models, :text_too_big, :integer, :limit => 0xfffffffff } + end + 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 36007255fa..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 @@ -104,9 +122,9 @@ module ActiveRecord end def test_invert_rename_index - @recorder.record :rename_index, [:old, :new] + @recorder.record :rename_index, [:table, :old, :new] rename = @recorder.inverse.first - assert_equal [:rename_index, [:new, :old]], rename + assert_equal [:rename_index, [:table, :new, :old]], rename end def test_invert_add_timestamps 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 93a1249e43..5d1bad0d54 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,2235 +7,741 @@ 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 - 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 teardown + ActiveRecord::Base.connection.initialize_schema_migrations_table + ActiveRecord::Base.connection.execute "DELETE FROM #{ActiveRecord::Migrator.schema_migrations_table_name}" - 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 + %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_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 + %w(reminders people_reminders prefix_reminders_suffix).each do |table| + Reminder.connection.drop_table(table) rescue nil end + Reminder.reset_column_information - 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 + %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_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 + 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 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 + # 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 - def test_create_table_without_id - testing_table_with_only_foo_attribute do |connection| - assert_equal connection.columns(:testings).size, 1 - end - end + assert_not_equal temp_conn, Person.connection - 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 + 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_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 - end - - 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 - end + def connection + ActiveRecord::Base.connection + end - def test_create_table_with_defaults - # MySQL doesn't allow defaults on TEXT or BLOB columns. - mysql = current_adapter?(:MysqlAdapter) || current_adapter?(:Mysql2Adapter) + def test_migration_instance_has_connection + migration = Class.new(ActiveRecord::Migration).new + assert_equal connection, migration.connection + end - 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 + def test_method_missing_delegates_to_connection + migration = Class.new(ActiveRecord::Migration) { + def connection + Class.new { + def create_table; "hi mom!"; end + }.new end + }.new - 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 - 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 + assert_equal "hi mom!", migration.method_missing(:create_table) + 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_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 - def test_create_table_with_primary_key_prefix_as_table_name_with_underscore - ActiveRecord::Base.primary_key_prefix_type = :table_name_with_underscore - - Person.connection.create_table :testings do |t| - t.column :foo, :string - end + GiveMeBigNumbers.down + assert_raise(ActiveRecord::StatementInvalid) { BigNumber.first } + end - 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 - end + def test_filtering_migrations + assert !Person.column_methods_hash.include?(:last_name) + assert !Reminder.table_exists? - def test_create_table_with_primary_key_prefix_as_table_name - ActiveRecord::Base.primary_key_prefix_type = :table_name + name_filter = lambda { |migration| migration.name == "ValidPeopleHaveLastNames" } + ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", &name_filter) - Person.connection.create_table :testings do |t| - t.column :foo, :string - end + Person.reset_column_information + assert Person.column_methods_hash.include?(:last_name) + assert_raise(ActiveRecord::StatementInvalid) { Reminder.first } - 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 + ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid", &name_filter) - def test_create_table_with_force_true_does_not_drop_nonexisting_table - if Person.connection.table_exists?(:testings2) - Person.connection.drop_table :testings2 - end + Person.reset_column_information + assert !Person.column_methods_hash.include?(:last_name) + assert_raise(ActiveRecord::StatementInvalid) { Reminder.first } + 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 - end - ensure - Person.connection.drop_table :testings2 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_with_timestamps_should_create_datetime_columns - table_name = :testings - - Person.connection.create_table table_name do |t| - t.timestamps - end - created_columns = Person.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 - ensure - Person.connection.drop_table table_name rescue nil + def up + @went_up = true + super end - def test_create_table_with_timestamps_should_create_datetime_columns_with_options - table_name = :testings - - Person.connection.create_table table_name do |t| - t.timestamps :null => false - end - created_columns = Person.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 - ensure - Person.connection.drop_table table_name rescue nil + def down + @went_down = true + super end + 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 - 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' - # 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 + migration.migrate :up + assert migration.went_up, 'have gone up' + assert !migration.went_down, 'have not gone down' + end - 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 - 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_not_null_with_default - Person.connection.create_table :testings do |t| - t.column :foo, :string - end + migration.migrate :down + assert !migration.went_up, 'have gone up' + assert migration.went_down, 'have not gone down' + 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 + 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 - # 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) values (people_seq.nextval, 12345678901234567890.0123456789)" - 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) values ('12345678901234567890.0123456789')" - else - Person.connection.execute "insert into people (wealth) values (12345678901234567890.0123456789)" - 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") } + refute Person.column_methods_hash.include?(:last_name) - # SELECT - row = Person.find(:first) - assert_kind_of BigDecimal, row.wealth + migration = Struct.new(:name, :version) { + def migrate(x); raise 'Something broke'; end + }.new('zomg', 100) - # 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 - - # Reset to old state - Person.connection.del_column "people", "wealth" rescue nil - Person.reset_column_information - end + migrator = ActiveRecord::Migrator.new(:up, [migration], 100) - def test_add_column_with_precision_and_scale - 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 - end + e = assert_raise(StandardError) { migrator.migrate } - 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 + assert_equal "An error has occurred, this and all later migrations canceled:\n\nSomething broke", e.message - 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 + Person.reset_column_information + refute Person.column_methods_hash.include?(:last_name) + end - # Test for 30 significant digits (beyond the 16 of float), 10 of them - # after the decimal place. + 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 - unless current_adapter?(:SQLite3Adapter) - assert_equal BigDecimal.new("0012345678901234567890.0123456789"), bob.wealth - 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 - assert_equal true, bob.male? + 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_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_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?(: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_create_table_with_binary_column + Person.connection.drop_table :binary_testings rescue nil - # 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 + assert_nothing_raised { + Person.connection.create_table :binary_testings do |t| + t.column "data", :binary, :null => false end + } - assert_instance_of TrueClass, bob.male? - assert_kind_of BigDecimal, bob.wealth - end + columns = Person.connection.columns(:binary_testings) + data_column = columns.detect { |c| c.name == "data" } if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter) - def test_unabstracted_database_dependent_types - Person.delete_all - - 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 - 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 - - 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 + Person.connection.drop_table :table_with_name_thats_just_ok, + :sequence_name => 'suitably_short_seq' 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')")} - 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 + # 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_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_out_of_range_limit_should_raise + skip("MySQL and PostgreSQL only") unless current_adapter?(:MysqlAdapter, :Mysql2Adapter, :PostgreSQLAdapter) - def test_column_exists - Person.connection.create_table :testings do |t| - t.column :foo, :string + Person.connection.drop_table :test_limits rescue nil + assert_raise(ActiveRecord::ActiveRecordError, "integer limit didn't raise") do + Person.connection.create_table :test_integer_limits, :force => true do |t| + t.column :bigone, :integer, :limit => 10 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 + unless current_adapter?(:PostgreSQLAdapter) + assert_raise(ActiveRecord::ActiveRecordError, "text limit didn't raise") do + Person.connection.create_table :test_text_limits, :force => true do |t| + t.column :bigtext, :text, :limit => 0xfffffffff + end 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 + Person.connection.drop_table :test_limits rescue nil + 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) + 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 + old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ') 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 - ensure - Person.connection.drop_table :testings rescue nil +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_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) } + assert_nothing_raised do + connection.add_index :values, :value + connection.remove_index :values, :column => :value 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 + connection.drop_table :values rescue nil + end +end - GiveMeBigNumbers.down - assert_raise(ActiveRecord::StatementInvalid) { BigNumber.find(:first) } +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 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) } + def teardown + Person.connection.drop_table(:delete_me) rescue nil 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 + 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_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' - 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' - 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? - - 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 - end - - def test_migrator_one_down - ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid") - - ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid", 1) - - Person.reset_column_information - assert Person.column_methods_hash.include?(:last_name) - assert !Reminder.table_exists? - 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? + 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_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) - 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) - end + def test_removing_columns + with_bulk_change_table do |t| + t.string :qualification, :experience + 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) + [:qualification, :experience].each {|c| assert column(c) } - e = assert_raise(StandardError) do - ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/broken", 100) + assert_queries(1) do + with_bulk_change_table do |t| + t.remove :qualification, :experience + t.string :qualification_experience 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 - 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 + [:qualification, :experience].each {|c| assert ! column(c) } + assert column(:qualification_experience) 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 + def test_adding_indexes + with_bulk_change_table do |t| + t.string :username + t.string :name + t.integer :age 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' - end - - def test_relative_migrations - list = Dir.chdir(MIGRATIONS_ROOT) do - ActiveRecord::Migrator.up("valid/", 1) + # 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 - migration_proxy = list.find { |item| - item.name == 'ValidPeopleHaveLastNames' - } - assert migration_proxy, 'should find pending migration' - 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') - end - - def test_target_version_zero_should_run_only_once - # migrate up to 1 - ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid", 1) + assert_equal 2, indexes.size - # migrate down to 0 - ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid", 0) + 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 - # migrate down to 0 again - proxies = ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid", 0) - assert_equal [], proxies + assert index(:awesome_username_index).unique 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) + def test_removing_index + with_bulk_change_table do |t| + t.string :name + t.index :name 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 - 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 - 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? + assert index(:index_delete_me_on_name) - 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 - 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) - 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) - 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) - 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 - 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 + assert_queries(3) do + with_bulk_change_table do |t| + t.remove_index :name + t.index :name, :name => :new_name_index, :unique => true 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 - end + assert ! index(:index_delete_me_on_name) - def test_migrator_with_duplicates - assert_raise(ActiveRecord::DuplicateMigrationVersionError) do - ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/duplicate", nil) - 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 + new_name_index = index(:new_name_index) + assert new_name_index.unique end - def test_migrator_with_missing_version_numbers - assert_raise(ActiveRecord::UnknownMigrationVersionError) do - ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/missing", 500) - 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 + def test_changing_columns + with_bulk_change_table do |t| + t.string :name + t.date :birthdate 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 + assert ! column(:name).default + assert_equal :date, column(:birthdate).type - 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 + # 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 - # confirm the custom sequence got dropped - assert_raise(ActiveRecord::StatementInvalid) do - Person.connection.execute("select suitably_short_seq.nextval from dual") - end + assert_equal 'NONAME', column(:name).default + assert_equal :datetime, column(:birthdate).type 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 SexyMigrationsTest < ActiveRecord::TestCase - def test_references_column_type_adds_id - with_new_table do |t| - t.expects(:column).with('customer_id', :integer, {}) - t.references :customer - end - end - def test_references_column_type_with_polymorphic_adds_type - with_new_table do |t| - t.expects(:column).with('taggable_type', :string, {}) - t.expects(:column).with('taggable_id', :integer, {}) - t.references :taggable, :polymorphic => true - end - end + def with_bulk_change_table + # Reset columns/indexes cache as we're changing the table + @columns = @indexes = nil - def test_references_column_type_with_polymorphic_and_options_null_is_false_adds_table_flag - with_new_table do |t| - t.expects(:column).with('taggable_type', :string, {:null => false}) - t.expects(:column).with('taggable_id', :integer, {:null => false}) - t.references :taggable, :polymorphic => true, :null => false + Person.connection.change_table(:delete_me, :bulk => true) do |t| + yield t end end - def test_belongs_to_works_like_references - with_new_table do |t| - t.expects(:column).with('customer_id', :integer, {}) - t.belongs_to :customer - end + def column(name) + columns.detect {|c| c.name == name.to_s } end - def test_timestamps_creates_updated_at_and_created_at - with_new_table do |t| - t.expects(:column).with(:created_at, :datetime, kind_of(Hash)) - t.expects(:column).with(:updated_at, :datetime, kind_of(Hash)) - t.timestamps - end + def columns + @columns ||= Person.connection.columns('delete_me') end - def test_integer_creates_integer_column - with_new_table do |t| - t.expects(:column).with(:foo, 'integer', {}) - t.expects(:column).with(:bar, 'integer', {}) - t.integer :foo, :bar - end + def index(name) + indexes.detect {|i| i.name == name.to_s } end - def test_string_creates_string_column - with_new_table do |t| - t.expects(:column).with(:foo, 'string', {}) - t.expects(:column).with(:bar, 'string', {}) - t.string :foo, :bar - end + def indexes + @indexes ||= Person.connection.indexes('delete_me') end + end # AlterTableMigrationsTest - if current_adapter?(:PostgreSQLAdapter) || current_adapter?(:SQLite3Adapter) || current_adapter?(:MysqlAdapter) || current_adapter?(:Mysql2Adapter) - def test_xml_creates_xml_column - type = current_adapter?(:PostgreSQLAdapter) ? 'xml' : :text - - with_new_table do |t| - t.expects(:column).with(:data, type, {}) - t.xml :data - end - end - else - def test_xml_creates_xml_column - with_new_table do |t| - assert_raises(NotImplementedError) do - t.xml :data - end - end - end - end - - protected - def with_new_table - Person.connection.create_table :delete_me, :force => true do |t| - yield t - end - ensure - Person.connection.drop_table :delete_me rescue nil - end - - end # SexyMigrationsTest +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 - end +class CopyMigrationsTest < ActiveRecord::TestCase + def setup 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 - end + def clear + ActiveRecord::Base.timestamped_migrations = true + to_delete = Dir[@migrations_path + "/*.rb"] - @existing_migrations + File.delete(*to_delete) 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 - 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 - - class ChangeTableMigrationsTest < ActiveRecord::TestCase - def setup - @connection = Person.connection - @connection.create_table :delete_me, :force => true do |t| - end - end - - def teardown - 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 - 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 - 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 - 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 - - 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_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_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_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_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 string_column - if current_adapter?(:PostgreSQLAdapter) - "character varying(255)" - elsif current_adapter?(:OracleAdapter) - 'VARCHAR2(255)' - else - 'varchar(255)' - end - end - - 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_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_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_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_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_index_creates_index - with_change_table do |t| - @connection.expects(:add_index).with(:delete_me, :bar, {}) - t.index :bar - end - 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 - 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 - - 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_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_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_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_remove_drops_single_column - with_change_table do |t| - @connection.expects(:remove_column).with(:delete_me, [:bar]) - t.remove :bar - 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 - 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 - 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 - - protected - def with_change_table - Person.connection.change_table :delete_me do |t| - yield t - end - 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 - 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 - - 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 - - def indexes - @indexes ||= Person.connection.indexes('delete_me') - end - end # AlterTableMigrationsTest + def test_copying_migrations_with_timestamps + @migrations_path = MIGRATIONS_ROOT + "/valid_with_timestamps" + @existing_migrations = Dir[@migrations_path + "/*.rb"] - end + 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) - class CopyMigrationsTest < ActiveRecord::TestCase - def setup + 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 clear - ActiveRecord::Base.timestamped_migrations = true - to_delete = Dir[@migrations_path + "/*.rb"] - @existing_migrations - File.delete(*to_delete) - end + def test_copying_migrations_with_timestamps_from_2_sources + @migrations_path = MIGRATIONS_ROOT + "/valid_with_timestamps" + @existing_migrations = Dir[@migrations_path + "/*.rb"] - def test_copying_migrations_without_timestamps - ActiveRecord::Base.timestamped_migrations = false - @migrations_path = MIGRATIONS_ROOT + "/valid" - @existing_migrations = Dir[@migrations_path + "/*.rb"] + sources = {} + sources[:bukkits] = MIGRATIONS_ROOT + "/to_copy_with_timestamps" + sources[:omg] = MIGRATIONS_ROOT + "/to_copy_with_timestamps2" - 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, 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 - copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy"}) + ActiveRecord::Migration.copy(@migrations_path, sources) 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_to_destination_with_timestamps_in_future + @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") + 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 - ActiveRecord::Migration.copy(@migrations_path, sources) + copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"}) assert_equal files_count, Dir[@migrations_path + "/*.rb"].length - ensure - clear - end - - def test_copying_migrations_with_timestamps - @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) - - 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 + 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"] - 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"] + sources = {} + sources[:bukkits] = MIGRATIONS_ROOT + "/to_copy_with_timestamps" + sources[:omg] = MIGRATIONS_ROOT + "/to_copy_with_name_collision" - 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") + 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 - 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 + assert_equal 1, skipped.length + assert_equal ["omg PeopleHaveHobbies"], skipped + ensure + clear + end - def test_skipping_migrations - @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"] - sources = ActiveSupport::OrderedHash.new - sources[:bukkits] = sources[:omg] = MIGRATIONS_ROOT + "/to_copy_with_timestamps" + sources = {} + sources[:bukkits] = MIGRATIONS_ROOT + "/to_copy_with_timestamps" - 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 + 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) - assert_equal 2, skipped.length - assert_equal ["bukkits PeopleHaveHobbies", "bukkits PeopleHaveDescriptions"], skipped - ensure - clear - end + assert_equal 2, copied.length + assert_equal 0, skipped.length + ensure + clear + end - def test_copying_migrations_to_non_existing_directory - @migrations_path = MIGRATIONS_ROOT + "/non_existing" - @existing_migrations = [] + 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) + 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 + 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 ed0240cada..bf825c002a 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,25 +130,21 @@ 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? + scope = Topic.where("content LIKE '%Have%'") + assert !scope.empty? - assert_equal Topic.find(:all, scope), Topic.scoped(scope) - end - - 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 assert_equal Topic.base.first(2), Topic.base.to_a.first(2) - assert_equal Topic.base.last(2), Topic.base.to_a.last(2) + assert_equal Topic.base.last(2), Topic.base.order("id").to_a.last(2) end def test_first_and_last_should_not_use_query_when_results_are_loaded @@ -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 @@ -319,7 +272,7 @@ class NamedScopeTest < ActiveRecord::TestCase end def test_should_use_where_in_query_for_scope - assert_equal Developer.find_all_by_name('Jamis').to_set, Developer.find_all_by_id(Developer.jamises).to_set + assert_equal Developer.where(name: 'Jamis').to_set, Developer.where(id: Developer.jamises).to_set end def test_size_should_use_count_when_results_are_not_loaded @@ -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,42 +418,24 @@ class NamedScopeTest < ActiveRecord::TestCase require "models/without_table" end end -end - -class DynamicScopeMatchTest < ActiveRecord::TestCase - def test_scoped_by_no_match - assert_nil ActiveRecord::DynamicScopeMatch.match("not_scoped_at_all") - end - - def test_scoped_by - match = ActiveRecord::DynamicScopeMatch.match("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 -class DynamicScopeTest < ActiveRecord::TestCase - fixtures :posts + def test_eager_scopes_are_deprecated + klass = Class.new(ActiveRecord::Base) + klass.table_name = 'posts' - def setup - @test_klass = Class.new(Post) do - def self.name; "Post"; end + 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_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"}) - end - - def test_dynamic_scope_should_create_methods_after_hitting_method_missing - assert_blank @test_klass.methods.grep(/scoped_by_type/) - @test_klass.scoped_by_type(nil) - assert_present @test_klass.methods.grep(/scoped_by_type/) - end + def test_eager_default_scope_relations_are_deprecated + klass = Class.new(ActiveRecord::Base) + klass.table_name = 'posts' - def test_dynamic_scope_with_less_number_of_arguments - assert_raise(ArgumentError){ @test_klass.scoped_by_author_id_and_title(1) } + 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 diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb index 67a9ed6cd8..0559bbbe9a 100644 --- a/activerecord/test/cases/nested_attributes_test.rb +++ b/activerecord/test/cases/nested_attributes_test.rb @@ -45,6 +45,14 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase end end + def test_should_not_build_a_new_record_using_reject_all_even_if_destroy_is_given + pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?") + pirate.birds_with_reject_all_blank_attributes = [{:name => '', :color => '', :_destroy => '0'}] + pirate.save! + + assert pirate.birds_with_reject_all_blank.empty? + end + def test_should_not_build_a_new_record_if_reject_all_blank_returns_false pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?") pirate.birds_with_reject_all_blank_attributes = [{:name => '', :color => ''}] @@ -164,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 @@ -655,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) @@ -665,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 9cd07fa8a5..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) @@ -202,9 +214,12 @@ class PersistencesTest < ActiveRecord::TestCase end def test_create_columns_not_equal_attributes - topic = Topic.new - topic.title = 'Another New Topic' - topic.send :write_attribute, 'does_not_exist', 'test' + topic = Topic.allocate.init_with( + 'attributes' => { + 'title' => 'Another New Topic', + 'does_not_exist' => 'test' + } + ) assert_nothing_raised { topic.save } end @@ -249,9 +264,11 @@ class PersistencesTest < ActiveRecord::TestCase topic.title = "Still another topic" topic.save - topicReloaded = Topic.find(topic.id) - topicReloaded.title = "A New Topic" - topicReloaded.send :write_attribute, 'does_not_exist', 'test' + topicReloaded = Topic.allocate + topicReloaded.init_with( + 'attributes' => topic.attributes.merge('does_not_exist' => 'test') + ) + topicReloaded.title = 'A New Topic' assert_nothing_raised { topicReloaded.save } end @@ -315,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 @@ -388,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 379cf5b44e..fba3006ebe 100644 --- a/activerecord/test/cases/pooled_connections_test.rb +++ b/activerecord/test/cases/pooled_connections_test.rb @@ -3,19 +3,21 @@ require "models/project" require "timeout" class PooledConnectionsTest < ActiveRecord::TestCase + self.use_transactional_fixtures = false + 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 @@ -31,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 @@ -61,85 +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 - end - - def test_pooled_connection_checkin_two - checkout_checkin_connections 2, 3 - assert_equal 3, @connection_count - assert_equal 0, @timed_out - assert_equal 1, ActiveRecord::Base.connection_pool.connections.size - end - - def test_pooled_connection_checkout_existing_first - ActiveRecord::Base.establish_connection(@connection.merge({:pool => 1})) - conn_pool = ActiveRecord::Base.connection_pool - conn = conn_pool.checkout - conn_pool.checkin(conn) - conn = conn_pool.checkout - assert ActiveRecord::ConnectionAdapters::AbstractAdapter === conn - conn_pool.checkin(conn) - end - - def test_not_connected_defined_connection_returns_false - ActiveRecord::Base.establish_connection(@connection) - assert ! ActiveRecord::Base.connected? - end - - def test_undefined_connection_returns_false - old_handler = ActiveRecord::Base.connection_handler - ActiveRecord::Base.connection_handler = ActiveRecord::ConnectionAdapters::ConnectionHandler.new - assert ! ActiveRecord::Base.connected? - ensure - ActiveRecord::Base.connection_handler = old_handler + assert_equal 1, ActiveRecord::Model.connection_pool.connections.size end - def test_connection_config - ActiveRecord::Base.establish_connection(@connection) - assert_equal @connection, ActiveRecord::Base.connection_config - end - - def test_with_connection_nesting_safety - ActiveRecord::Base.establish_connection(@connection.merge({:pool => 1, :wait_timeout => 0.1})) - - before_count = Project.count - - add_record('one') - - ActiveRecord::Base.connection.transaction do - add_record('two') - # Have another thread try to screw up the transaction - Thread.new do - ActiveRecord::Base.connection.rollback_db_transaction - ActiveRecord::Base.connection_pool.release_connection - end - add_record('three') - end - - after_count = Project.count - assert_equal 3, after_count - before_count - end - - def test_connection_pool_callbacks - checked_out, checked_in = false, false - ActiveRecord::ConnectionAdapters::AbstractAdapter.class_eval do - set_callback(:checkout, :after) { checked_out = true } - set_callback(:checkin, :before) { checked_in = true } - end - @per_test_teardown << proc do - ActiveRecord::ConnectionAdapters::AbstractAdapter.class_eval do - reset_callbacks :checkout - reset_callbacks :checkin - end - end - checkout_checkin_connections 1, 1 - assert checked_out - assert checked_in - 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 05a41d8a0a..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,7 +147,72 @@ 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 + +class PrimaryKeyWithNoConnectionTest < ActiveRecord::TestCase + self.use_transactional_fixtures = false + + def test_set_primary_key_with_no_connection + return skip("disconnect wipes in-memory db") if in_memory_db? + + connection = ActiveRecord::Model.remove_connection + + model = Class.new(ActiveRecord::Base) + model.primary_key = 'foo' + + assert_equal 'foo', model.primary_key + + 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 ad17f6f83a..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 @@ -13,10 +13,37 @@ class QueryCacheTest < ActiveRecord::TestCase ActiveRecord::Base.connection.disable_query_cache! end + def test_exceptional_middleware_clears_and_disables_cache_on_error + assert !ActiveRecord::Base.connection.query_cache_enabled, 'cache off' + + mw = ActiveRecord::QueryCache.new lambda { |env| + Task.find 1 + Task.find 1 + assert_equal 1, ActiveRecord::Base.connection.query_cache.length + raise "lol borked" + } + assert_raises(RuntimeError) { mw.call({}) } + + assert_equal 0, ActiveRecord::Base.connection.query_cache.length + assert !ActiveRecord::Base.connection.query_cache_enabled, 'cache off' + end + + def test_exceptional_middleware_leaves_enabled_cache_alone + ActiveRecord::Base.connection.enable_query_cache! + + mw = ActiveRecord::QueryCache.new lambda { |env| + raise "lol borked" + } + assert_raises(RuntimeError) { mw.call({}) } + + assert ActiveRecord::Base.connection.query_cache_enabled, 'cache on' + end + def test_middleware_delegates called = false mw = ActiveRecord::QueryCache.new lambda { |env| called = true + [200, {}, nil] } mw.call({}) assert called, 'middleware should delegate' @@ -27,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 @@ -36,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 @@ -57,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' @@ -67,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 @@ -77,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 @@ -121,13 +151,16 @@ class QueryCacheTest < ActiveRecord::TestCase end def test_cache_does_not_wrap_string_results_in_arrays - require 'sqlite3/version' if current_adapter?(:SQLite3Adapter) + if current_adapter?(:SQLite3Adapter) + require 'sqlite3/version' + sqlite3_version = RUBY_PLATFORM =~ /java/ ? Jdbc::SQLite3::VERSION : SQLite3::VERSION + end Task.cache do # Oracle adapter returns count() as Fixnum or Float if current_adapter?(:OracleAdapter) assert_kind_of Numeric, Task.connection.select_value("SELECT count(*) AS count_all FROM tasks") - elsif current_adapter?(:SQLite3Adapter) && SQLite3::VERSION > '1.2.5' || current_adapter?(:Mysql2Adapter) || current_adapter?(:MysqlAdapter) + elsif current_adapter?(:SQLite3Adapter) && sqlite3_version > '1.2.5' || current_adapter?(:Mysql2Adapter) || current_adapter?(:MysqlAdapter) # Future versions of the sqlite3 adapter will return numeric assert_instance_of Fixnum, Task.connection.select_value("SELECT count(*) AS count_all FROM tasks") @@ -141,6 +174,18 @@ end class QueryCacheExpiryTest < ActiveRecord::TestCase fixtures :tasks, :posts, :categories, :categories_posts + def test_cache_gets_cleared_after_migration + # warm the cache + Post.find(1) + + # change the column definition + Post.connection.change_column :posts, :title, :string, :limit => 80 + assert_nothing_raised { Post.find(1) } + + # restore the old definition + Post.connection.change_column :posts, :title, :string + end + def test_find Task.connection.expects(:clear_query_cache).times(1) @@ -188,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 @@ -203,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) - assert proxy.respond_to?(:to_path) - assert_equal proxy.to_path, "/path" - end - -end
\ No newline at end of file diff --git a/activerecord/test/cases/readonly_test.rb b/activerecord/test/cases/readonly_test.rb index e21109baae..df0399f548 100644 --- a/activerecord/test/cases/readonly_test.rb +++ b/activerecord/test/cases/readonly_test.rb @@ -23,11 +23,12 @@ class ReadOnlyTest < ActiveRecord::TestCase end assert_raise(ActiveRecord::ReadOnlyRecord) { dev.save } assert_raise(ActiveRecord::ReadOnlyRecord) { dev.save! } + assert_raise(ActiveRecord::ReadOnlyRecord) { dev.destroy } end 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 +49,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 +71,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 +86,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 41312e8661..7dd5698dcf 100644 --- a/activerecord/test/cases/reflection_test.rb +++ b/activerecord/test/cases/reflection_test.rb @@ -18,6 +18,7 @@ require 'models/subscriber' require 'models/subscription' require 'models/tag' require 'models/sponsor' +require 'models/edge' class ReflectionTest < ActiveRecord::TestCase include ActiveRecord::Reflection @@ -35,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 @@ -62,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 @@ -188,8 +189,8 @@ class ReflectionTest < ActiveRecord::TestCase def test_reflection_of_all_associations # FIXME these assertions bust a lot - assert_equal 36, Firm.reflect_on_all_associations.size - assert_equal 26, 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 @@ -244,7 +245,7 @@ class ReflectionTest < ActiveRecord::TestCase # Normal association assert_equal "id", Author.reflect_on_association(:posts).association_primary_key.to_s assert_equal "name", Author.reflect_on_association(:essay).association_primary_key.to_s - assert_equal "id", Tagging.reflect_on_association(:taggable).association_primary_key.to_s + assert_equal "name", Essay.reflect_on_association(:writer).association_primary_key.to_s # Through association (uses the :primary_key option from the source reflection) assert_equal "nick", Author.reflect_on_association(:subscribers).association_primary_key.to_s @@ -252,11 +253,25 @@ class ReflectionTest < ActiveRecord::TestCase assert_equal "custom_primary_key", Author.reflect_on_association(:tags_with_primary_key).association_primary_key.to_s # nested end + def test_association_primary_key_raises_when_missing_primary_key + reflection = ActiveRecord::Reflection::AssociationReflection.new(:fuu, :edge, {}, Author) + assert_raises(ActiveRecord::UnknownPrimaryKey) { reflection.association_primary_key } + + through = ActiveRecord::Reflection::ThroughReflection.new(:fuu, :edge, {}, Author) + through.stubs(:source_reflection).returns(stub_everything(:options => {}, :class_name => 'Edge')) + assert_raises(ActiveRecord::UnknownPrimaryKey) { through.association_primary_key } + end + def test_active_record_primary_key assert_equal "nick", Subscriber.reflect_on_association(:subscriptions).active_record_primary_key.to_s assert_equal "name", Author.reflect_on_association(:essay).active_record_primary_key.to_s end + def test_active_record_primary_key_raises_when_missing_primary_key + reflection = ActiveRecord::Reflection::AssociationReflection.new(:fuu, :author, {}, Edge) + assert_raises(ActiveRecord::UnknownPrimaryKey) { reflection.active_record_primary_key } + end + def test_foreign_type assert_equal "sponsorable_type", Sponsor.reflect_on_association(:sponsorable).foreign_type.to_s assert_equal "sponsorable_type", Sponsor.reflect_on_association(:thing).foreign_type.to_s @@ -304,6 +319,16 @@ class ReflectionTest < ActiveRecord::TestCase assert_equal "category_id", Post.reflect_on_association(:categorizations).foreign_key.to_s end + def test_through_reflection_conditions_do_not_modify_other_reflections + orig_conds = Post.reflect_on_association(:first_blue_tags_2).conditions.inspect + Author.reflect_on_association(:misc_post_first_blue_tags_2).conditions + assert_equal orig_conds, Post.reflect_on_association(:first_blue_tags_2).conditions.inspect + end + + def test_symbol_for_class_name + assert_equal Client, Firm.reflect_on_association(:unsorted_clients_with_symbol).klass + end + private def assert_reflection(klass, association, options) assert reflection = klass.reflect_on_association(association) diff --git a/activerecord/test/cases/relation_scoping_test.rb b/activerecord/test/cases/relation_scoping_test.rb index 1e2093273e..cf367242f2 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 @@ -84,7 +84,7 @@ class RelationScopingTest < ActiveRecord::TestCase def test_scope_select_concatenates Developer.select("id, name").scoping do - developer = Developer.select('id, salary').where("name = 'David'").first + developer = Developer.select('salary').where("name = 'David'").first assert_equal 80000, developer.salary assert developer.has_attribute?(:id) assert developer.has_attribute?(:name) @@ -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)) @@ -254,11 +254,6 @@ class HasManyScopingTest< ActiveRecord::TestCase assert_equal 2, @welcome.comments.search_by_type('Comment').size end - def test_forwarding_to_dynamic_finders - assert_equal 4, Comment.find_all_by_type('Comment').size - assert_equal 2, @welcome.comments.find_all_by_type('Comment').size - end - def test_nested_scope_finder Comment.where('1=0').scoping do assert_equal 0, @welcome.comments.count @@ -300,12 +295,6 @@ class HasAndBelongsToManyScopingTest< ActiveRecord::TestCase assert_equal 'a category...', @welcome.categories.what_are_you end - def test_forwarding_to_dynamic_finders - assert_equal 4, Category.find_all_by_type('SpecialCategory').size - assert_equal 0, @welcome.categories.find_all_by_type('SpecialCategory').size - assert_equal 2, @welcome.categories.find_all_by_type('Category').size - end - def test_nested_scope_finder Category.where('1=0').scoping do assert_equal 0, @welcome.categories.count @@ -323,8 +312,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 +351,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.where(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.where(name: 'Jamis').map(&:id).sort, DeveloperCalledJamis.all.map(&:id).sort assert_equal 'Jamis', DeveloperCalledJamis.create!.name end @@ -395,23 +384,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 +396,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 b23ead6feb..89f818a689 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].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,138 @@ 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 - [:from, :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 '#from!' do + assert relation.from!('foo').equal?(relation) + assert_equal ['foo', nil], relation.from_value + 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 97abd67385..2dc8f0053b 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' @@ -133,6 +133,13 @@ class RelationTest < ActiveRecord::TestCase assert topics.loaded? end + def test_finding_with_subquery + relation = Topic.where(:approved => true) + assert_equal relation.to_a, Topic.select('*').from(relation).to_a + assert_equal relation.to_a, Topic.select('subquery.*').from(relation).to_a + assert_equal relation.to_a, Topic.select('a.*').from(relation, :a).to_a + end + def test_finding_with_conditions assert_equal ["David"], Author.where(:name => 'David').map(&:name) assert_equal ['Mary'], Author.where(["name = ?", 'Mary']).map(&:name) @@ -184,12 +191,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 +222,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 +289,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 +337,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 +362,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 +372,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 +390,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 +400,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 @@ -356,18 +408,18 @@ class RelationTest < ActiveRecord::TestCase end def test_default_scope_with_conditions_string - assert_equal Developer.find_all_by_name('David').map(&:id).sort, DeveloperCalledDavid.scoped.map(&:id).sort + assert_equal Developer.where(name: 'David').map(&:id).sort, DeveloperCalledDavid.scoped.map(&:id).sort assert_nil DeveloperCalledDavid.create!.name end def test_default_scope_with_conditions_hash - assert_equal Developer.find_all_by_name('Jamis').map(&:id).sort, DeveloperCalledJamis.scoped.map(&:id).sort + assert_equal Developer.where(name: 'Jamis').map(&:id).sort, DeveloperCalledJamis.scoped.map(&:id).sort assert_equal 'Jamis', DeveloperCalledJamis.create!.name end def test_default_scoping_finder_methods developers = DeveloperCalledDavid.order('id').map(&:id).sort - assert_equal Developer.find_all_by_name('David').map(&:id).sort, developers + assert_equal Developer.where(name: 'David').map(&:id).sort, developers end def test_loading_with_one_association @@ -391,15 +443,6 @@ class RelationTest < ActiveRecord::TestCase assert_equal Post.find(1).last_comment, post.last_comment end - def test_dynamic_find_by_attributes_should_yield_found_object - david = authors(:david) - yielded_value = nil - Author.find_by_name(david.name) do |author| - yielded_value = author - end - assert_equal david, yielded_value - end - def test_dynamic_find_by_attributes david = authors(:david) author = Author.preload(:taggings).find_by_id(david.id) @@ -421,34 +464,6 @@ class RelationTest < ActiveRecord::TestCase assert_raises(ActiveRecord::RecordNotFound) { Author.scoped.find_by_id_and_name!(20, 'invalid') } end - def test_dynamic_find_all_by_attributes - authors = Author.scoped - - davids = authors.find_all_by_name('David') - assert_kind_of Array, davids - assert_equal [authors(:david)], davids - end - - def test_dynamic_find_or_initialize_by_attributes - authors = Author.scoped - - lifo = authors.find_or_initialize_by_name('Lifo') - assert_equal "Lifo", lifo.name - assert !lifo.persisted? - - assert_equal authors(:david), authors.find_or_initialize_by_name(:name => 'David') - end - - def test_dynamic_find_or_create_by_attributes - authors = Author.scoped - - 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 @@ -540,6 +555,29 @@ class RelationTest < ActiveRecord::TestCase } end + def test_find_all_using_where_with_relation_and_alternate_primary_key + cool_first = minivans(:cool_first) + # switching the lines below would succeed in current rails + # assert_queries(2) { + assert_queries(1) { + relation = Minivan.where(:minivan_id => Minivan.where(:name => cool_first.name)) + assert_equal [cool_first], relation.all + } + end + + def test_find_all_using_where_with_relation_does_not_alter_select_values + david = authors(:david) + + subquery = Author.where(:id => david.id) + + assert_queries(1) { + relation = Author.where(:id => subquery) + assert_equal [david], relation.all + } + + assert_equal 0, subquery.select_values.size + end + def test_find_all_using_where_with_relation_with_joins david = authors(:david) assert_queries(1) { @@ -564,6 +602,7 @@ class RelationTest < ActiveRecord::TestCase assert ! davids.exists?(authors(:mary).id) assert ! davids.exists?("42") assert ! davids.exists?(42) + assert ! davids.exists?(davids.new) fake = Author.where(:name => 'fake author') assert ! fake.exists? @@ -608,6 +647,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 @@ -637,10 +680,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 @@ -840,6 +881,128 @@ class RelationTest < ActiveRecord::TestCase assert_equal 'hen', hen.name end + def test_first_or_create + parrot = Bird.where(:color => 'green').first_or_create(:name => 'parrot') + assert_kind_of Bird, parrot + assert parrot.persisted? + assert_equal 'parrot', parrot.name + assert_equal 'green', parrot.color + + same_parrot = Bird.where(:color => 'green').first_or_create(:name => 'parakeet') + assert_kind_of Bird, same_parrot + assert same_parrot.persisted? + assert_equal parrot, same_parrot + end + + def test_first_or_create_with_no_parameters + parrot = Bird.where(:color => 'green').first_or_create + assert_kind_of Bird, parrot + assert !parrot.persisted? + assert_equal 'green', parrot.color + end + + def test_first_or_create_with_block + parrot = Bird.where(:color => 'green').first_or_create { |bird| bird.name = 'parrot' } + assert_kind_of Bird, parrot + assert parrot.persisted? + assert_equal 'green', parrot.color + assert_equal 'parrot', parrot.name + + same_parrot = Bird.where(:color => 'green').first_or_create { |bird| bird.name = 'parakeet' } + assert_equal parrot, same_parrot + end + + def test_first_or_create_with_array + several_green_birds = Bird.where(:color => 'green').first_or_create([{:name => 'parrot'}, {:name => 'parakeet'}]) + assert_kind_of Array, several_green_birds + several_green_birds.each { |bird| assert bird.persisted? } + + same_parrot = Bird.where(:color => 'green').first_or_create([{:name => 'hummingbird'}, {:name => 'macaw'}]) + assert_kind_of Bird, same_parrot + assert_equal several_green_birds.first, same_parrot + end + + def test_first_or_create_bang_with_valid_options + parrot = Bird.where(:color => 'green').first_or_create!(:name => 'parrot') + assert_kind_of Bird, parrot + assert parrot.persisted? + assert_equal 'parrot', parrot.name + assert_equal 'green', parrot.color + + same_parrot = Bird.where(:color => 'green').first_or_create!(:name => 'parakeet') + assert_kind_of Bird, same_parrot + assert same_parrot.persisted? + assert_equal parrot, same_parrot + end + + def test_first_or_create_bang_with_invalid_options + assert_raises(ActiveRecord::RecordInvalid) { Bird.where(:color => 'green').first_or_create!(:pirate_id => 1) } + end + + def test_first_or_create_bang_with_no_parameters + assert_raises(ActiveRecord::RecordInvalid) { Bird.where(:color => 'green').first_or_create! } + end + + def test_first_or_create_bang_with_valid_block + parrot = Bird.where(:color => 'green').first_or_create! { |bird| bird.name = 'parrot' } + assert_kind_of Bird, parrot + assert parrot.persisted? + assert_equal 'green', parrot.color + assert_equal 'parrot', parrot.name + + same_parrot = Bird.where(:color => 'green').first_or_create! { |bird| bird.name = 'parakeet' } + assert_equal parrot, same_parrot + end + + def test_first_or_create_bang_with_invalid_block + assert_raise(ActiveRecord::RecordInvalid) do + Bird.where(:color => 'green').first_or_create! { |bird| bird.pirate_id = 1 } + end + end + + def test_first_or_create_with_valid_array + several_green_birds = Bird.where(:color => 'green').first_or_create!([{:name => 'parrot'}, {:name => 'parakeet'}]) + assert_kind_of Array, several_green_birds + several_green_birds.each { |bird| assert bird.persisted? } + + same_parrot = Bird.where(:color => 'green').first_or_create!([{:name => 'hummingbird'}, {:name => 'macaw'}]) + assert_kind_of Bird, same_parrot + assert_equal several_green_birds.first, same_parrot + end + + def test_first_or_create_with_invalid_array + assert_raises(ActiveRecord::RecordInvalid) { Bird.where(:color => 'green').first_or_create!([ {:name => 'parrot'}, {:pirate_id => 1} ]) } + end + + def test_first_or_initialize + parrot = Bird.where(:color => 'green').first_or_initialize(:name => 'parrot') + assert_kind_of Bird, parrot + assert !parrot.persisted? + assert parrot.valid? + assert parrot.new_record? + assert_equal 'parrot', parrot.name + assert_equal 'green', parrot.color + end + + def test_first_or_initialize_with_no_parameters + parrot = Bird.where(:color => 'green').first_or_initialize + assert_kind_of Bird, parrot + assert !parrot.persisted? + assert !parrot.valid? + assert parrot.new_record? + assert_equal 'green', parrot.color + end + + def test_first_or_initialize_with_block + parrot = Bird.where(:color => 'green').first_or_initialize { |bird| bird.name = 'parrot' } + assert_kind_of Bird, parrot + assert !parrot.persisted? + assert parrot.valid? + assert parrot.new_record? + assert_equal 'green', parrot.color + assert_equal 'parrot', parrot.name + end + def test_explicit_create_scope hens = Bird.where(:name => 'hen') assert_equal 'hen', hens.new.name @@ -859,10 +1022,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 @@ -874,10 +1033,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 @@ -899,36 +1054,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 @@ -939,10 +1084,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 @@ -959,7 +1100,9 @@ class RelationTest < ActiveRecord::TestCase ) ) - assert scope.eager_loading? + assert_deprecated do + assert scope.eager_loading? + end end def test_ordering_with_extra_spaces @@ -995,7 +1138,7 @@ class RelationTest < ActiveRecord::TestCase end def test_update_all_with_joins_and_offset_and_order - all_comments = Comment.joins(:post).where('posts.id' => posts(:welcome).id).order('posts.id') + all_comments = Comment.joins(:post).where('posts.id' => posts(:welcome).id).order('posts.id', 'comments.id') count = all_comments.count comments = all_comments.offset(1) @@ -1003,4 +1146,129 @@ class RelationTest < ActiveRecord::TestCase assert_equal posts(:thinking), comments(:more_greetings).post assert_equal posts(:welcome), comments(:greetings).post end + + def test_uniq + tag1 = Tag.create(:name => 'Foo') + tag2 = Tag.create(:name => 'Foo') + + query = Tag.select(:name).where(:id => [tag1.id, tag2.id]) + + assert_equal ['Foo', 'Foo'], query.map(&:name) + assert_sql(/DISTINCT/) do + assert_equal ['Foo'], query.uniq.map(&:name) + end + assert_sql(/DISTINCT/) do + assert_equal ['Foo'], query.uniq(true).map(&:name) + 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 doesn't have implicit ordering" do + assert_sql(/^((?!ORDER).)*$/) { Post.find_by(author_id: 2) } + 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! doesn't have implicit ordering" do + assert_sql(/^((?!ORDER).)*$/) { Post.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 71ff727b7f..ab80dd1d6d 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,34 @@ class SchemaDumperTest < ActiveRecord::TestCase end end + def test_schema_dump_includes_inet_shorthand_definition + output = standard_dump + if %r{create_table "postgresql_network_address"} =~ output + assert_match %r{t.inet "inet_address"}, output + end + end + + def test_schema_dump_includes_cidr_shorthand_definition + output = standard_dump + if %r{create_table "postgresql_network_address"} =~ output + assert_match %r{t.cidr "cidr_address"}, output + end + end + + def test_schema_dump_includes_macaddr_shorthand_definition + output = standard_dump + if %r{create_table "postgresql_network_address"} =~ output + assert_match %r{t.macaddr "macaddr_address"}, output + 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 @@ -238,4 +289,9 @@ class SchemaDumperTest < ActiveRecord::TestCase assert_match %r(:id => false), match[1], "no table id not preserved" assert_match %r{t.string[[:space:]]+"id",[[:space:]]+:null => false$}, match[2], "non-primary key id column not preserved" end + + def test_schema_dump_keeps_id_false_when_id_is_false_and_unique_not_null_column_added + output = standard_dump + assert_match %r{create_table "subscribers", :id => false}, output + end end diff --git a/activerecord/test/cases/serialization_test.rb b/activerecord/test/cases/serialization_test.rb index 382d659289..a4c065e667 100644 --- a/activerecord/test/cases/serialization_test.rb +++ b/activerecord/test/cases/serialization_test.rb @@ -7,12 +7,14 @@ class SerializationTest < ActiveRecord::TestCase def setup @contact_attributes = { - :name => 'aaron stack', - :age => 25, - :avatar => 'binarydata', - :created_at => Time.utc(2006, 8, 1), - :awesome => false, - :preferences => { :gem => '<strong>ruby</strong>' } + :name => 'aaron stack', + :age => 25, + :avatar => 'binarydata', + :created_at => Time.utc(2006, 8, 1), + :awesome => false, + :preferences => { :gem => '<strong>ruby</strong>' }, + :alternative_id => nil, + :id => nil } end @@ -23,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) @@ -32,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}" @@ -41,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 669c0b7b4d..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 @@ -36,6 +40,7 @@ module ActiveRecord end def test_find_by_sess_id_compat + Session.reset_column_information klass = Class.new(Session) do def self.session_id_column 'sessid' @@ -53,13 +58,14 @@ module ActiveRecord assert_equal session.sessid, found.session_id ensure klass.drop_table! + Session.reset_column_information end 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/session_store/sql_bypass.rb b/activerecord/test/cases/session_store/sql_bypass_test.rb index 7402b2afd6..6749d4ce98 100644 --- a/activerecord/test/cases/session_store/sql_bypass.rb +++ b/activerecord/test/cases/session_store/sql_bypass_test.rb @@ -18,6 +18,11 @@ module ActiveRecord assert !Session.table_exists? end + def test_new_record? + s = SqlBypass.new :data => 'foo', :session_id => 10 + assert s.new_record?, 'this is a new record!' + end + def test_persisted? s = SqlBypass.new :data => 'foo', :session_id => 10 assert !s.persisted?, 'this is a new record!' diff --git a/activerecord/test/cases/store_test.rb b/activerecord/test/cases/store_test.rb new file mode 100644 index 0000000000..e1d0f1f799 --- /dev/null +++ b/activerecord/test/cases/store_test.rb @@ -0,0 +1,77 @@ +require 'cases/helper' +require 'models/admin' +require 'models/admin/user' + +class StoreTest < ActiveRecord::TestCase + setup do + @john = Admin::User.create(:name => 'John Doe', :color => 'black', :remember_login => true, :height => 'tall', :is_a_good_guy => 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' + + assert_equal 'red', @john.color + assert_equal '37signals.com', @john.homepage + end + + test "accessing attributes not exposed by accessors" do + @john.settings[:icecream] = 'graeters' + @john.save + + 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 + + test "reading store attributes through accessors encoded with JSON" do + assert_equal 'tall', @john.height + assert_nil @john.weight + end + + test "writing store attributes through accessors encoded with JSON" do + @john.height = 'short' + @john.weight = 'heavy' + + assert_equal 'short', @john.height + assert_equal 'heavy', @john.weight + end + + test "accessing attributes not exposed by accessors encoded with JSON" do + @john.json_data['somestuff'] = 'somecoolstuff' + @john.save + + assert_equal 'somecoolstuff', @john.reload.json_data['somestuff'] + end + + test "updating the store will mark it as changed encoded with JSON" do + @john.height = 'short' + assert @john.json_data_changed? + end + + test "object initialization with not nullable column encoded with JSON" do + assert_equal true, @john.is_a_good_guy + end + + test "writing with not nullable column encoded with JSON" do + @john.is_a_good_guy = false + assert_equal false, @john.is_a_good_guy + 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/timestamp_test.rb b/activerecord/test/cases/timestamp_test.rb index 4445a12e1d..447aa29ffe 100644 --- a/activerecord/test/cases/timestamp_test.rb +++ b/activerecord/test/cases/timestamp_test.rb @@ -60,6 +60,16 @@ class TimestampTest < ActiveRecord::TestCase Developer.record_timestamps = true end + def test_saving_when_instance_record_timestamps_is_false_doesnt_update_its_timestamp + @developer.record_timestamps = false + assert Developer.record_timestamps + + @developer.name = "John Smith" + @developer.save! + + assert_equal @previously_updated_at, @developer.updated_at + end + def test_touching_an_attribute_updates_timestamp previously_created_at = @developer.created_at @developer.touch(:created_at) diff --git a/activerecord/test/cases/transaction_callbacks_test.rb b/activerecord/test/cases/transaction_callbacks_test.rb index 85f222bca2..961ba8d9ba 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 @@ -287,6 +287,74 @@ class TransactionObserverCallbacksTest < ActiveRecord::TestCase raise ActiveRecord::Rollback end + assert topic.id.nil? + assert !topic.persisted? assert_equal %w{ after_rollback }, topic.history end + + class TopicWithManualRollbackObserverAttached < ActiveRecord::Base + self.table_name = :topics + def history + @history ||= [] + end + end + + class TopicWithManualRollbackObserverAttachedObserver < ActiveRecord::Observer + def after_save(record) + record.history.push "after_save" + raise ActiveRecord::Rollback + end + end + + def test_after_save_called_with_manual_rollback + assert TopicWithManualRollbackObserverAttachedObserver.instance, 'should have observer' + + topic = TopicWithManualRollbackObserverAttached.new + + assert !topic.save + assert_equal nil, topic.id + assert !topic.persisted? + assert_equal %w{ after_save }, topic.history + end + def test_after_save_called_with_manual_rollback_bang + assert TopicWithManualRollbackObserverAttachedObserver.instance, 'should have observer' + + topic = TopicWithManualRollbackObserverAttached.new + + topic.save! + assert_equal nil, topic.id + assert !topic.persisted? + assert_equal %w{ after_save }, topic.history + end +end + +class SaveFromAfterCommitBlockTest < ActiveRecord::TestCase + self.use_transactional_fixtures = false + + class TopicWithSaveInCallback < ActiveRecord::Base + self.table_name = :topics + after_commit :cache_topic, :on => :create + after_commit :call_update, :on => :update + attr_accessor :cached, :record_updated + + def call_update + self.record_updated = true + end + + def cache_topic + unless cached + self.cached = true + self.save + else + self.cached = false + end + end + end + + def test_after_commit_in_save + topic = TopicWithSaveInCallback.new() + topic.save + assert_equal true, topic.cached + assert_equal true, topic.record_updated + end 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 f85fb4e5da..5a69054445 100644 --- a/activerecord/test/cases/unconnected_test.rb +++ b/activerecord/test/cases/unconnected_test.rb @@ -4,16 +4,16 @@ class TestRecord < ActiveRecord::Base end class TestUnconnectedAdapter < ActiveRecord::TestCase - self.use_transactional_fixtures = false unless supports_savepoints? + 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 |