From 42b39ae3f2991692672364d7e09b1e4002e66261 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Fri, 18 Jan 2008 07:30:42 +0000 Subject: Move tests to cases git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@8660 5ecf4fe2-1ee6-0310-87b1-e25e094e27de --- activerecord/test/aaa_create_tables_test.rb | 72 - activerecord/test/active_schema_test_mysql.rb | 46 - activerecord/test/adapter_test.rb | 106 - activerecord/test/adapter_test_sqlserver.rb | 95 - activerecord/test/aggregations_test.rb | 128 -- activerecord/test/ar_schema_test.rb | 33 - activerecord/test/associations/callbacks_test.rb | 147 -- .../associations/cascaded_eager_loading_test.rb | 110 - .../associations/eager_singularization_test.rb | 145 -- activerecord/test/associations/eager_test.rb | 447 ---- activerecord/test/associations/extension_test.rb | 47 - .../associations/inner_join_association_test.rb | 88 - activerecord/test/associations/join_model_test.rb | 559 ----- activerecord/test/associations_test.rb | 2177 -------------------- activerecord/test/attribute_methods_test.rb | 146 -- activerecord/test/base_test.rb | 1831 ---------------- activerecord/test/binary_test.rb | 32 - activerecord/test/calculations_test.rb | 251 --- activerecord/test/callbacks_test.rb | 400 ---- activerecord/test/cases/aaa_create_tables_test.rb | 72 + .../test/cases/active_schema_test_mysql.rb | 46 + activerecord/test/cases/adapter_test.rb | 106 + activerecord/test/cases/adapter_test_sqlserver.rb | 95 + activerecord/test/cases/aggregations_test.rb | 128 ++ activerecord/test/cases/ar_schema_test.rb | 33 + .../test/cases/associations/callbacks_test.rb | 146 ++ .../associations/cascaded_eager_loading_test.rb | 110 + .../associations/eager_singularization_test.rb | 145 ++ activerecord/test/cases/associations/eager_test.rb | 447 ++++ .../test/cases/associations/extension_test.rb | 47 + .../associations/inner_join_association_test.rb | 88 + .../test/cases/associations/join_model_test.rb | 559 +++++ activerecord/test/cases/associations_test.rb | 2177 ++++++++++++++++++++ activerecord/test/cases/attribute_methods_test.rb | 146 ++ activerecord/test/cases/base_test.rb | 1831 ++++++++++++++++ activerecord/test/cases/binary_test.rb | 32 + activerecord/test/cases/calculations_test.rb | 251 +++ activerecord/test/cases/callbacks_test.rb | 400 ++++ .../cases/class_inheritable_attributes_test.rb | 32 + activerecord/test/cases/column_alias_test.rb | 17 + .../test/cases/connection_test_firebird.rb | 8 + activerecord/test/cases/connection_test_mysql.rb | 30 + activerecord/test/cases/copy_table_test_sqlite.rb | 69 + .../test/cases/datatype_test_postgresql.rb | 203 ++ activerecord/test/cases/date_time_test.rb | 37 + activerecord/test/cases/default_test_firebird.rb | 16 + activerecord/test/cases/defaults_test.rb | 67 + activerecord/test/cases/deprecated_finder_test.rb | 30 + activerecord/test/cases/finder_test.rb | 650 ++++++ activerecord/test/cases/fixtures_test.rb | 594 ++++++ activerecord/test/cases/inheritance_test.rb | 211 ++ activerecord/test/cases/json_serialization_test.rb | 180 ++ activerecord/test/cases/lifecycle_test.rb | 137 ++ activerecord/test/cases/locking_test.rb | 282 +++ activerecord/test/cases/method_scoping_test.rb | 416 ++++ activerecord/test/cases/migration_test.rb | 987 +++++++++ activerecord/test/cases/migration_test_firebird.rb | 124 ++ activerecord/test/cases/mixin_test.rb | 95 + activerecord/test/cases/modules_test.rb | 34 + activerecord/test/cases/multiple_db_test.rb | 60 + activerecord/test/cases/pk_test.rb | 101 + activerecord/test/cases/query_cache_test.rb | 124 ++ activerecord/test/cases/readonly_test.rb | 107 + activerecord/test/cases/reflection_test.rb | 175 ++ .../test/cases/reserved_word_test_mysql.rb | 177 ++ .../cases/schema_authorization_test_postgresql.rb | 75 + activerecord/test/cases/schema_dumper_test.rb | 131 ++ activerecord/test/cases/schema_test_postgresql.rb | 64 + activerecord/test/cases/serialization_test.rb | 47 + activerecord/test/cases/synonym_test_oracle.rb | 17 + .../test/cases/table_name_test_sqlserver.rb | 23 + .../test/cases/threaded_connections_test.rb | 48 + activerecord/test/cases/transactions_test.rb | 281 +++ activerecord/test/cases/unconnected_test.rb | 32 + activerecord/test/cases/validations_test.rb | 1407 +++++++++++++ activerecord/test/cases/xml_serialization_test.rb | 202 ++ .../test/class_inheritable_attributes_test.rb | 32 - activerecord/test/column_alias_test.rb | 17 - activerecord/test/connection_test_firebird.rb | 8 - activerecord/test/connection_test_mysql.rb | 30 - .../test/connections/native_db2/connection.rb | 2 +- .../test/connections/native_firebird/connection.rb | 2 +- .../connections/native_frontbase/connection.rb | 2 +- .../test/connections/native_mysql/connection.rb | 2 +- .../test/connections/native_openbase/connection.rb | 2 +- .../test/connections/native_oracle/connection.rb | 2 +- .../connections/native_postgresql/connection.rb | 2 +- .../test/connections/native_sqlite/connection.rb | 2 +- .../test/connections/native_sqlite3/connection.rb | 2 +- .../native_sqlite3/in_memory_connection.rb | 2 +- .../test/connections/native_sybase/connection.rb | 2 +- activerecord/test/copy_table_test_sqlite.rb | 69 - activerecord/test/datatype_test_postgresql.rb | 203 -- activerecord/test/date_time_test.rb | 37 - activerecord/test/default_test_firebird.rb | 16 - activerecord/test/defaults_test.rb | 67 - activerecord/test/deprecated_finder_test.rb | 30 - activerecord/test/finder_test.rb | 650 ------ activerecord/test/fixtures_test.rb | 594 ------ activerecord/test/inheritance_test.rb | 211 -- activerecord/test/json_serialization_test.rb | 180 -- activerecord/test/lifecycle_test.rb | 137 -- activerecord/test/locking_test.rb | 282 --- activerecord/test/method_scoping_test.rb | 416 ---- activerecord/test/migration_test.rb | 988 --------- activerecord/test/migration_test_firebird.rb | 124 -- activerecord/test/mixin_test.rb | 95 - activerecord/test/modules_test.rb | 34 - activerecord/test/multiple_db_test.rb | 60 - activerecord/test/pk_test.rb | 101 - activerecord/test/query_cache_test.rb | 124 -- activerecord/test/readonly_test.rb | 107 - activerecord/test/reflection_test.rb | 175 -- activerecord/test/reserved_word_test_mysql.rb | 177 -- .../test/schema_authorization_test_postgresql.rb | 75 - activerecord/test/schema_dumper_test.rb | 131 -- activerecord/test/schema_test_postgresql.rb | 64 - activerecord/test/serialization_test.rb | 47 - activerecord/test/synonym_test_oracle.rb | 17 - activerecord/test/table_name_test_sqlserver.rb | 23 - activerecord/test/threaded_connections_test.rb | 48 - activerecord/test/transactions_test.rb | 281 --- activerecord/test/unconnected_test.rb | 32 - activerecord/test/validations_test.rb | 1407 ------------- activerecord/test/xml_serialization_test.rb | 202 -- 125 files changed, 14160 insertions(+), 14162 deletions(-) delete mode 100644 activerecord/test/aaa_create_tables_test.rb delete mode 100644 activerecord/test/active_schema_test_mysql.rb delete mode 100644 activerecord/test/adapter_test.rb delete mode 100644 activerecord/test/adapter_test_sqlserver.rb delete mode 100644 activerecord/test/aggregations_test.rb delete mode 100644 activerecord/test/ar_schema_test.rb delete mode 100644 activerecord/test/associations/callbacks_test.rb delete mode 100644 activerecord/test/associations/cascaded_eager_loading_test.rb delete mode 100644 activerecord/test/associations/eager_singularization_test.rb delete mode 100644 activerecord/test/associations/eager_test.rb delete mode 100644 activerecord/test/associations/extension_test.rb delete mode 100644 activerecord/test/associations/inner_join_association_test.rb delete mode 100644 activerecord/test/associations/join_model_test.rb delete mode 100755 activerecord/test/associations_test.rb delete mode 100755 activerecord/test/attribute_methods_test.rb delete mode 100755 activerecord/test/base_test.rb delete mode 100644 activerecord/test/binary_test.rb delete mode 100644 activerecord/test/calculations_test.rb delete mode 100644 activerecord/test/callbacks_test.rb create mode 100644 activerecord/test/cases/aaa_create_tables_test.rb create mode 100644 activerecord/test/cases/active_schema_test_mysql.rb create mode 100644 activerecord/test/cases/adapter_test.rb create mode 100644 activerecord/test/cases/adapter_test_sqlserver.rb create mode 100644 activerecord/test/cases/aggregations_test.rb create mode 100644 activerecord/test/cases/ar_schema_test.rb create mode 100644 activerecord/test/cases/associations/callbacks_test.rb create mode 100644 activerecord/test/cases/associations/cascaded_eager_loading_test.rb create mode 100644 activerecord/test/cases/associations/eager_singularization_test.rb create mode 100644 activerecord/test/cases/associations/eager_test.rb create mode 100644 activerecord/test/cases/associations/extension_test.rb create mode 100644 activerecord/test/cases/associations/inner_join_association_test.rb create mode 100644 activerecord/test/cases/associations/join_model_test.rb create mode 100755 activerecord/test/cases/associations_test.rb create mode 100755 activerecord/test/cases/attribute_methods_test.rb create mode 100755 activerecord/test/cases/base_test.rb create mode 100644 activerecord/test/cases/binary_test.rb create mode 100644 activerecord/test/cases/calculations_test.rb create mode 100644 activerecord/test/cases/callbacks_test.rb create mode 100644 activerecord/test/cases/class_inheritable_attributes_test.rb create mode 100644 activerecord/test/cases/column_alias_test.rb create mode 100644 activerecord/test/cases/connection_test_firebird.rb create mode 100644 activerecord/test/cases/connection_test_mysql.rb create mode 100644 activerecord/test/cases/copy_table_test_sqlite.rb create mode 100644 activerecord/test/cases/datatype_test_postgresql.rb create mode 100644 activerecord/test/cases/date_time_test.rb create mode 100644 activerecord/test/cases/default_test_firebird.rb create mode 100644 activerecord/test/cases/defaults_test.rb create mode 100755 activerecord/test/cases/deprecated_finder_test.rb create mode 100644 activerecord/test/cases/finder_test.rb create mode 100755 activerecord/test/cases/fixtures_test.rb create mode 100755 activerecord/test/cases/inheritance_test.rb create mode 100644 activerecord/test/cases/json_serialization_test.rb create mode 100755 activerecord/test/cases/lifecycle_test.rb create mode 100644 activerecord/test/cases/locking_test.rb create mode 100644 activerecord/test/cases/method_scoping_test.rb create mode 100644 activerecord/test/cases/migration_test.rb create mode 100644 activerecord/test/cases/migration_test_firebird.rb create mode 100644 activerecord/test/cases/mixin_test.rb create mode 100644 activerecord/test/cases/modules_test.rb create mode 100644 activerecord/test/cases/multiple_db_test.rb create mode 100644 activerecord/test/cases/pk_test.rb create mode 100644 activerecord/test/cases/query_cache_test.rb create mode 100755 activerecord/test/cases/readonly_test.rb create mode 100644 activerecord/test/cases/reflection_test.rb create mode 100644 activerecord/test/cases/reserved_word_test_mysql.rb create mode 100644 activerecord/test/cases/schema_authorization_test_postgresql.rb create mode 100644 activerecord/test/cases/schema_dumper_test.rb create mode 100644 activerecord/test/cases/schema_test_postgresql.rb create mode 100644 activerecord/test/cases/serialization_test.rb create mode 100644 activerecord/test/cases/synonym_test_oracle.rb create mode 100644 activerecord/test/cases/table_name_test_sqlserver.rb create mode 100644 activerecord/test/cases/threaded_connections_test.rb create mode 100644 activerecord/test/cases/transactions_test.rb create mode 100755 activerecord/test/cases/unconnected_test.rb create mode 100755 activerecord/test/cases/validations_test.rb create mode 100644 activerecord/test/cases/xml_serialization_test.rb delete mode 100644 activerecord/test/class_inheritable_attributes_test.rb delete mode 100644 activerecord/test/column_alias_test.rb delete mode 100644 activerecord/test/connection_test_firebird.rb delete mode 100644 activerecord/test/connection_test_mysql.rb delete mode 100644 activerecord/test/copy_table_test_sqlite.rb delete mode 100644 activerecord/test/datatype_test_postgresql.rb delete mode 100644 activerecord/test/date_time_test.rb delete mode 100644 activerecord/test/default_test_firebird.rb delete mode 100644 activerecord/test/defaults_test.rb delete mode 100755 activerecord/test/deprecated_finder_test.rb delete mode 100644 activerecord/test/finder_test.rb delete mode 100755 activerecord/test/fixtures_test.rb delete mode 100755 activerecord/test/inheritance_test.rb delete mode 100644 activerecord/test/json_serialization_test.rb delete mode 100755 activerecord/test/lifecycle_test.rb delete mode 100644 activerecord/test/locking_test.rb delete mode 100644 activerecord/test/method_scoping_test.rb delete mode 100644 activerecord/test/migration_test.rb delete mode 100644 activerecord/test/migration_test_firebird.rb delete mode 100644 activerecord/test/mixin_test.rb delete mode 100644 activerecord/test/modules_test.rb delete mode 100644 activerecord/test/multiple_db_test.rb delete mode 100644 activerecord/test/pk_test.rb delete mode 100644 activerecord/test/query_cache_test.rb delete mode 100755 activerecord/test/readonly_test.rb delete mode 100644 activerecord/test/reflection_test.rb delete mode 100644 activerecord/test/reserved_word_test_mysql.rb delete mode 100644 activerecord/test/schema_authorization_test_postgresql.rb delete mode 100644 activerecord/test/schema_dumper_test.rb delete mode 100644 activerecord/test/schema_test_postgresql.rb delete mode 100644 activerecord/test/serialization_test.rb delete mode 100644 activerecord/test/synonym_test_oracle.rb delete mode 100644 activerecord/test/table_name_test_sqlserver.rb delete mode 100644 activerecord/test/threaded_connections_test.rb delete mode 100644 activerecord/test/transactions_test.rb delete mode 100755 activerecord/test/unconnected_test.rb delete mode 100755 activerecord/test/validations_test.rb delete mode 100644 activerecord/test/xml_serialization_test.rb (limited to 'activerecord/test') diff --git a/activerecord/test/aaa_create_tables_test.rb b/activerecord/test/aaa_create_tables_test.rb deleted file mode 100644 index f255182952..0000000000 --- a/activerecord/test/aaa_create_tables_test.rb +++ /dev/null @@ -1,72 +0,0 @@ -# The filename begins with "aaa" to ensure this is the first test. -require 'abstract_unit' - -class AAACreateTablesTest < ActiveSupport::TestCase - self.use_transactional_fixtures = false - - def setup - @base_path = "#{File.dirname(__FILE__)}/fixtures/db_definitions" - end - - def test_drop_and_create_main_tables - recreate ActiveRecord::Base unless use_migrations? - assert true - end - - def test_load_schema - if ActiveRecord::Base.connection.supports_migrations? - eval(File.read("#{File.dirname(__FILE__)}/fixtures/db_definitions/schema.rb")) - else - recreate ActiveRecord::Base, '3' - end - assert true - end - - def test_drop_and_create_courses_table - if Course.connection.supports_migrations? - eval(File.read("#{File.dirname(__FILE__)}/fixtures/db_definitions/schema2.rb")) - end - recreate Course, '2' unless use_migrations_for_courses? - assert true - end - - private - def use_migrations? - unittest_sql_filename = ActiveRecord::Base.connection.adapter_name.downcase + ".sql" - not File.exist? "#{@base_path}/#{unittest_sql_filename}" - end - - def use_migrations_for_courses? - unittest2_sql_filename = ActiveRecord::Base.connection.adapter_name.downcase + "2.sql" - not File.exist? "#{@base_path}/#{unittest2_sql_filename}" - end - - def recreate(base, suffix = nil) - connection = base.connection - adapter_name = connection.adapter_name.downcase + suffix.to_s - execute_sql_file "#{@base_path}/#{adapter_name}.drop.sql", connection - execute_sql_file "#{@base_path}/#{adapter_name}.sql", connection - end - - def execute_sql_file(path, connection) - # OpenBase has a different format for sql files - if current_adapter?(:OpenBaseAdapter) then - File.read(path).split("go").each_with_index do |sql, i| - begin - # OpenBase does not support comments embedded in sql - connection.execute(sql,"SQL statement ##{i}") unless sql.blank? - rescue ActiveRecord::StatementInvalid - #$stderr.puts "warning: #{$!}" - end - end - else - File.read(path).split(';').each_with_index do |sql, i| - begin - connection.execute("\n\n-- statement ##{i}\n#{sql}\n") unless sql.blank? - rescue ActiveRecord::StatementInvalid - #$stderr.puts "warning: #{$!}" - end - end - end - end -end diff --git a/activerecord/test/active_schema_test_mysql.rb b/activerecord/test/active_schema_test_mysql.rb deleted file mode 100644 index 336f74d234..0000000000 --- a/activerecord/test/active_schema_test_mysql.rb +++ /dev/null @@ -1,46 +0,0 @@ -require 'abstract_unit' - -class ActiveSchemaTest < ActiveSupport::TestCase - def setup - ActiveRecord::ConnectionAdapters::MysqlAdapter.class_eval do - alias_method :execute_without_stub, :execute - def execute(sql, name = nil) return sql end - end - end - - def teardown - ActiveRecord::ConnectionAdapters::MysqlAdapter.class_eval do - remove_method :execute - alias_method :execute, :execute_without_stub - end - end - - def test_drop_table - assert_equal "DROP TABLE `people`", drop_table(:people) - end - - if current_adapter?(:MysqlAdapter) - def test_create_mysql_database_with_encoding - assert_equal "CREATE DATABASE `matt` DEFAULT CHARACTER SET `utf8`", create_database(:matt) - assert_equal "CREATE DATABASE `aimonetti` DEFAULT CHARACTER SET `latin1`", create_database(:aimonetti, {:charset => 'latin1'}) - assert_equal "CREATE DATABASE `matt_aimonetti` DEFAULT CHARACTER SET `big5` COLLATE `big5_chinese_ci`", create_database(:matt_aimonetti, {:charset => :big5, :collation => :big5_chinese_ci}) - end - end - - def test_add_column - assert_equal "ALTER TABLE `people` ADD `last_name` varchar(255)", add_column(:people, :last_name, :string) - end - - def test_add_column_with_limit - assert_equal "ALTER TABLE `people` ADD `key` varchar(32)", add_column(:people, :key, :string, :limit => 32) - end - - def test_drop_table_with_specific_database - assert_equal "DROP TABLE `otherdb`.`people`", drop_table('otherdb.people') - end - - private - def method_missing(method_symbol, *arguments) - ActiveRecord::Base.connection.send(method_symbol, *arguments) - end -end diff --git a/activerecord/test/adapter_test.rb b/activerecord/test/adapter_test.rb deleted file mode 100644 index d93a99d7cd..0000000000 --- a/activerecord/test/adapter_test.rb +++ /dev/null @@ -1,106 +0,0 @@ -require 'abstract_unit' - -class AdapterTest < ActiveSupport::TestCase - def setup - @connection = ActiveRecord::Base.connection - end - - def test_tables - if @connection.respond_to?(:tables) - tables = @connection.tables - assert tables.include?("accounts") - assert tables.include?("authors") - assert tables.include?("tasks") - assert tables.include?("topics") - else - warn "#{@connection.class} does not respond to #tables" - end - 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 - - ensure - @connection.remove_index(:accounts, :name => idx_name) rescue nil - end - - def test_current_database - if @connection.respond_to?(:current_database) - assert_equal ENV['ARUNIT_DB_NAME'] || "activerecord_unittest", @connection.current_database - 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 - 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_show_nonexistent_variable_returns_nil - assert_nil @connection.show_variable('foo_bar_baz') - 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 - - 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 - end - end - - # test resetting sequences in odd tables in postgreSQL - if ActiveRecord::Base.connection.respond_to?(:reset_pk_sequence!) - require 'fixtures/movie' - require 'fixtures/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 - - 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 diff --git a/activerecord/test/adapter_test_sqlserver.rb b/activerecord/test/adapter_test_sqlserver.rb deleted file mode 100644 index 0556466bbd..0000000000 --- a/activerecord/test/adapter_test_sqlserver.rb +++ /dev/null @@ -1,95 +0,0 @@ -require 'abstract_unit' -require 'fixtures/default' -require 'fixtures/post' -require 'fixtures/task' - -class SqlServerAdapterTest < ActiveSupport::TestCase - class TableWithRealColumn < ActiveRecord::Base; end - - fixtures :posts, :tasks - - def setup - @connection = ActiveRecord::Base.connection - end - - def teardown - @connection.execute("SET LANGUAGE us_english") rescue nil - end - - def test_real_column_has_float_type - assert_equal :float, TableWithRealColumn.columns_hash["real_number"].type - end - - # SQL Server 2000 has a bug where some unambiguous date formats are not - # correctly identified if the session language is set to german - def test_date_insertion_when_language_is_german - @connection.execute("SET LANGUAGE deutsch") - - assert_nothing_raised do - Task.create(:starting => Time.utc(2000, 1, 31, 5, 42, 0), :ending => Date.new(2006, 12, 31)) - end - end - - def test_indexes_with_descending_order - # Make sure we have an index with descending order - @connection.execute "CREATE INDEX idx_credit_limit ON accounts (credit_limit DESC)" rescue nil - assert_equal ["credit_limit"], @connection.indexes('accounts').first.columns - ensure - @connection.execute "DROP INDEX accounts.idx_credit_limit" - end - - def test_execute_without_block_closes_statement - assert_all_statements_used_are_closed do - @connection.execute("SELECT 1") - end - end - - def test_execute_with_block_closes_statement - assert_all_statements_used_are_closed do - @connection.execute("SELECT 1") do |sth| - assert !sth.finished?, "Statement should still be alive within block" - end - end - end - - def test_insert_with_identity_closes_statement - assert_all_statements_used_are_closed do - @connection.insert("INSERT INTO accounts ([id], [firm_id],[credit_limit]) values (999, 1, 50)") - end - end - - def test_insert_without_identity_closes_statement - assert_all_statements_used_are_closed do - @connection.insert("INSERT INTO accounts ([firm_id],[credit_limit]) values (1, 50)") - end - end - - def test_active_closes_statement - assert_all_statements_used_are_closed do - @connection.active? - end - end - - def assert_all_statements_used_are_closed(&block) - existing_handles = [] - ObjectSpace.each_object(DBI::StatementHandle) {|handle| existing_handles << handle} - GC.disable - - yield - - used_handles = [] - ObjectSpace.each_object(DBI::StatementHandle) {|handle| used_handles << handle unless existing_handles.include? handle} - - assert_block "No statements were used within given block" do - used_handles.size > 0 - end - - ObjectSpace.each_object(DBI::StatementHandle) do |handle| - assert_block "Statement should have been closed within given block" do - handle.finished? - end - end - ensure - GC.enable - end -end diff --git a/activerecord/test/aggregations_test.rb b/activerecord/test/aggregations_test.rb deleted file mode 100644 index ea041fe955..0000000000 --- a/activerecord/test/aggregations_test.rb +++ /dev/null @@ -1,128 +0,0 @@ -require 'abstract_unit' -require 'fixtures/customer' - -class AggregationsTest < ActiveSupport::TestCase - fixtures :customers - - def test_find_single_value_object - assert_equal 50, customers(:david).balance.amount - assert_kind_of Money, customers(:david).balance - assert_equal 300, customers(:david).balance.exchange_to("DKK").amount - end - - def test_find_multiple_value_object - assert_equal customers(:david).address_street, customers(:david).address.street - assert( - customers(:david).address.close_to?(Address.new("Different Street", customers(:david).address_city, customers(:david).address_country)) - ) - end - - def test_change_single_value_object - customers(:david).balance = Money.new(100) - customers(:david).save - assert_equal 100, customers(:david).reload.balance.amount - end - - def test_immutable_value_objects - customers(:david).balance = Money.new(100) - assert_raise(ActiveSupport::FrozenObjectError) { customers(:david).balance.instance_eval { @amount = 20 } } - end - - def test_inferred_mapping - assert_equal "35.544623640962634", customers(:david).gps_location.latitude - assert_equal "-105.9309951055148", customers(:david).gps_location.longitude - - customers(:david).gps_location = GpsLocation.new("39x-110") - - assert_equal "39", customers(:david).gps_location.latitude - assert_equal "-110", customers(:david).gps_location.longitude - - customers(:david).save - - customers(:david).reload - - assert_equal "39", customers(:david).gps_location.latitude - assert_equal "-110", customers(:david).gps_location.longitude - end - - def test_reloaded_instance_refreshes_aggregations - assert_equal "35.544623640962634", customers(:david).gps_location.latitude - assert_equal "-105.9309951055148", customers(:david).gps_location.longitude - - Customer.update_all("gps_location = '24x113'") - customers(:david).reload - assert_equal '24x113', customers(:david)['gps_location'] - - assert_equal GpsLocation.new('24x113'), customers(:david).gps_location - end - - def test_gps_equality - assert GpsLocation.new('39x110') == GpsLocation.new('39x110') - end - - def test_gps_inequality - assert GpsLocation.new('39x110') != GpsLocation.new('39x111') - end - - def test_allow_nil_gps_is_nil - assert_equal nil, customers(:zaphod).gps_location - end - - def test_allow_nil_gps_set_to_nil - customers(:david).gps_location = nil - customers(:david).save - customers(:david).reload - assert_equal nil, customers(:david).gps_location - end - - def test_allow_nil_set_address_attributes_to_nil - customers(:zaphod).address = nil - assert_equal nil, customers(:zaphod).attributes[:address_street] - assert_equal nil, customers(:zaphod).attributes[:address_city] - assert_equal nil, customers(:zaphod).attributes[:address_country] - end - - def test_allow_nil_address_set_to_nil - customers(:zaphod).address = nil - customers(:zaphod).save - customers(:zaphod).reload - assert_equal nil, customers(:zaphod).address - end - - def test_nil_raises_error_when_allow_nil_is_false - assert_raise(NoMethodError) { customers(:david).balance = nil } - end - - def test_allow_nil_address_loaded_when_only_some_attributes_are_nil - customers(:zaphod).address_street = nil - customers(:zaphod).save - customers(:zaphod).reload - assert_kind_of Address, customers(:zaphod).address - assert customers(:zaphod).address.street.nil? - end - - def test_nil_assignment_results_in_nil - customers(:david).gps_location = GpsLocation.new('39x111') - assert_not_equal nil, customers(:david).gps_location - customers(:david).gps_location = nil - assert_equal nil, customers(:david).gps_location - end -end - -class OverridingAggregationsTest < ActiveSupport::TestCase - class Name; end - class DifferentName; end - - class Person < ActiveRecord::Base - composed_of :composed_of, :mapping => %w(person_first_name first_name) - end - - class DifferentPerson < Person - composed_of :composed_of, :class_name => 'DifferentName', :mapping => %w(different_person_first_name first_name) - end - - def test_composed_of_aggregation_redefinition_reflections_should_differ_and_not_inherited - assert_not_equal Person.reflect_on_aggregation(:composed_of), - DifferentPerson.reflect_on_aggregation(:composed_of) - end -end diff --git a/activerecord/test/ar_schema_test.rb b/activerecord/test/ar_schema_test.rb deleted file mode 100644 index c3dca54a94..0000000000 --- a/activerecord/test/ar_schema_test.rb +++ /dev/null @@ -1,33 +0,0 @@ -require 'abstract_unit' -require "#{File.dirname(__FILE__)}/../lib/active_record/schema" - -if ActiveRecord::Base.connection.supports_migrations? - - class ActiveRecordSchemaTest < ActiveSupport::TestCase - self.use_transactional_fixtures = false - - def setup - @connection = ActiveRecord::Base.connection - end - - def teardown - @connection.drop_table :fruits rescue nil - end - - def test_schema_define - ActiveRecord::Schema.define(:version => 7) do - create_table :fruits do |t| - t.column :color, :string - t.column :fruit_size, :string # NOTE: "size" is reserved in Oracle - t.column :texture, :string - t.column :flavor, :string - end - end - - assert_nothing_raised { @connection.select_all "SELECT * FROM fruits" } - assert_nothing_raised { @connection.select_all "SELECT * FROM schema_info" } - assert_equal 7, @connection.select_one("SELECT version FROM schema_info")['version'].to_i - end - end - -end diff --git a/activerecord/test/associations/callbacks_test.rb b/activerecord/test/associations/callbacks_test.rb deleted file mode 100644 index 29f93788e5..0000000000 --- a/activerecord/test/associations/callbacks_test.rb +++ /dev/null @@ -1,147 +0,0 @@ -require 'abstract_unit' -require 'fixtures/post' -require 'fixtures/comment' -require 'fixtures/author' -require 'fixtures/category' -require 'fixtures/project' -require 'fixtures/developer' - -class AssociationCallbacksTest < ActiveSupport::TestCase - fixtures :posts, :authors, :projects, :developers - - def setup - @david = authors(:david) - @thinking = posts(:thinking) - @authorless = posts(:authorless) - assert @david.post_log.empty? - end - - def test_adding_macro_callbacks - @david.posts_with_callbacks << @thinking - assert_equal ["before_adding#{@thinking.id}", "after_adding#{@thinking.id}"], @david.post_log - @david.posts_with_callbacks << @thinking - assert_equal ["before_adding#{@thinking.id}", "after_adding#{@thinking.id}", "before_adding#{@thinking.id}", - "after_adding#{@thinking.id}"], @david.post_log - end - - def test_adding_with_proc_callbacks - @david.posts_with_proc_callbacks << @thinking - assert_equal ["before_adding#{@thinking.id}", "after_adding#{@thinking.id}"], @david.post_log - @david.posts_with_proc_callbacks << @thinking - assert_equal ["before_adding#{@thinking.id}", "after_adding#{@thinking.id}", "before_adding#{@thinking.id}", - "after_adding#{@thinking.id}"], @david.post_log - end - - def test_removing_with_macro_callbacks - first_post, second_post = @david.posts_with_callbacks[0, 2] - @david.posts_with_callbacks.delete(first_post) - assert_equal ["before_removing#{first_post.id}", "after_removing#{first_post.id}"], @david.post_log - @david.posts_with_callbacks.delete(second_post) - assert_equal ["before_removing#{first_post.id}", "after_removing#{first_post.id}", "before_removing#{second_post.id}", - "after_removing#{second_post.id}"], @david.post_log - end - - def test_removing_with_proc_callbacks - first_post, second_post = @david.posts_with_callbacks[0, 2] - @david.posts_with_proc_callbacks.delete(first_post) - assert_equal ["before_removing#{first_post.id}", "after_removing#{first_post.id}"], @david.post_log - @david.posts_with_proc_callbacks.delete(second_post) - assert_equal ["before_removing#{first_post.id}", "after_removing#{first_post.id}", "before_removing#{second_post.id}", - "after_removing#{second_post.id}"], @david.post_log - end - - def test_multiple_callbacks - @david.posts_with_multiple_callbacks << @thinking - assert_equal ["before_adding#{@thinking.id}", "before_adding_proc#{@thinking.id}", "after_adding#{@thinking.id}", - "after_adding_proc#{@thinking.id}"], @david.post_log - @david.posts_with_multiple_callbacks << @thinking - assert_equal ["before_adding#{@thinking.id}", "before_adding_proc#{@thinking.id}", "after_adding#{@thinking.id}", - "after_adding_proc#{@thinking.id}", "before_adding#{@thinking.id}", "before_adding_proc#{@thinking.id}", - "after_adding#{@thinking.id}", "after_adding_proc#{@thinking.id}"], @david.post_log - end - - def test_has_many_callbacks_with_create - morten = Author.create :name => "Morten" - post = morten.posts_with_proc_callbacks.create! :title => "Hello", :body => "How are you doing?" - assert_equal ["before_adding", "after_adding#{post.id}"], morten.post_log - end - - def test_has_many_callbacks_with_create! - morten = Author.create! :name => "Morten" - post = morten.posts_with_proc_callbacks.create :title => "Hello", :body => "How are you doing?" - assert_equal ["before_adding", "after_adding#{post.id}"], morten.post_log - end - - def test_has_many_callbacks_for_save_on_parent - jack = Author.new :name => "Jack" - post = jack.posts_with_callbacks.build :title => "Call me back!", :body => "Before you wake up and after you sleep" - - callback_log = ["before_adding", "after_adding#{jack.posts_with_callbacks.first.id}"] - assert_equal callback_log, jack.post_log - assert jack.save - assert_equal 1, jack.posts_with_callbacks.count - assert_equal callback_log, jack.post_log - end - - def test_has_and_belongs_to_many_add_callback - david = developers(:david) - ar = projects(:active_record) - assert ar.developers_log.empty? - ar.developers_with_callbacks << david - assert_equal ["before_adding#{david.id}", "after_adding#{david.id}"], ar.developers_log - ar.developers_with_callbacks << david - assert_equal ["before_adding#{david.id}", "after_adding#{david.id}", "before_adding#{david.id}", - "after_adding#{david.id}"], ar.developers_log - end - - def test_has_and_belongs_to_many_remove_callback - david = developers(:david) - jamis = developers(:jamis) - activerecord = projects(:active_record) - assert activerecord.developers_log.empty? - activerecord.developers_with_callbacks.delete(david) - assert_equal ["before_removing#{david.id}", "after_removing#{david.id}"], activerecord.developers_log - - activerecord.developers_with_callbacks.delete(jamis) - assert_equal ["before_removing#{david.id}", "after_removing#{david.id}", "before_removing#{jamis.id}", - "after_removing#{jamis.id}"], activerecord.developers_log - end - - def test_has_and_belongs_to_many_remove_callback_on_clear - activerecord = projects(:active_record) - assert activerecord.developers_log.empty? - if activerecord.developers_with_callbacks.size == 0 - activerecord.developers << developers(:david) - activerecord.developers << developers(:jamis) - activerecord.reload - assert activerecord.developers_with_callbacks.size == 2 - end - log_array = activerecord.developers_with_callbacks.collect {|d| ["before_removing#{d.id}","after_removing#{d.id}"]}.flatten.sort - assert activerecord.developers_with_callbacks.clear - assert_equal log_array, activerecord.developers_log.sort - end - - def test_has_many_and_belongs_to_many_callbacks_for_save_on_parent - project = Project.new :name => "Callbacks" - project.developers_with_callbacks.build :name => "Jack", :salary => 95000 - - callback_log = ["before_adding", "after_adding"] - assert_equal callback_log, project.developers_log - assert project.save - assert_equal 1, project.developers_with_callbacks.size - assert_equal callback_log, project.developers_log - end - - def test_dont_add_if_before_callback_raises_exception - assert !@david.unchangable_posts.include?(@authorless) - begin - @david.unchangable_posts << @authorless - rescue Exception => e - end - assert @david.post_log.empty? - assert !@david.unchangable_posts.include?(@authorless) - @david.reload - assert !@david.unchangable_posts.include?(@authorless) - end -end - diff --git a/activerecord/test/associations/cascaded_eager_loading_test.rb b/activerecord/test/associations/cascaded_eager_loading_test.rb deleted file mode 100644 index 9d6dffbd48..0000000000 --- a/activerecord/test/associations/cascaded_eager_loading_test.rb +++ /dev/null @@ -1,110 +0,0 @@ -require 'abstract_unit' -require 'fixtures/post' -require 'fixtures/comment' -require 'fixtures/author' -require 'fixtures/category' -require 'fixtures/categorization' -require 'fixtures/company' -require 'fixtures/topic' -require 'fixtures/reply' - -class CascadedEagerLoadingTest < ActiveSupport::TestCase - fixtures :authors, :mixins, :companies, :posts, :topics - - def test_eager_association_loading_with_cascaded_two_levels - authors = Author.find(:all, :include=>{:posts=>:comments}, :order=>"authors.id") - assert_equal 2, authors.size - assert_equal 5, authors[0].posts.size - assert_equal 1, authors[1].posts.size - assert_equal 9, authors[0].posts.collect{|post| post.comments.size }.inject(0){|sum,i| sum+i} - end - - def test_eager_association_loading_with_cascaded_two_levels_and_one_level - authors = Author.find(:all, :include=>[{:posts=>:comments}, :categorizations], :order=>"authors.id") - assert_equal 2, authors.size - assert_equal 5, authors[0].posts.size - assert_equal 1, authors[1].posts.size - assert_equal 9, authors[0].posts.collect{|post| post.comments.size }.inject(0){|sum,i| sum+i} - assert_equal 1, authors[0].categorizations.size - assert_equal 2, authors[1].categorizations.size - 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") - assert_equal 2, authors.size - assert_equal 5, authors[0].posts.size - assert_equal 1, authors[1].posts.size - assert_equal 9, authors[0].posts.collect{|post| post.comments.size }.inject(0){|sum,i| sum+i} - 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") - assert_equal 2, authors.size - assert_equal 5, authors[0].posts.size - assert_equal authors(:david).name, authors[0].name - assert_equal [authors(:david).name], authors[0].posts.collect{|post| post.author.name}.uniq - 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") - 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") - 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 } - assert_equal companies(:first_firm).account.firm.account, assert_no_queries { firms.first.account.firm.account } - end - - def test_eager_association_loading_with_has_many_sti - topics = Topic.find(:all, :include => :replies, :order => 'topics.id') - assert_equal topics(:first, :second), topics - assert_no_queries do - assert_equal 1, topics[0].replies.size - assert_equal 0, topics[1].replies.size - end - end - - def test_eager_association_loading_with_belongs_to_sti - replies = Reply.find(:all, :include => :topic, :order => 'topics.id') - assert_equal [topics(:second)], replies - 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') - assert_equal authors(:david), author - assert_no_queries do - author.posts.first.special_comments - author.posts.first.very_special_comment - end - 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') - assert_equal [authors(:david)], authors - assert_no_queries do - authors.first.posts.first.special_comments.first.post.special_comments - authors.first.posts.first.special_comments.first.post.very_special_comment - end - end -end - -require 'fixtures/vertex' -require 'fixtures/edge' -class CascadedEagerLoadingTest < ActiveSupport::TestCase - fixtures :edges, :vertices - - 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') - 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') - assert_equal vertices(:vertex_1), assert_no_queries { sink.sources.first.sources.first.sources.first.sources.first } - end -end diff --git a/activerecord/test/associations/eager_singularization_test.rb b/activerecord/test/associations/eager_singularization_test.rb deleted file mode 100644 index 2990684afc..0000000000 --- a/activerecord/test/associations/eager_singularization_test.rb +++ /dev/null @@ -1,145 +0,0 @@ -require 'abstract_unit' - -class Virus < ActiveRecord::Base - belongs_to :octopus -end -class Octopus < ActiveRecord::Base - has_one :virus -end -class Pass < ActiveRecord::Base - belongs_to :bus -end -class Bus < ActiveRecord::Base - has_many :passes -end -class Mess < ActiveRecord::Base - has_and_belongs_to_many :crises -end -class Crisis < ActiveRecord::Base - has_and_belongs_to_many :messes - has_many :analyses, :dependent => :destroy - has_many :successes, :through => :analyses - has_many :dresses, :dependent => :destroy - has_many :compresses, :through => :dresses -end -class Analysis < ActiveRecord::Base - belongs_to :crisis - belongs_to :success -end -class Success < ActiveRecord::Base - has_many :analyses, :dependent => :destroy - has_many :crises, :through => :analyses -end -class Dress < ActiveRecord::Base - belongs_to :crisis - has_many :compresses -end -class Compress < ActiveRecord::Base - belongs_to :dress -end - - -class EagerSingularizationTest < ActiveSupport::TestCase - - def setup - if ActiveRecord::Base.connection.supports_migrations? - ActiveRecord::Base.connection.create_table :viri do |t| - t.column :octopus_id, :integer - t.column :species, :string - end - ActiveRecord::Base.connection.create_table :octopi do |t| - t.column :species, :string - end - ActiveRecord::Base.connection.create_table :passes do |t| - t.column :bus_id, :integer - t.column :rides, :integer - end - ActiveRecord::Base.connection.create_table :buses do |t| - t.column :name, :string - end - ActiveRecord::Base.connection.create_table :crises_messes, :id => false do |t| - t.column :crisis_id, :integer - t.column :mess_id, :integer - end - ActiveRecord::Base.connection.create_table :messes do |t| - t.column :name, :string - end - ActiveRecord::Base.connection.create_table :crises do |t| - t.column :name, :string - end - ActiveRecord::Base.connection.create_table :successes do |t| - t.column :name, :string - end - ActiveRecord::Base.connection.create_table :analyses do |t| - t.column :crisis_id, :integer - t.column :success_id, :integer - end - ActiveRecord::Base.connection.create_table :dresses do |t| - t.column :crisis_id, :integer - end - ActiveRecord::Base.connection.create_table :compresses do |t| - t.column :dress_id, :integer - end - @have_tables = true - else - @have_tables = false - end - end - - def teardown - ActiveRecord::Base.connection.drop_table :viri - ActiveRecord::Base.connection.drop_table :octopi - ActiveRecord::Base.connection.drop_table :passes - ActiveRecord::Base.connection.drop_table :buses - ActiveRecord::Base.connection.drop_table :crises_messes - ActiveRecord::Base.connection.drop_table :messes - ActiveRecord::Base.connection.drop_table :crises - ActiveRecord::Base.connection.drop_table :successes - ActiveRecord::Base.connection.drop_table :analyses - ActiveRecord::Base.connection.drop_table :dresses - ActiveRecord::Base.connection.drop_table :compresses - end - - def test_eager_no_extra_singularization_belongs_to - return unless @have_tables - assert_nothing_raised do - Virus.find(:all, :include => :octopus) - end - end - - def test_eager_no_extra_singularization_has_one - return unless @have_tables - assert_nothing_raised do - Octopus.find(:all, :include => :virus) - end - end - - def test_eager_no_extra_singularization_has_many - return unless @have_tables - assert_nothing_raised do - Bus.find(:all, :include => :passes) - 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) - 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) - 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) - end - end -end diff --git a/activerecord/test/associations/eager_test.rb b/activerecord/test/associations/eager_test.rb deleted file mode 100644 index bdadada3f4..0000000000 --- a/activerecord/test/associations/eager_test.rb +++ /dev/null @@ -1,447 +0,0 @@ -require 'abstract_unit' -require 'fixtures/post' -require 'fixtures/comment' -require 'fixtures/author' -require 'fixtures/category' -require 'fixtures/company' -require 'fixtures/person' -require 'fixtures/reader' - -class EagerAssociationTest < ActiveSupport::TestCase - fixtures :posts, :comments, :authors, :categories, :categories_posts, - :companies, :accounts, :tags, :people, :readers - - def test_loading_with_one_association - posts = Post.find(:all, :include => :comments) - 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'") - assert_equal 2, post.comments.size - assert post.comments.include?(comments(:greetings)) - 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'") - 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") - [:eager_other, :sti_habtm, :sti_post_and_comments, :sti_comments, - :authorless, :thinking, :welcome - ].each_with_index do |post, index| - assert_equal posts(post), list[index] - end - end - - def test_with_two_tables_in_from_without_getting_double_quoted - posts = Post.find(:all, - :select => "posts.*", - :from => "authors, posts", - :include => :comments, - :conditions => "posts.author_id = authors.id", - :order => "posts.id" - ) - - assert_equal 2, posts.first.comments.size - end - - def test_loading_with_multiple_associations - posts = Post.find(:all, :include => [ :comments, :author, :categories ], :order => "posts.id") - assert_equal 2, posts.first.comments.size - assert_equal 2, posts.first.categories.size - assert posts.first.comments.include?(comments(:greetings)) - end - - def test_loading_from_an_association - posts = authors(:david).posts.find(:all, :include => :comments, :order => "posts.id") - assert_equal 2, posts.first.comments.size - end - - def test_loading_with_no_associations - assert_nil Post.find(posts(:authorless).id, :include => :author).author - end - - def test_eager_association_loading_with_belongs_to - comments = Comment.find(:all, :include => :post) - assert_equal 10, comments.length - titles = comments.map { |c| c.post.title } - assert titles.include?(posts(:welcome).title) - assert titles.include?(posts(:sti_post_and_comments).title) - end - - def test_eager_association_loading_with_belongs_to_and_limit - comments = Comment.find(:all, :include => :post, :limit => 5, :order => 'comments.id') - 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') - 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') - 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') - 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') - 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_multiple_associations - posts = Post.find(:all, :include => [:author, :very_special_comment], :limit => 1, :order => 'posts.id') - 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') - 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) - assert_equal authors(:mary), assert_no_queries { author_favorite.favorite_author } - 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') - assert_equal 1, posts.length - end - - def test_eager_with_has_many_through - posts_with_comments = people(:michael).posts.find(:all, :include => :comments) - posts_with_author = people(:michael).posts.find(:all, :include => :author ) - posts_with_comments_and_author = people(:michael).posts.find(:all, :include => [ :comments, :author ]) - 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 } - end - - def test_eager_with_has_many_through_an_sti_join_model - author = Author.find(:first, :include => :special_post_comments, :order => 'authors.id') - 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') - 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) - end - - def test_eager_with_has_many_and_limit - posts = Post.find(:all, :order => 'posts.id asc', :include => [ :author, :comments ], :limit => 2) - 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") - else - posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :conditions => "posts.body = 'hello'", :order => "posts.id") - 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 - if current_adapter?(:OpenBaseAdapter) - posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :conditions => [ "FETCHBLOB(posts.body) = ?", 'hello' ], :order => "posts.id") - else - posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :conditions => [ "posts.body = ?", 'hello' ], :order => "posts.id") - 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' ]) - assert_equal 2, posts.size - - count = Post.count(:include => [ :author, :comments ], :limit => 2, :conditions => [ "authors.name = ?", 'David' ]) - assert_equal count, posts.size - end - - def test_eager_with_has_many_and_limit_ond_high_offset - posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :offset => 10, :conditions => [ "authors.name = ?", 'David' ]) - assert_equal 0, posts.size - end - - def test_count_eager_with_has_many_and_limit_ond_high_offset - posts = Post.count(:all, :include => [ :author, :comments ], :limit => 2, :offset => 10, :conditions => [ "authors.name = ?", 'David' ]) - 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'") - 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') - end - - def test_eager_with_has_and_belongs_to_many_and_limit - posts = Post.find(:all, :include => :categories, :order => "posts.id", :limit => 3) - assert_equal 3, posts.size - assert_equal 2, posts[0].categories.size - assert_equal 1, posts[1].categories.size - assert_equal 0, posts[2].categories.size - assert posts[0].categories.include?(categories(:technology)) - assert posts[1].categories.include?(categories(:general)) - 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 - ) - 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 - ) - assert_equal count, posts.size - end - - def test_eager_with_has_many_and_limit_and_scoped_conditions_on_the_eagers - posts = nil - Post.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.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 - end - end - - def test_eager_with_has_many_and_limit_and_scoped_and_explicit_conditions_on_the_eagers - Post.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 - - count = Post.count( - :include => [ :comments, :author ], - :conditions => "authors.name = 'David' AND (comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment')", - :limit => 2 - ) - 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.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") - assert_equal 2, posts[0].categories.size - assert_equal 1, posts[1].categories.size - assert_equal 0, posts[2].categories.size - assert posts[0].categories.include?(categories(:technology)) - assert posts[1].categories.include?(categories(:general)) - end - - def test_eager_with_inheritance - posts = SpecialPost.find(:all, :include => [ :comments ]) - end - - def test_eager_has_one_with_association_inheritance - post = Post.find(4, :include => [ :very_special_comment ]) - 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.special_comments.each do |special_comment| - assert_equal "SpecialComment", special_comment.class.to_s - end - end - - def test_eager_habtm_with_association_inheritance - post = Post.find(6, :include => [ :special_categories ]) - assert_equal 1, post.special_categories.size - post.special_categories.each do |special_category| - assert_equal "SpecialCategory", special_category.class.to_s - end - end - - 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"]) - assert_not_nil f.account - assert_equal companies(:first_firm, :reload).account, f.account - end - - def test_eager_with_multi_table_conditional_properly_counts_the_records_when_using_size - author = authors(:david) - posts_with_no_comments = author.posts.select { |post| post.comments.blank? } - assert_equal posts_with_no_comments.size, author.posts_with_no_comments.size - assert_equal posts_with_no_comments, author.posts_with_no_comments - end - - def test_eager_with_invalid_association_reference - assert_raises(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") { - post = Post.find(6, :include=> :monkeys ) - } - assert_raises(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") { - post = Post.find(6, :include=>[ :monkeys ]) - } - assert_raises(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") { - post = Post.find(6, :include=>[ 'monkeys' ]) - } - assert_raises(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys, :elephants") { - post = Post.find(6, :include=>[ :monkeys, :elephants ]) - } - end - - def find_all_ordered(className, include=nil) - className.find(:all, :order=>"#{className.table_name}.#{className.primary_key}", :include=>include) - 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) - 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) - end - - def test_eager_with_multiple_associations_with_same_table_has_many_and_habtm - # Eager includes of has many and habtm associations aren't necessarily sorted in the same way - def assert_equal_after_sort(item1, item2, item3 = nil) - assert_equal(item1.sort{|a,b| a.id <=> b.id}, item2.sort{|a,b| a.id <=> b.id}) - assert_equal(item3.sort{|a,b| a.id <=> b.id}, item2.sort{|a,b| a.id <=> b.id}) if item3 - end - # Test regular association, association with conditions, association with - # STI, and association with conditions assured not to be true - post_types = [:posts, :other_posts, :special_posts] - # test both has_many and has_and_belongs_to_many - [Author, Category].each do |className| - d1 = find_all_ordered(className) - # test including all post types at once - d2 = find_all_ordered(className, post_types) - d1.each_index do |i| - assert_equal(d1[i], d2[i]) - assert_equal_after_sort(d1[i].posts, d2[i].posts) - post_types[1..-1].each do |post_type| - # test including post_types together - d3 = find_all_ordered(className, [:posts, post_type]) - assert_equal(d1[i], d3[i]) - assert_equal_after_sort(d1[i].posts, d3[i].posts) - assert_equal_after_sort(d1[i].send(post_type), d2[i].send(post_type), d3[i].send(post_type)) - end - end - end - end - - def test_eager_with_multiple_associations_with_same_table_has_one - d1 = find_all_ordered(Firm) - d2 = find_all_ordered(Firm, :account) - d1.each_index do |i| - assert_equal(d1[i], d2[i]) - assert_equal(d1[i].account, d2[i].account) - end - end - - def test_eager_with_multiple_associations_with_same_table_belongs_to - firm_types = [:firm, :firm_with_basic_id, :firm_with_other_name, :firm_with_condition] - d1 = find_all_ordered(Client) - d2 = find_all_ordered(Client, firm_types) - d1.each_index do |i| - assert_equal(d1[i], d2[i]) - firm_types.each { |type| assert_equal(d1[i].send(type), d2[i].send(type)) } - end - end - def test_eager_with_valid_association_as_string_not_symbol - assert_nothing_raised { Post.find(:all, :include => 'comments') } - end - - def test_preconfigured_includes_with_belongs_to - author = posts(:welcome).author_with_posts - assert_equal 5, author.posts.size - end - - def test_preconfigured_includes_with_has_one - comment = posts(:sti_comments).very_special_comment_with_post - assert_equal posts(:sti_comments), comment.post - end - - def test_preconfigured_includes_with_has_many - posts = authors(:david).posts_with_comments - one = posts.detect { |p| p.id == 1 } - assert_equal 5, posts.size - assert_equal 2, one.comments.size - end - - def test_preconfigured_includes_with_habtm - posts = authors(:david).posts_with_categories - one = posts.detect { |p| p.id == 1 } - assert_equal 5, posts.size - assert_equal 2, one.categories.size - end - - def test_preconfigured_includes_with_has_many_and_habtm - posts = authors(:david).posts_with_comments_and_categories - one = posts.detect { |p| p.id == 1 } - assert_equal 5, posts.size - assert_equal 2, one.comments.size - assert_equal 2, one.categories.size - end - - def test_count_with_include - if current_adapter?(:SQLServerAdapter, :SybaseAdapter) - assert_equal 3, authors(:david).posts_with_comments.count(:conditions => "len(comments.body) > 15") - elsif current_adapter?(:OpenBaseAdapter) - assert_equal 3, authors(:david).posts_with_comments.count(:conditions => "length(FETCHBLOB(comments.body)) > 15") - else - assert_equal 3, authors(:david).posts_with_comments.count(:conditions => "length(comments.body) > 15") - end - end -end diff --git a/activerecord/test/associations/extension_test.rb b/activerecord/test/associations/extension_test.rb deleted file mode 100644 index 3749f7ac41..0000000000 --- a/activerecord/test/associations/extension_test.rb +++ /dev/null @@ -1,47 +0,0 @@ -require 'abstract_unit' -require 'fixtures/post' -require 'fixtures/comment' -require 'fixtures/project' -require 'fixtures/developer' - -class AssociationsExtensionsTest < ActiveSupport::TestCase - fixtures :projects, :developers, :developers_projects, :comments, :posts - - def test_extension_on_has_many - assert_equal comments(:more_greetings), posts(:welcome).comments.find_most_recent - end - - def test_extension_on_habtm - assert_equal projects(:action_controller), developers(:david).projects.find_most_recent - end - - def test_named_extension_on_habtm - assert_equal projects(:action_controller), developers(:david).projects_extended_by_name.find_most_recent - end - - def test_named_two_extensions_on_habtm - assert_equal projects(:action_controller), developers(:david).projects_extended_by_name_twice.find_most_recent - assert_equal projects(:active_record), developers(:david).projects_extended_by_name_twice.find_least_recent - end - - def test_named_extension_and_block_on_habtm - assert_equal projects(:action_controller), developers(:david).projects_extended_by_name_and_block.find_most_recent - assert_equal projects(:active_record), developers(:david).projects_extended_by_name_and_block.find_least_recent - end - - def test_marshalling_extensions - david = developers(:david) - assert_equal projects(:action_controller), david.projects.find_most_recent - - david = Marshal.load(Marshal.dump(david)) - assert_equal projects(:action_controller), david.projects.find_most_recent - end - - def test_marshalling_named_extensions - david = developers(:david) - assert_equal projects(:action_controller), david.projects_extended_by_name.find_most_recent - - david = Marshal.load(Marshal.dump(david)) - assert_equal projects(:action_controller), david.projects_extended_by_name.find_most_recent - end -end \ No newline at end of file diff --git a/activerecord/test/associations/inner_join_association_test.rb b/activerecord/test/associations/inner_join_association_test.rb deleted file mode 100644 index 56735afae5..0000000000 --- a/activerecord/test/associations/inner_join_association_test.rb +++ /dev/null @@ -1,88 +0,0 @@ -require 'abstract_unit' -require 'fixtures/post' -require 'fixtures/comment' -require 'fixtures/author' -require 'fixtures/category' -require 'fixtures/categorization' - -class InnerJoinAssociationTest < ActiveSupport::TestCase - fixtures :authors, :posts, :comments, :categories, :categories_posts, :categorizations - - def test_construct_finder_sql_creates_inner_joins - sql = Author.send(:construct_finder_sql, :joins => :posts) - assert_match /INNER JOIN .?posts.? ON .?posts.?.author_id = authors.id/, sql - end - - def test_construct_finder_sql_cascades_inner_joins - sql = Author.send(:construct_finder_sql, :joins => {:posts => :comments}) - assert_match /INNER JOIN .?posts.? ON .?posts.?.author_id = authors.id/, sql - assert_match /INNER JOIN .?comments.? ON .?comments.?.post_id = posts.id/, sql - end - - def test_construct_finder_sql_inner_joins_through_associations - sql = Author.send(:construct_finder_sql, :joins => :categorized_posts) - assert_match /INNER JOIN .?categorizations.?.*INNER JOIN .?posts.?/, sql - end - - def test_construct_finder_sql_applies_association_conditions - sql = Author.send(:construct_finder_sql, :joins => :categories_like_general, :conditions => "TERMINATING_MARKER") - assert_match /INNER JOIN .?categories.? ON.*AND.*.?General.?.*TERMINATING_MARKER/, sql - end - - def test_construct_finder_sql_unpacks_nested_joins - sql = Author.send(:construct_finder_sql, :joins => {:posts => [[:comments]]}) - assert_no_match /inner join.*inner join.*inner join/i, sql, "only two join clauses should be present" - assert_match /INNER JOIN .?posts.? ON .?posts.?.author_id = authors.id/, sql - assert_match /INNER JOIN .?comments.? ON .?comments.?.post_id = .?posts.?.id/, sql - end - - def test_construct_finder_sql_ignores_empty_joins_hash - sql = Author.send(:construct_finder_sql, :joins => {}) - assert_no_match /JOIN/i, sql - end - - def test_construct_finder_sql_ignores_empty_joins_array - sql = Author.send(:construct_finder_sql, :joins => []) - assert_no_match /JOIN/i, sql - end - - def test_find_with_implicit_inner_joins_honors_readonly_without_select - authors = Author.find(:all, :joins => :posts) - assert !authors.empty?, "expected authors to be non-empty" - assert authors.all? {|a| a.readonly? }, "expected all authors to be readonly" - end - - def test_find_with_implicit_inner_joins_honors_readonly_with_select - authors = Author.find(:all, :select => 'authors.*', :joins => :posts) - assert !authors.empty?, "expected authors to be non-empty" - assert authors.all? {|a| !a.readonly? }, "expected no authors to be readonly" - end - - def test_find_with_implicit_inner_joins_honors_readonly_false - authors = Author.find(:all, :joins => :posts, :readonly => false) - assert !authors.empty?, "expected authors to be non-empty" - assert authors.all? {|a| !a.readonly? }, "expected no authors to be readonly" - end - - def test_find_with_implicit_inner_joins_does_not_set_associations - authors = Author.find(:all, :select => 'authors.*', :joins => :posts) - 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" - end - - def test_count_honors_implicit_inner_joins - real_count = Author.find(:all).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" - end - - def test_calculate_honors_implicit_inner_joins - real_count = Author.find(:all).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" - end - - def test_calculate_honors_implicit_inner_joins_and_distinct_and_conditions - real_count = Author.find(:all).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%'") - assert_equal real_count, authors_with_welcoming_post_titles, "inner join and conditions should have only returned authors posting titles starting with 'Welcome'" - end -end diff --git a/activerecord/test/associations/join_model_test.rb b/activerecord/test/associations/join_model_test.rb deleted file mode 100644 index d55faf70c6..0000000000 --- a/activerecord/test/associations/join_model_test.rb +++ /dev/null @@ -1,559 +0,0 @@ -require 'abstract_unit' -require 'fixtures/tag' -require 'fixtures/tagging' -require 'fixtures/post' -require 'fixtures/item' -require 'fixtures/comment' -require 'fixtures/author' -require 'fixtures/category' -require 'fixtures/categorization' -require 'fixtures/vertex' -require 'fixtures/edge' -require 'fixtures/book' -require 'fixtures/citation' - -class AssociationsJoinModelTest < ActiveSupport::TestCase - self.use_transactional_fixtures = false - fixtures :posts, :authors, :categories, :categorizations, :comments, :tags, :taggings, :author_favorites, :vertices, :items, :books - - def test_has_many - assert authors(:david).categories.include?(categories(:general)) - end - - def test_has_many_inherited - assert authors(:mary).categories.include?(categories(:sti_test)) - end - - def test_inherited_has_many - assert categories(:sti_test).authors.include?(authors(:mary)) - end - - def test_has_many_uniq_through_join_model - assert_equal 2, authors(:mary).categorized_posts.size - assert_equal 1, authors(:mary).unique_categorized_posts.size - end - - def test_has_many_uniq_through_count - author = authors(:mary) - 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 !authors(:mary).unique_categorized_posts.loaded? - end - - def test_polymorphic_has_many - assert posts(:welcome).taggings.include?(taggings(:welcome_general)) - end - - def test_polymorphic_has_one - assert_equal taggings(:welcome_general), posts(:welcome).tagging - end - - def test_polymorphic_belongs_to - assert_equal posts(:welcome), posts(:welcome).taggings.first.taggable - end - - def test_polymorphic_has_many_going_through_join_model - assert_equal tags(:general), tag = posts(:welcome).tags.first - assert_no_queries do - tag.tagging - end - end - - def test_count_polymorphic_has_many - assert_equal 1, posts(:welcome).taggings.count - assert_equal 1, posts(:welcome).tags.count - end - - def test_polymorphic_has_many_going_through_join_model_with_find - assert_equal tags(:general), tag = posts(:welcome).tags.find(:first) - assert_no_queries do - tag.tagging - end - end - - def test_polymorphic_has_many_going_through_join_model_with_include_on_source_reflection - assert_equal tags(:general), tag = posts(:welcome).funky_tags.first - assert_no_queries do - tag.tagging - end - 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_no_queries do - tag.tagging - end - end - - def test_polymorphic_has_many_going_through_join_model_with_disabled_include - assert_equal tags(:general), tag = posts(:welcome).tags.add_joins_and_select.first - assert_queries 1 do - tag.tagging - end - end - - def test_polymorphic_has_many_going_through_join_model_with_custom_select_and_joins - assert_equal tags(:general), tag = posts(:welcome).tags.add_joins_and_select.first - tag.author_id - end - - def test_polymorphic_has_many_going_through_join_model_with_custom_foreign_key - assert_equal tags(:misc), taggings(:welcome_general).super_tag - assert_equal tags(:misc), posts(:welcome).super_tags.first - end - - def test_polymorphic_has_many_create_model_with_inheritance_and_custom_base_class - post = SubStiPost.create :title => 'SubStiPost', :body => 'SubStiPost body' - assert_instance_of SubStiPost, post - - tagging = tags(:misc).taggings.create(:taggable => post) - assert_equal "SubStiPost", tagging.taggable_type - end - - def test_polymorphic_has_many_going_through_join_model_with_inheritance - assert_equal tags(:general), posts(:thinking).tags.first - end - - def test_polymorphic_has_many_going_through_join_model_with_inheritance_with_custom_class_name - assert_equal tags(:general), posts(:thinking).funky_tags.first - end - - def test_polymorphic_has_many_create_model_with_inheritance - post = posts(:thinking) - assert_instance_of SpecialPost, post - - tagging = tags(:misc).taggings.create(:taggable => post) - assert_equal "Post", tagging.taggable_type - end - - def test_polymorphic_has_one_create_model_with_inheritance - tagging = tags(:misc).create_tagging(:taggable => posts(:thinking)) - assert_equal "Post", tagging.taggable_type - end - - def test_set_polymorphic_has_many - tagging = tags(:misc).taggings.create - posts(:thinking).taggings << tagging - assert_equal "Post", tagging.taggable_type - end - - def test_set_polymorphic_has_one - tagging = tags(:misc).taggings.create - posts(:thinking).tagging = tagging - assert_equal "Post", tagging.taggable_type - end - - def test_create_polymorphic_has_many_with_scope - old_count = posts(:welcome).taggings.count - tagging = posts(:welcome).taggings.create(:tag => tags(:misc)) - assert_equal "Post", tagging.taggable_type - assert_equal old_count+1, posts(:welcome).taggings.count - end - - def test_create_bang_polymorphic_with_has_many_scope - old_count = posts(:welcome).taggings.count - tagging = posts(:welcome).taggings.create!(:tag => tags(:misc)) - assert_equal "Post", tagging.taggable_type - assert_equal old_count+1, posts(:welcome).taggings.count - end - - def test_create_polymorphic_has_one_with_scope - old_count = Tagging.count - tagging = posts(:welcome).tagging.create(:tag => tags(:misc)) - assert_equal "Post", tagging.taggable_type - assert_equal old_count+1, Tagging.count - end - - def test_delete_polymorphic_has_many_with_delete_all - assert_equal 1, posts(:welcome).taggings.count - posts(:welcome).taggings.first.update_attribute :taggable_type, 'PostWithHasManyDeleteAll' - post = find_post_with_dependency(1, :has_many, :taggings, :delete_all) - - old_count = Tagging.count - post.destroy - assert_equal old_count-1, Tagging.count - assert_equal 0, posts(:welcome).taggings.count - end - - def test_delete_polymorphic_has_many_with_destroy - assert_equal 1, posts(:welcome).taggings.count - posts(:welcome).taggings.first.update_attribute :taggable_type, 'PostWithHasManyDestroy' - post = find_post_with_dependency(1, :has_many, :taggings, :destroy) - - old_count = Tagging.count - post.destroy - assert_equal old_count-1, Tagging.count - assert_equal 0, posts(:welcome).taggings.count - end - - def test_delete_polymorphic_has_many_with_nullify - assert_equal 1, posts(:welcome).taggings.count - posts(:welcome).taggings.first.update_attribute :taggable_type, 'PostWithHasManyNullify' - post = find_post_with_dependency(1, :has_many, :taggings, :nullify) - - old_count = Tagging.count - post.destroy - assert_equal old_count, Tagging.count - assert_equal 0, posts(:welcome).taggings.count - end - - def test_delete_polymorphic_has_one_with_destroy - assert posts(:welcome).tagging - posts(:welcome).tagging.update_attribute :taggable_type, 'PostWithHasOneDestroy' - post = find_post_with_dependency(1, :has_one, :tagging, :destroy) - - old_count = Tagging.count - post.destroy - assert_equal old_count-1, Tagging.count - assert_nil posts(:welcome).tagging(true) - end - - def test_delete_polymorphic_has_one_with_nullify - assert posts(:welcome).tagging - posts(:welcome).tagging.update_attribute :taggable_type, 'PostWithHasOneNullify' - post = find_post_with_dependency(1, :has_one, :tagging, :nullify) - - old_count = Tagging.count - post.destroy - assert_equal old_count, Tagging.count - assert_nil posts(:welcome).tagging(true) - end - - def test_has_many_with_piggyback - assert_equal "2", categories(:sti_test).authors.first.post_id.to_s - 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') - 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 } - end - end - - def test_include_polymorphic_has_one - post = Post.find_by_id(posts(:welcome).id, :include => :tagging) - tagging = taggings(:welcome_general) - assert_no_queries do - assert_equal tagging, post.tagging - end - end - - def test_include_polymorphic_has_one_defined_in_abstract_parent - item = Item.find_by_id(items(:dvd).id, :include => :tagging) - tagging = taggings(:godfather) - assert_no_queries do - assert_equal tagging, item.tagging - end - 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') - 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 } - end - 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') - 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 } - end - end - - def test_has_many_find_all - assert_equal [categories(:general)], authors(:david).categories.find(:all) - end - - def test_has_many_find_first - assert_equal categories(:general), authors(:david).categories.find(:first) - end - - def test_has_many_with_hash_conditions - assert_equal categories(:general), authors(:david).categories_like_general.find(:first) - end - - def test_has_many_find_conditions - assert_equal categories(:general), authors(:david).categories.find(:first, :conditions => "categories.name = 'General'") - assert_equal 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_equal nil, authors(:david).categories.find_by_name('Technology') - end - - def test_has_many_going_through_join_model_with_custom_foreign_key - assert_equal [], posts(:thinking).authors - assert_equal [authors(:mary)], posts(:authorless).authors - end - - def test_belongs_to_polymorphic_with_counter_cache - assert_equal 0, posts(:welcome)[:taggings_count] - tagging = posts(:welcome).taggings.create(:tag => tags(:general)) - assert_equal 1, posts(:welcome, :reload)[:taggings_count] - tagging.destroy - assert posts(:welcome, :reload)[:taggings_count].zero? - end - - def test_unavailable_through_reflection - assert_raise(ActiveRecord::HasManyThroughAssociationNotFoundError) { authors(:david).nothings } - end - - def test_has_many_through_join_model_with_conditions - assert_equal [], posts(:welcome).invalid_taggings - assert_equal [], posts(:welcome).invalid_tags - end - - def test_has_many_polymorphic - assert_raise ActiveRecord::HasManyThroughAssociationPolymorphicError do - assert_equal posts(:welcome, :thinking), tags(:general).taggables - end - assert_raise ActiveRecord::EagerLoadPolymorphicError do - assert_equal posts(:welcome, :thinking), tags(:general).taggings.find(:all, :include => :taggable) - end - end - - def test_has_many_polymorphic_with_source_type - assert_equal posts(:welcome, :thinking), tags(:general).tagged_posts - end - - def test_eager_has_many_polymorphic_with_source_type - tag_with_include = Tag.find(tags(:general).id, :include => :tagged_posts) - desired = posts(:welcome, :thinking) - assert_no_queries do - assert_equal desired, tag_with_include.tagged_posts - end - end - - def test_has_many_through_has_many_find_all - assert_equal comments(:greetings), authors(:david).comments.find(:all, :order => 'comments.id').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 - end - - def test_has_many_through_has_many_find_first - assert_equal comments(:greetings), authors(:david).comments.find(:first, :order => 'comments.id') - 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) - end - - def test_has_many_through_has_many_find_by_id - assert_equal comments(:more_greetings), authors(:david).comments.find(2) - end - - def test_has_many_through_polymorphic_has_one - assert_raise(ActiveRecord::HasManyThroughSourceAssociationMacroError) { authors(:david).tagging } - end - - def test_has_many_through_polymorphic_has_many - assert_equal taggings(:welcome_general, :thinking_general), authors(:david).taggings.uniq.sort_by { |t| t.id } - end - - def test_include_has_many_through_polymorphic_has_many - author = Author.find_by_id(authors(:david).id, :include => :taggings) - expected_taggings = taggings(:welcome_general, :thinking_general) - assert_no_queries do - assert_equal expected_taggings, author.taggings.uniq.sort_by { |t| t.id } - end - end - - def test_has_many_through_has_many_through - assert_raise(ActiveRecord::HasManyThroughSourceAssociationMacroError) { authors(:david).tags } - end - - def test_has_many_through_habtm - assert_raise(ActiveRecord::HasManyThroughSourceAssociationMacroError) { authors(:david).post_categories } - end - - def test_eager_load_has_many_through_has_many - author = Author.find :first, :conditions => ['name = ?', 'David'], :include => :comments, :order => 'comments.id' - SpecialComment.new; VerySpecialComment.new - assert_no_queries do - assert_equal [1,2,3,5,6,7,8,9,10], author.comments.collect(&:id) - end - end - - def test_eager_load_has_many_through_has_many_with_conditions - post = Post.find(:first, :include => :invalid_tags) - assert_no_queries do - post.invalid_tags - end - end - - 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) - end - end - - def test_self_referential_has_many_through - assert_equal [authors(:mary)], authors(:david).favorite_authors - assert_equal [], authors(:mary).favorite_authors - end - - def test_add_to_self_referential_has_many_through - new_author = Author.create(:name => "Bob") - authors(:david).author_favorites.create :favorite_author => new_author - assert_equal new_author, authors(:david).reload.favorite_authors.first - end - - def test_has_many_through_uses_conditions_specified_on_the_has_many_association - author = Author.find(:first) - assert !author.comments.blank? - assert author.nonexistant_comments.blank? - end - - def test_has_many_through_uses_correct_attributes - assert_nil posts(:thinking).tags.find_by_name("General").attributes["tag_id"] - end - - def test_raise_error_when_adding_new_record_to_has_many_through - assert_raise(ActiveRecord::HasManyThroughCantAssociateNewRecords) { posts(:thinking).tags << tags(:general).clone } - assert_raise(ActiveRecord::HasManyThroughCantAssociateNewRecords) { posts(:thinking).clone.tags << tags(:general) } - assert_raise(ActiveRecord::HasManyThroughCantAssociateNewRecords) { posts(:thinking).tags.build } - assert_raise(ActiveRecord::HasManyThroughCantAssociateNewRecords) { posts(:thinking).tags.new } - end - - def test_create_associate_when_adding_to_has_many_through - count = posts(:thinking).tags.count - push = Tag.create!(:name => 'pushme') - post_thinking = posts(:thinking) - assert_nothing_raised { post_thinking.tags << push } - assert_nil( wrong = post_thinking.tags.detect { |t| t.class != Tag }, - message = "Expected a Tag in tags collection, got #{wrong.class}.") - assert_nil( wrong = post_thinking.taggings.detect { |t| t.class != Tagging }, - message = "Expected a Tagging in taggings collection, got #{wrong.class}.") - assert_equal(count + 1, post_thinking.tags.size) - assert_equal(count + 1, post_thinking.tags(true).size) - - assert_kind_of Tag, post_thinking.tags.create!(:name => 'foo') - assert_nil( wrong = post_thinking.tags.detect { |t| t.class != Tag }, - message = "Expected a Tag in tags collection, got #{wrong.class}.") - assert_nil( wrong = post_thinking.taggings.detect { |t| t.class != Tagging }, - message = "Expected a Tagging in taggings collection, got #{wrong.class}.") - assert_equal(count + 2, post_thinking.tags.size) - assert_equal(count + 2, post_thinking.tags(true).size) - - assert_nothing_raised { post_thinking.tags.concat(Tag.create!(:name => 'abc'), Tag.create!(:name => 'def')) } - assert_nil( wrong = post_thinking.tags.detect { |t| t.class != Tag }, - message = "Expected a Tag in tags collection, got #{wrong.class}.") - assert_nil( wrong = post_thinking.taggings.detect { |t| t.class != Tagging }, - message = "Expected a Tagging in taggings collection, got #{wrong.class}.") - assert_equal(count + 4, post_thinking.tags.size) - assert_equal(count + 4, post_thinking.tags(true).size) - - # Raises if the wrong reflection name is used to set the Edge belongs_to - assert_nothing_raised { vertices(:vertex_1).sinks << vertices(:vertex_5) } - end - - def test_has_many_through_collection_size_doesnt_load_target_if_not_loaded - author = authors(:david) - assert_equal 9, author.comments.size - assert !author.comments.loaded? - end - - uses_mocha('has_many_through_collection_size_uses_counter_cache_if_it_exists') do - def test_has_many_through_collection_size_uses_counter_cache_if_it_exists - author = authors(:david) - author.stubs(:read_attribute).with('comments_count').returns(100) - assert_equal 100, author.comments.size - assert !author.comments.loaded? - end - end - - def test_adding_junk_to_has_many_through_should_raise_type_mismatch - assert_raise(ActiveRecord::AssociationTypeMismatch) { posts(:thinking).tags << "Uhh what now?" } - end - - def test_adding_to_has_many_through_should_return_self - tags = posts(:thinking).tags - assert_equal tags, posts(:thinking).tags.push(tags(:general)) - end - - def test_delete_associate_when_deleting_from_has_many_through_with_nonstandard_id - count = books(:awdr).references.count - references_before = books(:awdr).references - book = Book.create!(:name => 'Getting Real') - book_awdr = books(:awdr) - book_awdr.references << book - assert_equal(count + 1, book_awdr.references(true).size) - - assert_nothing_raised { book_awdr.references.delete(book) } - assert_equal(count, book_awdr.references.size) - assert_equal(count, book_awdr.references(true).size) - assert_equal(references_before.sort, book_awdr.references.sort) - end - - def test_delete_associate_when_deleting_from_has_many_through - count = posts(:thinking).tags.count - tags_before = posts(:thinking).tags - tag = Tag.create!(:name => 'doomed') - post_thinking = posts(:thinking) - post_thinking.tags << tag - assert_equal(count + 1, post_thinking.tags(true).size) - - assert_nothing_raised { post_thinking.tags.delete(tag) } - assert_equal(count, post_thinking.tags.size) - assert_equal(count, post_thinking.tags(true).size) - assert_equal(tags_before.sort, post_thinking.tags.sort) - end - - def test_delete_associate_when_deleting_from_has_many_through_with_multiple_tags - count = posts(:thinking).tags.count - tags_before = posts(:thinking).tags - doomed = Tag.create!(:name => 'doomed') - doomed2 = Tag.create!(:name => 'doomed2') - quaked = Tag.create!(:name => 'quaked') - post_thinking = posts(:thinking) - post_thinking.tags << doomed << doomed2 - assert_equal(count + 2, post_thinking.tags(true).size) - - assert_nothing_raised { post_thinking.tags.delete(doomed, doomed2, quaked) } - assert_equal(count, post_thinking.tags.size) - assert_equal(count, post_thinking.tags(true).size) - assert_equal(tags_before.sort, post_thinking.tags.sort) - end - - def test_deleting_junk_from_has_many_through_should_raise_type_mismatch - assert_raise(ActiveRecord::AssociationTypeMismatch) { posts(:thinking).tags.delete("Uhh what now?") } - end - - def test_has_many_through_sum_uses_calculations - assert_nothing_raised { authors(:david).comments.sum(:post_id) } - end - - def test_has_many_through_has_many_with_sti - assert_equal [comments(:does_it_hurt)], authors(:david).special_post_comments - end - - def test_uniq_has_many_through_should_retain_order - comment_ids = authors(:david).comments.map(&:id) - assert_equal comment_ids.sort, authors(:david).ordered_uniq_comments.map(&:id) - assert_equal comment_ids.sort.reverse, authors(:david).ordered_uniq_comments_desc.map(&:id) - end - - private - # create dynamic Post models to allow different dependency options - def find_post_with_dependency(post_id, association, association_name, dependency) - class_name = "PostWith#{association.to_s.classify}#{dependency.to_s.classify}" - Post.find(post_id).update_attribute :type, class_name - klass = Object.const_set(class_name, Class.new(ActiveRecord::Base)) - klass.set_table_name 'posts' - klass.send(association, association_name, :as => :taggable, :dependent => dependency) - klass.find(post_id) - end -end diff --git a/activerecord/test/associations_test.rb b/activerecord/test/associations_test.rb deleted file mode 100755 index 1749751852..0000000000 --- a/activerecord/test/associations_test.rb +++ /dev/null @@ -1,2177 +0,0 @@ -require 'abstract_unit' -require 'fixtures/developer' -require 'fixtures/project' -require 'fixtures/company' -require 'fixtures/topic' -require 'fixtures/reply' -require 'fixtures/computer' -require 'fixtures/customer' -require 'fixtures/order' -require 'fixtures/categorization' -require 'fixtures/category' -require 'fixtures/post' -require 'fixtures/author' -require 'fixtures/comment' -require 'fixtures/tag' -require 'fixtures/tagging' -require 'fixtures/person' -require 'fixtures/reader' - -class AssociationsTest < ActiveSupport::TestCase - fixtures :accounts, :companies, :developers, :projects, :developers_projects, - :computers - - def test_bad_collection_keys - assert_raise(ArgumentError, 'ActiveRecord should have barked on bad collection keys') do - Class.new(ActiveRecord::Base).has_many(:wheels, :name => 'wheels') - end - end - - def test_should_construct_new_finder_sql_after_create - person = Person.new - assert_equal [], person.readers.find(:all) - person.save! - reader = Reader.create! :person => person, :post => Post.new(:title => "foo", :body => "bar") - assert_equal [reader], person.readers.find(:all) - end - - def test_force_reload - firm = Firm.new("name" => "A New Firm, Inc") - firm.save - firm.clients.each {|c|} # forcing to load all clients - assert firm.clients.empty?, "New firm shouldn't have client objects" - assert_equal 0, firm.clients.size, "New firm should have 0 clients" - - client = Client.new("name" => "TheClient.com", "firm_id" => firm.id) - client.save - - assert firm.clients.empty?, "New firm should have cached no client objects" - assert_equal 0, firm.clients.size, "New firm should have cached 0 clients count" - - assert !firm.clients(true).empty?, "New firm should have reloaded client objects" - assert_equal 1, firm.clients(true).size, "New firm should have reloaded clients count" - end - - def test_storing_in_pstore - require "tmpdir" - store_filename = File.join(Dir.tmpdir, "ar-pstore-association-test") - File.delete(store_filename) if File.exist?(store_filename) - require "pstore" - apple = Firm.create("name" => "Apple") - natural = Client.new("name" => "Natural Company") - apple.clients << natural - - db = PStore.new(store_filename) - db.transaction do - db["apple"] = apple - end - - db = PStore.new(store_filename) - db.transaction do - assert_equal "Natural Company", db["apple"].clients.first.name - end - end -end - -class AssociationProxyTest < ActiveSupport::TestCase - fixtures :authors, :posts, :categorizations, :categories, :developers, :projects, :developers_projects - - def test_proxy_accessors - welcome = posts(:welcome) - assert_equal welcome, welcome.author.proxy_owner - assert_equal welcome.class.reflect_on_association(:author), welcome.author.proxy_reflection - welcome.author.class # force load target - assert_equal welcome.author, welcome.author.proxy_target - - david = authors(:david) - assert_equal david, david.posts.proxy_owner - assert_equal david.class.reflect_on_association(:posts), david.posts.proxy_reflection - david.posts.first # force load target - assert_equal david.posts, david.posts.proxy_target - - assert_equal david, david.posts_with_extension.testing_proxy_owner - assert_equal david.class.reflect_on_association(:posts_with_extension), david.posts_with_extension.testing_proxy_reflection - david.posts_with_extension.first # force load target - assert_equal david.posts_with_extension, david.posts_with_extension.testing_proxy_target - end - - def test_push_does_not_load_target - david = authors(:david) - - david.posts << (post = Post.new(:title => "New on Edge", :body => "More cool stuff!")) - assert !david.posts.loaded? - assert david.posts.include?(post) - end - - def test_push_has_many_through_does_not_load_target - david = authors(:david) - - david.categories << categories(:technology) - assert !david.categories.loaded? - assert david.categories.include?(categories(:technology)) - end - - def test_push_followed_by_save_does_not_load_target - david = authors(:david) - - david.posts << (post = Post.new(:title => "New on Edge", :body => "More cool stuff!")) - assert !david.posts.loaded? - david.save - assert !david.posts.loaded? - assert david.posts.include?(post) - end - - def test_push_does_not_lose_additions_to_new_record - josh = Author.new(:name => "Josh") - josh.posts << Post.new(:title => "New on Edge", :body => "More cool stuff!") - assert josh.posts.loaded? - assert_equal 1, josh.posts.size - end - - def test_save_on_parent_does_not_load_target - david = developers(:david) - - assert !david.projects.loaded? - david.update_attribute(:created_at, Time.now) - assert !david.projects.loaded? - end - - def test_save_on_parent_saves_children - developer = Developer.create :name => "Bryan", :salary => 50_000 - assert_equal 1, developer.reload.audit_logs.size - end - - def test_failed_reload_returns_nil - p = setup_dangling_association - assert_nil p.author.reload - end - - def test_failed_reset_returns_nil - p = setup_dangling_association - assert_nil p.author.reset - end - - def test_reload_returns_assocition - david = developers(:david) - assert_nothing_raised do - assert_equal david.projects, david.projects.reload.reload - end - end - - def setup_dangling_association - josh = Author.create(:name => "Josh") - p = Post.create(:title => "New on Edge", :body => "More cool stuff!", :author => josh) - josh.destroy - p - end -end - -class HasOneAssociationsTest < ActiveSupport::TestCase - fixtures :accounts, :companies, :developers, :projects, :developers_projects - - def setup - Account.destroyed_account_ids.clear - end - - def test_has_one - assert_equal companies(:first_firm).account, Account.find(1) - assert_equal Account.find(1).credit_limit, companies(:first_firm).account.credit_limit - end - - def test_has_one_cache_nils - firm = companies(:another_firm) - assert_queries(1) { assert_nil firm.account } - assert_queries(0) { assert_nil firm.account } - - firms = Firm.find(:all, :include => :account) - assert_queries(0) { firms.each(&:account) } - end - - def test_can_marshal_has_one_association_with_nil_target - firm = Firm.new - assert_nothing_raised do - assert_equal firm.attributes, Marshal.load(Marshal.dump(firm)).attributes - end - - firm.account - assert_nothing_raised do - assert_equal firm.attributes, Marshal.load(Marshal.dump(firm)).attributes - end - end - - def test_proxy_assignment - company = companies(:first_firm) - assert_nothing_raised { company.account = company.account } - end - - def test_triple_equality - assert Account === companies(:first_firm).account - assert companies(:first_firm).account === Account - end - - def test_type_mismatch - assert_raises(ActiveRecord::AssociationTypeMismatch) { companies(:first_firm).account = 1 } - assert_raises(ActiveRecord::AssociationTypeMismatch) { companies(:first_firm).account = Project.find(1) } - end - - def test_natural_assignment - apple = Firm.create("name" => "Apple") - citibank = Account.create("credit_limit" => 10) - apple.account = citibank - assert_equal apple.id, citibank.firm_id - end - - def test_natural_assignment_to_nil - old_account_id = companies(:first_firm).account.id - companies(:first_firm).account = nil - companies(:first_firm).save - assert_nil companies(:first_firm).account - # account is dependent, therefore is destroyed when reference to owner is lost - assert_raises(ActiveRecord::RecordNotFound) { Account.find(old_account_id) } - end - - def test_assignment_without_replacement - apple = Firm.create("name" => "Apple") - citibank = Account.create("credit_limit" => 10) - apple.account = citibank - assert_equal apple.id, citibank.firm_id - - hsbc = apple.build_account({ :credit_limit => 20}, false) - assert_equal apple.id, hsbc.firm_id - hsbc.save - assert_equal apple.id, citibank.firm_id - - nykredit = apple.create_account({ :credit_limit => 30}, false) - assert_equal apple.id, nykredit.firm_id - assert_equal apple.id, citibank.firm_id - assert_equal apple.id, hsbc.firm_id - end - - def test_assignment_without_replacement_on_create - apple = Firm.create("name" => "Apple") - citibank = Account.create("credit_limit" => 10) - apple.account = citibank - assert_equal apple.id, citibank.firm_id - - hsbc = apple.create_account({:credit_limit => 10}, false) - assert_equal apple.id, hsbc.firm_id - hsbc.save - assert_equal apple.id, citibank.firm_id - end - - def test_dependence - num_accounts = Account.count - - firm = Firm.find(1) - assert !firm.account.nil? - account_id = firm.account.id - assert_equal [], Account.destroyed_account_ids[firm.id] - - firm.destroy - assert_equal num_accounts - 1, Account.count - assert_equal [account_id], Account.destroyed_account_ids[firm.id] - end - - def test_exclusive_dependence - num_accounts = Account.count - - firm = ExclusivelyDependentFirm.find(9) - assert !firm.account.nil? - account_id = firm.account.id - assert_equal [], Account.destroyed_account_ids[firm.id] - - firm.destroy - assert_equal num_accounts - 1, Account.count - assert_equal [], Account.destroyed_account_ids[firm.id] - end - - def test_dependence_with_nil_associate - firm = DependentFirm.new(:name => 'nullify') - firm.save! - assert_nothing_raised { firm.destroy } - end - - def test_succesful_build_association - firm = Firm.new("name" => "GlobalMegaCorp") - firm.save - - account = firm.build_account("credit_limit" => 1000) - assert account.save - assert_equal account, firm.account - end - - def test_failing_build_association - firm = Firm.new("name" => "GlobalMegaCorp") - firm.save - - account = firm.build_account - assert !account.save - assert_equal "can't be empty", account.errors.on("credit_limit") - end - - def test_build_association_twice_without_saving_affects_nothing - count_of_account = Account.count - firm = Firm.find(:first) - account1 = firm.build_account("credit_limit" => 1000) - account2 = firm.build_account("credit_limit" => 2000) - - assert_equal count_of_account, Account.count - end - - def test_create_association - firm = Firm.create(:name => "GlobalMegaCorp") - account = firm.create_account(:credit_limit => 1000) - assert_equal account, firm.reload.account - end - - def test_build - firm = Firm.new("name" => "GlobalMegaCorp") - firm.save - - firm.account = account = Account.new("credit_limit" => 1000) - assert_equal account, firm.account - assert account.save - assert_equal account, firm.account - end - - def test_build_before_child_saved - firm = Firm.find(1) - - account = firm.account.build("credit_limit" => 1000) - assert_equal account, firm.account - assert account.new_record? - assert firm.save - assert_equal account, firm.account - assert !account.new_record? - end - - def test_build_before_either_saved - firm = Firm.new("name" => "GlobalMegaCorp") - - firm.account = account = Account.new("credit_limit" => 1000) - assert_equal account, firm.account - assert account.new_record? - assert firm.save - assert_equal account, firm.account - assert !account.new_record? - end - - def test_failing_build_association - firm = Firm.new("name" => "GlobalMegaCorp") - firm.save - - firm.account = account = Account.new - assert_equal account, firm.account - assert !account.save - assert_equal account, firm.account - assert_equal "can't be empty", account.errors.on("credit_limit") - end - - def test_create - firm = Firm.new("name" => "GlobalMegaCorp") - firm.save - firm.account = account = Account.create("credit_limit" => 1000) - assert_equal account, firm.account - end - - def test_create_before_save - firm = Firm.new("name" => "GlobalMegaCorp") - firm.account = account = Account.create("credit_limit" => 1000) - assert_equal account, firm.account - end - - def test_dependence_with_missing_association - Account.destroy_all - firm = Firm.find(1) - assert firm.account.nil? - firm.destroy - end - - def test_dependence_with_missing_association_and_nullify - Account.destroy_all - firm = DependentFirm.find(:first) - assert firm.account.nil? - firm.destroy - end - - def test_assignment_before_parent_saved - firm = Firm.new("name" => "GlobalMegaCorp") - firm.account = a = Account.find(1) - assert firm.new_record? - assert_equal a, firm.account - assert firm.save - assert_equal a, firm.account - assert_equal a, firm.account(true) - end - - def test_finding_with_interpolated_condition - firm = Firm.find(:first) - superior = firm.clients.create(:name => 'SuperiorCo') - superior.rating = 10 - superior.save - assert_equal 10, firm.clients_with_interpolated_conditions.first.rating - end - - def test_assignment_before_child_saved - firm = Firm.find(1) - firm.account = a = Account.new("credit_limit" => 1000) - assert !a.new_record? - assert_equal a, firm.account - assert_equal a, firm.account - assert_equal a, firm.account(true) - end - - def test_assignment_before_either_saved - firm = Firm.new("name" => "GlobalMegaCorp") - firm.account = a = Account.new("credit_limit" => 1000) - assert firm.new_record? - assert a.new_record? - assert_equal a, firm.account - assert firm.save - assert !firm.new_record? - assert !a.new_record? - assert_equal a, firm.account - assert_equal a, firm.account(true) - end - - def test_not_resaved_when_unchanged - firm = Firm.find(:first, :include => :account) - assert_queries(1) { firm.save! } - - firm = Firm.find(:first) - firm.account = Account.find(:first) - assert_queries(1) { firm.save! } - - firm = Firm.find(:first).clone - firm.account = Account.find(:first) - assert_queries(2) { firm.save! } - - firm = Firm.find(:first).clone - firm.account = Account.find(:first).clone - assert_queries(2) { firm.save! } - end - - def test_save_still_works_after_accessing_nil_has_one - jp = Company.new :name => 'Jaded Pixel' - jp.dummy_account.nil? - - assert_nothing_raised do - jp.save! - end - end - -end - - -class HasManyAssociationsTest < ActiveSupport::TestCase - fixtures :accounts, :companies, :developers, :projects, - :developers_projects, :topics, :authors, :comments - - def setup - Client.destroyed_client_ids.clear - end - - def force_signal37_to_load_all_clients_of_firm - companies(:first_firm).clients_of_firm.each {|f| } - end - - def test_counting_with_counter_sql - assert_equal 2, Firm.find(:first).clients.count - end - - def test_counting - assert_equal 2, Firm.find(:first).plain_clients.count - end - - def test_counting_with_single_conditions - assert_equal 2, Firm.find(:first).plain_clients.count(:conditions => '1=1') - end - - def test_counting_with_single_hash - assert_equal 2, Firm.find(:first).plain_clients.count(:conditions => '1=1') - end - - def test_counting_with_column_name_and_hash - assert_equal 2, Firm.find(:first).plain_clients.count(:all, :conditions => '1=1') - end - - def test_finding - assert_equal 2, Firm.find(:first).clients.length - end - - def test_find_many_with_merged_options - assert_equal 1, companies(:first_firm).limited_clients.size - assert_equal 1, companies(:first_firm).limited_clients.find(:all).size - assert_equal 2, companies(:first_firm).limited_clients.find(:all, :limit => nil).size - 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.find_by_type('Client') - end - - def test_dynamic_find_order_should_override_association_order - assert_equal companies(:first_client), companies(:first_firm).clients_sorted_desc.find(:first, :conditions => "type = 'Client'", :order => 'id') - assert_equal companies(:first_client), companies(:first_firm).clients_sorted_desc.find_by_type('Client', :order => 'id') - 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_order_should_override_association_order - assert_equal [companies(:first_client), companies(:second_client)], companies(:first_firm).clients_sorted_desc.find(:all, :conditions => "type = 'Client'", :order => 'id') - assert_equal [companies(:first_client), companies(:second_client)], companies(:first_firm).clients_sorted_desc.find_all_by_type('Client', :order => 'id') - 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_triple_equality - assert !(Array === Firm.find(:first).clients) - assert Firm.find(:first).clients === Array - end - - def test_finding_default_orders - assert_equal "Summit", Firm.find(:first).clients.first.name - end - - def test_finding_with_different_class_name_and_order - assert_equal "Microsoft", Firm.find(:first).clients_sorted_desc.first.name - end - - def test_finding_with_foreign_key - assert_equal "Microsoft", Firm.find(:first).clients_of_firm.first.name - end - - def test_finding_with_condition - assert_equal "Microsoft", Firm.find(:first).clients_like_ms.first.name - end - - def test_finding_with_condition_hash - assert_equal "Microsoft", Firm.find(:first).clients_like_ms_with_hash_conditions.first.name - end - - def test_finding_using_sql - firm = Firm.find(: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).clients_using_sql.size - end - - def test_counting_using_sql - assert_equal 1, Firm.find(:first).clients_using_counter_sql.size - assert Firm.find(:first).clients_using_counter_sql.any? - assert_equal 0, Firm.find(:first).clients_using_zero_counter_sql.size - assert !Firm.find(:first).clients_using_zero_counter_sql.any? - end - - def test_counting_non_existant_items_using_sql - assert_equal 0, Firm.find(:first).no_clients_using_counter_sql.size - end - - def test_belongs_to_sanity - c = Client.new - assert_nil c.firm - - if c.firm - assert false, "belongs_to failed if check" - end - - unless c.firm - else - assert false, "belongs_to failed unless check" - end - end - - def test_find_ids - firm = Firm.find(:first) - - assert_raises(ActiveRecord::RecordNotFound) { firm.clients.find } - - client = firm.clients.find(2) - assert_kind_of Client, client - - client_ary = firm.clients.find([2]) - assert_kind_of Array, client_ary - assert_equal client, client_ary.first - - client_ary = firm.clients.find(2, 3) - assert_kind_of Array, client_ary - assert_equal 2, client_ary.size - assert_equal client, client_ary.first - - assert_raises(ActiveRecord::RecordNotFound) { firm.clients.find(2, 99) } - end - - def test_find_string_ids_when_using_finder_sql - firm = Firm.find(:first) - - client = firm.clients_using_finder_sql.find("2") - assert_kind_of Client, client - - client_ary = firm.clients_using_finder_sql.find(["2"]) - assert_kind_of Array, client_ary - assert_equal client, client_ary.first - - client_ary = firm.clients_using_finder_sql.find("2", "3") - assert_kind_of Array, client_ary - assert_equal 2, client_ary.size - assert client_ary.include?(client) - end - - def test_find_all - firm = Firm.find(:first) - assert_equal 2, firm.clients.find(:all, :conditions => "#{QUOTED_TYPE} = 'Client'").length - assert_equal 1, firm.clients.find(:all, :conditions => "name = 'Summit'").length - end - - def test_find_all_sanitized - firm = Firm.find(:first) - 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" }]) - end - - def test_find_first - firm = Firm.find(:first) - client2 = Client.find(2) - assert_equal firm.clients.first, firm.clients.find(:first) - assert_equal client2, firm.clients.find(:first, :conditions => "#{QUOTED_TYPE} = 'Client'") - end - - def test_find_first_sanitized - firm = Firm.find(:first) - client2 = Client.find(2) - assert_equal client2, firm.clients.find(:first, :conditions => ["#{QUOTED_TYPE} = ?", 'Client']) - assert_equal client2, firm.clients.find(:first, :conditions => ["#{QUOTED_TYPE} = :type", { :type => 'Client' }]) - end - - def test_find_in_collection - assert_equal Client.find(2).name, companies(:first_firm).clients.find(2).name - assert_raises(ActiveRecord::RecordNotFound) { companies(:first_firm).clients.find(6) } - 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') - assert_equal 2, all_clients_of_firm1.size - assert_equal 1, grouped_clients_of_firm1.size - end - - def test_adding - force_signal37_to_load_all_clients_of_firm - natural = Client.new("name" => "Natural Company") - companies(:first_firm).clients_of_firm << natural - assert_equal 2, companies(:first_firm).clients_of_firm.size # checking via the collection - assert_equal 2, companies(:first_firm).clients_of_firm(true).size # checking using the db - assert_equal natural, companies(:first_firm).clients_of_firm.last - end - - def test_adding_using_create - first_firm = companies(:first_firm) - assert_equal 2, first_firm.plain_clients.size - natural = first_firm.plain_clients.create(:name => "Natural Company") - assert_equal 3, first_firm.plain_clients.length - assert_equal 3, first_firm.plain_clients.size - end - - def test_create_with_bang_on_has_many_when_parent_is_new_raises - assert_raises(ActiveRecord::RecordNotSaved) do - firm = Firm.new - firm.plain_clients.create! :name=>"Whoever" - end - end - - def test_regular_create_on_has_many_when_parent_is_new_raises - assert_raises(ActiveRecord::RecordNotSaved) do - firm = Firm.new - firm.plain_clients.create :name=>"Whoever" - end - end - - def test_create_with_bang_on_has_many_raises_when_record_not_saved - assert_raises(ActiveRecord::RecordInvalid) do - firm = Firm.find(:first) - firm.plain_clients.create! - end - end - - def test_create_with_bang_on_habtm_when_parent_is_new_raises - assert_raises(ActiveRecord::RecordNotSaved) do - Developer.new("name" => "Aredridel").projects.create! - end - end - - def test_adding_a_mismatch_class - assert_raises(ActiveRecord::AssociationTypeMismatch) { companies(:first_firm).clients_of_firm << nil } - assert_raises(ActiveRecord::AssociationTypeMismatch) { companies(:first_firm).clients_of_firm << 1 } - assert_raises(ActiveRecord::AssociationTypeMismatch) { companies(:first_firm).clients_of_firm << Topic.find(1) } - end - - def test_adding_a_collection - force_signal37_to_load_all_clients_of_firm - companies(:first_firm).clients_of_firm.concat([Client.new("name" => "Natural Company"), Client.new("name" => "Apple")]) - assert_equal 3, companies(:first_firm).clients_of_firm.size - assert_equal 3, companies(:first_firm).clients_of_firm(true).size - end - - def test_adding_before_save - no_of_firms = Firm.count - no_of_clients = Client.count - - new_firm = Firm.new("name" => "A New Firm, Inc") - c = Client.new("name" => "Apple") - - new_firm.clients_of_firm.push Client.new("name" => "Natural Company") - assert_equal 1, new_firm.clients_of_firm.size - new_firm.clients_of_firm << c - assert_equal 2, new_firm.clients_of_firm.size - - assert_equal no_of_firms, Firm.count # Firm was not saved to database. - assert_equal no_of_clients, Client.count # Clients were not saved to database. - assert new_firm.save - assert !new_firm.new_record? - assert !c.new_record? - assert_equal new_firm, c.firm - assert_equal no_of_firms+1, Firm.count # Firm was saved to database. - assert_equal no_of_clients+2, Client.count # Clients were saved to database. - - assert_equal 2, new_firm.clients_of_firm.size - assert_equal 2, new_firm.clients_of_firm(true).size - end - - def test_invalid_adding - firm = Firm.find(1) - assert !(firm.clients_of_firm << c = Client.new) - assert c.new_record? - assert !firm.valid? - assert !firm.save - assert c.new_record? - end - - def test_invalid_adding_before_save - no_of_firms = Firm.count - no_of_clients = Client.count - new_firm = Firm.new("name" => "A New Firm, Inc") - new_firm.clients_of_firm.concat([c = Client.new, Client.new("name" => "Apple")]) - assert c.new_record? - assert !c.valid? - assert !new_firm.valid? - assert !new_firm.save - assert c.new_record? - assert new_firm.new_record? - end - - def test_build - new_client = companies(:first_firm).clients_of_firm.build("name" => "Another Client") - assert_equal "Another Client", new_client.name - assert new_client.new_record? - assert_equal new_client, companies(:first_firm).clients_of_firm.last - assert companies(:first_firm).save - assert !new_client.new_record? - assert_equal 2, companies(:first_firm).clients_of_firm(true).size - end - - def test_build_many - new_clients = companies(:first_firm).clients_of_firm.build([{"name" => "Another Client"}, {"name" => "Another Client II"}]) - assert_equal 2, new_clients.size - - assert companies(:first_firm).save - assert_equal 3, companies(:first_firm).clients_of_firm(true).size - end - - def test_build_followed_by_save_does_not_load_target - new_client = companies(:first_firm).clients_of_firm.build("name" => "Another Client") - assert companies(:first_firm).save - assert !companies(:first_firm).clients_of_firm.loaded? - end - - def test_build_without_loading_association - first_topic = topics(:first) - Reply.column_names - - assert_equal 1, first_topic.replies.length - - assert_no_queries do - first_topic.replies.build(:title => "Not saved", :content => "Superstars") - assert_equal 2, first_topic.replies.size - end - - assert_equal 2, first_topic.replies.to_ary.size - end - - def test_create_without_loading_association - first_firm = companies(:first_firm) - Firm.column_names - Client.column_names - - assert_equal 1, first_firm.clients_of_firm.size - first_firm.clients_of_firm.reset - - assert_queries(1) do - first_firm.clients_of_firm.create(:name => "Superstars") - end - - assert_equal 2, first_firm.clients_of_firm.size - end - - def test_invalid_build - new_client = companies(:first_firm).clients_of_firm.build - assert new_client.new_record? - assert !new_client.valid? - assert_equal new_client, companies(:first_firm).clients_of_firm.last - assert !companies(:first_firm).save - assert new_client.new_record? - assert_equal 1, companies(:first_firm).clients_of_firm(true).size - end - - def test_create - force_signal37_to_load_all_clients_of_firm - new_client = companies(:first_firm).clients_of_firm.create("name" => "Another Client") - assert !new_client.new_record? - assert_equal new_client, companies(:first_firm).clients_of_firm.last - assert_equal new_client, companies(:first_firm).clients_of_firm(true).last - end - - def test_create_many - companies(:first_firm).clients_of_firm.create([{"name" => "Another Client"}, {"name" => "Another Client II"}]) - assert_equal 3, companies(:first_firm).clients_of_firm(true).size - end - - def test_create_followed_by_save_does_not_load_target - new_client = companies(:first_firm).clients_of_firm.create("name" => "Another Client") - assert companies(:first_firm).save - 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.new_record? - end - - def test_find_or_create - 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_deleting - force_signal37_to_load_all_clients_of_firm - companies(:first_firm).clients_of_firm.delete(companies(:first_firm).clients_of_firm.first) - assert_equal 0, companies(:first_firm).clients_of_firm.size - assert_equal 0, companies(:first_firm).clients_of_firm(true).size - end - - def test_deleting_before_save - new_firm = Firm.new("name" => "A New Firm, Inc.") - new_client = new_firm.clients_of_firm.build("name" => "Another Client") - assert_equal 1, new_firm.clients_of_firm.size - new_firm.clients_of_firm.delete(new_client) - assert_equal 0, new_firm.clients_of_firm.size - end - - def test_deleting_a_collection - force_signal37_to_load_all_clients_of_firm - companies(:first_firm).clients_of_firm.create("name" => "Another Client") - assert_equal 2, companies(:first_firm).clients_of_firm.size - companies(:first_firm).clients_of_firm.delete([companies(:first_firm).clients_of_firm[0], companies(:first_firm).clients_of_firm[1]]) - assert_equal 0, companies(:first_firm).clients_of_firm.size - assert_equal 0, companies(:first_firm).clients_of_firm(true).size - end - - def test_delete_all - force_signal37_to_load_all_clients_of_firm - companies(:first_firm).clients_of_firm.create("name" => "Another Client") - assert_equal 2, companies(:first_firm).clients_of_firm.size - companies(:first_firm).clients_of_firm.delete_all - assert_equal 0, companies(:first_firm).clients_of_firm.size - assert_equal 0, companies(:first_firm).clients_of_firm(true).size - end - - def test_delete_all_with_not_yet_loaded_association_collection - force_signal37_to_load_all_clients_of_firm - companies(:first_firm).clients_of_firm.create("name" => "Another Client") - assert_equal 2, companies(:first_firm).clients_of_firm.size - companies(:first_firm).clients_of_firm.reset - companies(:first_firm).clients_of_firm.delete_all - assert_equal 0, companies(:first_firm).clients_of_firm.size - assert_equal 0, companies(:first_firm).clients_of_firm(true).size - end - - def test_clearing_an_association_collection - firm = companies(:first_firm) - client_id = firm.clients_of_firm.first.id - assert_equal 1, firm.clients_of_firm.size - - 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] - - # Should not be destroyed since the association is not dependent. - assert_nothing_raised do - assert Client.find(client_id).firm.nil? - end - end - - def test_clearing_a_dependent_association_collection - firm = companies(:first_firm) - client_id = firm.dependent_clients_of_firm.first.id - assert_equal 1, firm.dependent_clients_of_firm.size - - # :dependent means destroy is called on each client - firm.dependent_clients_of_firm.clear - - assert_equal 0, firm.dependent_clients_of_firm.size - assert_equal 0, firm.dependent_clients_of_firm(true).size - assert_equal [client_id], Client.destroyed_client_ids[firm.id] - - # Should be destroyed since the association is dependent. - assert Client.find_by_id(client_id).nil? - end - - def test_clearing_an_exclusively_dependent_association_collection - firm = companies(:first_firm) - client_id = firm.exclusively_dependent_clients_of_firm.first.id - assert_equal 1, firm.exclusively_dependent_clients_of_firm.size - - assert_equal [], Client.destroyed_client_ids[firm.id] - - # :exclusively_dependent means each client is deleted directly from - # the database without looping through them calling destroy. - firm.exclusively_dependent_clients_of_firm.clear - - assert_equal 0, firm.exclusively_dependent_clients_of_firm.size - assert_equal 0, firm.exclusively_dependent_clients_of_firm(true).size - # no destroy-filters should have been called - assert_equal [], Client.destroyed_client_ids[firm.id] - - # Should be destroyed since the association is exclusively dependent. - assert Client.find_by_id(client_id).nil? - end - - def test_dependent_association_respects_optional_conditions_on_delete - firm = companies(:odegy) - 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 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 - end - - def test_dependent_association_respects_optional_sanitized_conditions_on_delete - firm = companies(:odegy) - 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 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 - end - - def test_clearing_without_initial_access - firm = companies(:first_firm) - - firm.clients_of_firm.clear - - assert_equal 0, firm.clients_of_firm.size - assert_equal 0, firm.clients_of_firm(true).size - end - - def test_deleting_a_item_which_is_not_in_the_collection - force_signal37_to_load_all_clients_of_firm - summit = Client.find_by_name('Summit') - companies(:first_firm).clients_of_firm.delete(summit) - assert_equal 1, companies(:first_firm).clients_of_firm.size - assert_equal 1, companies(:first_firm).clients_of_firm(true).size - assert_equal 2, summit.client_of - end - - def test_deleting_type_mismatch - david = Developer.find(1) - david.projects.reload - assert_raises(ActiveRecord::AssociationTypeMismatch) { david.projects.delete(1) } - end - - def test_deleting_self_type_mismatch - david = Developer.find(1) - david.projects.reload - assert_raises(ActiveRecord::AssociationTypeMismatch) { david.projects.delete(Project.find(1).developers) } - end - - def test_destroy_all - force_signal37_to_load_all_clients_of_firm - assert !companies(:first_firm).clients_of_firm.empty?, "37signals has clients after load" - companies(:first_firm).clients_of_firm.destroy_all - assert companies(:first_firm).clients_of_firm.empty?, "37signals has no clients after destroy all" - assert companies(:first_firm).clients_of_firm(true).empty?, "37signals has no clients after destroy all and refresh" - end - - def test_dependence - firm = companies(:first_firm) - assert_equal 2, firm.clients.size - firm.destroy - assert Client.find(:all, :conditions => "firm_id=#{firm.id}").empty? - end - - def test_destroy_dependent_when_deleted_from_association - firm = Firm.find(:first) - assert_equal 2, firm.clients.size - - client = firm.clients.first - firm.clients.delete(client) - - assert_raise(ActiveRecord::RecordNotFound) { Client.find(client.id) } - assert_raise(ActiveRecord::RecordNotFound) { firm.clients.find(client.id) } - assert_equal 1, firm.clients.size - end - - def test_three_levels_of_dependence - topic = Topic.create "title" => "neat and simple" - reply = topic.replies.create "title" => "neat and simple", "content" => "still digging it" - silly_reply = reply.replies.create "title" => "neat and simple", "content" => "ain't complaining" - - assert_nothing_raised { topic.destroy } - end - - uses_transaction :test_dependence_with_transaction_support_on_failure - def test_dependence_with_transaction_support_on_failure - firm = companies(:first_firm) - clients = firm.clients - assert_equal 2, clients.length - clients.last.instance_eval { def before_destroy() raise "Trigger rollback" end } - - firm.destroy rescue "do nothing" - - assert_equal 2, Client.find(:all, :conditions => "firm_id=#{firm.id}").size - end - - def test_dependence_on_account - num_accounts = Account.count - companies(:first_firm).destroy - assert_equal num_accounts - 1, Account.count - end - - def test_depends_and_nullify - num_accounts = Account.count - num_companies = Company.count - - core = companies(:rails_core) - assert_equal accounts(:rails_core_account), core.account - assert_equal companies(:leetsoft, :jadedpixel), core.companies - core.destroy - assert_nil accounts(:rails_core_account).reload.firm_id - assert_nil companies(:leetsoft).reload.client_of - assert_nil companies(:jadedpixel).reload.client_of - - - assert_equal num_accounts, Account.count - end - - def test_included_in_collection - assert companies(:first_firm).clients.include?(Client.find(2)) - 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 - end - - def test_replace_with_less - firm = Firm.find(:first) - firm.clients = [companies(:first_client)] - assert firm.save, "Could not save firm" - firm.reload - assert_equal 1, firm.clients.length - end - - def test_replace_with_less_and_dependent_nullify - num_companies = Company.count - companies(:rails_core).companies = [] - assert_equal num_companies, Company.count - end - - def test_replace_with_new - firm = Firm.find(:first) - firm.clients = [companies(:second_client), Client.new("name" => "New Client")] - firm.save - firm.reload - assert_equal 2, firm.clients.length - assert !firm.clients.include?(:first_client) - end - - def test_replace_on_new_object - firm = Firm.new("name" => "New Firm") - firm.clients = [companies(:second_client), Client.new("name" => "New Client")] - assert firm.save - firm.reload - assert_equal 2, firm.clients.length - assert firm.clients.include?(Client.find_by_name("New Client")) - end - - def test_get_ids - assert_equal [companies(:first_client).id, companies(:second_client).id], companies(:first_firm).client_ids - end - - def test_assign_ids - firm = Firm.new("name" => "Apple") - firm.client_ids = [companies(:first_client).id, companies(:second_client).id] - firm.save - firm.reload - assert_equal 2, firm.clients.length - assert firm.clients.include?(companies(:second_client)) - end - - def test_assign_ids_ignoring_blanks - firm = Firm.create!(:name => 'Apple') - firm.client_ids = [companies(:first_client).id, nil, companies(:second_client).id, ''] - firm.save! - - assert_equal 2, firm.clients(true).size - assert firm.clients.include?(companies(:second_client)) - end - - def test_get_ids_for_through - assert_equal [comments(:eager_other_comment1).id], authors(:mary).comment_ids - end - - def test_assign_ids_for_through - assert_raise(NoMethodError) { authors(:mary).comment_ids = [123] } - 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.find_by_type('SpecialComment') - end - - def test_dynamic_find_order_should_override_association_order_for_through - assert_equal Comment.find(3), authors(:david).comments_desc.find(:first, :conditions => "comments.type = 'SpecialComment'", :order => 'comments.id') - assert_equal Comment.find(3), authors(:david).comments_desc.find_by_type('SpecialComment', :order => 'comments.id') - 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_order_should_override_association_order_for_through - assert_equal [Comment.find(3), Comment.find(6), Comment.find(7), Comment.find(10)], authors(:david).comments_desc.find(:all, :conditions => "comments.type = 'SpecialComment'", :order => 'comments.id') - assert_equal [Comment.find(3), Comment.find(6), Comment.find(7), Comment.find(10)], authors(:david).comments_desc.find_all_by_type('SpecialComment', :order => 'comments.id') - 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 - -end - -class BelongsToAssociationsTest < ActiveSupport::TestCase - fixtures :accounts, :companies, :developers, :projects, :topics, - :developers_projects, :computers, :authors, :posts, :tags, :taggings - - def test_belongs_to - Client.find(3).firm.name - assert_equal companies(:first_firm).name, Client.find(3).firm.name - assert !Client.find(3).firm.nil?, "Microsoft should have a firm" - end - - def test_proxy_assignment - account = Account.find(1) - assert_nothing_raised { account.firm = account.firm } - end - - def test_triple_equality - assert Client.find(3).firm === Firm - assert Firm === Client.find(3).firm - end - - def test_type_mismatch - assert_raise(ActiveRecord::AssociationTypeMismatch) { Account.find(1).firm = 1 } - assert_raise(ActiveRecord::AssociationTypeMismatch) { Account.find(1).firm = Project.find(1) } - end - - def test_natural_assignment - apple = Firm.create("name" => "Apple") - citibank = Account.create("credit_limit" => 10) - citibank.firm = apple - assert_equal apple.id, citibank.firm_id - end - - def test_no_unexpected_aliasing - first_firm = companies(:first_firm) - another_firm = companies(:another_firm) - - citibank = Account.create("credit_limit" => 10) - citibank.firm = first_firm - original_proxy = citibank.firm - citibank.firm = another_firm - - assert_equal first_firm.object_id, original_proxy.object_id - assert_equal another_firm.object_id, citibank.firm.object_id - end - - def test_creating_the_belonging_object - citibank = Account.create("credit_limit" => 10) - apple = citibank.create_firm("name" => "Apple") - assert_equal apple, citibank.firm - citibank.save - citibank.reload - assert_equal apple, citibank.firm - end - - def test_building_the_belonging_object - citibank = Account.create("credit_limit" => 10) - apple = citibank.build_firm("name" => "Apple") - citibank.save - assert_equal apple.id, citibank.firm_id - end - - def test_natural_assignment_to_nil - client = Client.find(3) - client.firm = nil - client.save - assert_nil client.firm(true) - assert_nil client.client_of - end - - def test_with_different_class_name - assert_equal Company.find(1).name, Company.find(3).firm_with_other_name.name - assert_not_nil Company.find(3).firm_with_other_name, "Microsoft should have a firm" - end - - def test_with_condition - assert_equal Company.find(1).name, Company.find(3).firm_with_condition.name - assert_not_nil Company.find(3).firm_with_condition, "Microsoft should have a firm" - end - - def test_belongs_to_counter - debate = Topic.create("title" => "debate") - assert_equal 0, debate.send(:read_attribute, "replies_count"), "No replies yet" - - trash = debate.replies.create("title" => "blah!", "content" => "world around!") - assert_equal 1, Topic.find(debate.id).send(:read_attribute, "replies_count"), "First reply created" - - trash.destroy - assert_equal 0, Topic.find(debate.id).send(:read_attribute, "replies_count"), "First reply deleted" - end - - def test_belongs_to_counter_with_reassigning - t1 = Topic.create("title" => "t1") - t2 = Topic.create("title" => "t2") - r1 = Reply.new("title" => "r1", "content" => "r1") - r1.topic = t1 - - assert r1.save - assert_equal 1, Topic.find(t1.id).replies.size - assert_equal 0, Topic.find(t2.id).replies.size - - r1.topic = Topic.find(t2.id) - - assert r1.save - assert_equal 0, Topic.find(t1.id).replies.size - assert_equal 1, Topic.find(t2.id).replies.size - - r1.topic = nil - - assert_equal 0, Topic.find(t1.id).replies.size - assert_equal 0, Topic.find(t2.id).replies.size - - r1.topic = t1 - - assert_equal 1, Topic.find(t1.id).replies.size - assert_equal 0, Topic.find(t2.id).replies.size - - r1.destroy - - assert_equal 0, Topic.find(t1.id).replies.size - assert_equal 0, Topic.find(t2.id).replies.size - end - - def test_belongs_to_counter_after_save - topic = Topic.create!(:title => "monday night") - topic.replies.create!(:title => "re: monday night", :content => "football") - assert_equal 1, Topic.find(topic.id)[:replies_count] - - topic.save! - assert_equal 1, Topic.find(topic.id)[:replies_count] - end - - def test_belongs_to_counter_after_update_attributes - topic = Topic.create!(:title => "37s") - topic.replies.create!(:title => "re: 37s", :content => "rails") - assert_equal 1, Topic.find(topic.id)[:replies_count] - - topic.update_attributes(:title => "37signals") - assert_equal 1, Topic.find(topic.id)[:replies_count] - end - - def test_belongs_to_counter_after_save - topic = Topic.create("title" => "monday night") - topic.replies.create("title" => "re: monday night", "content" => "football") - assert_equal 1, Topic.find(topic.id).send(:read_attribute, "replies_count") - - topic.save - assert_equal 1, Topic.find(topic.id).send(:read_attribute, "replies_count") - end - - def test_belongs_to_counter_after_update_attributes - topic = Topic.create("title" => "37s") - topic.replies.create("title" => "re: 37s", "content" => "rails") - assert_equal 1, Topic.find(topic.id).send(:read_attribute, "replies_count") - - topic.update_attributes("title" => "37signals") - assert_equal 1, Topic.find(topic.id).send(:read_attribute, "replies_count") - end - - def test_assignment_before_parent_saved - client = Client.find(:first) - apple = Firm.new("name" => "Apple") - client.firm = apple - assert_equal apple, client.firm - assert apple.new_record? - assert client.save - assert apple.save - assert !apple.new_record? - assert_equal apple, client.firm - assert_equal apple, client.firm(true) - end - - def test_assignment_before_child_saved - final_cut = Client.new("name" => "Final Cut") - firm = Firm.find(1) - final_cut.firm = firm - assert final_cut.new_record? - assert final_cut.save - assert !final_cut.new_record? - assert !firm.new_record? - assert_equal firm, final_cut.firm - assert_equal firm, final_cut.firm(true) - end - - def test_assignment_before_either_saved - final_cut = Client.new("name" => "Final Cut") - apple = Firm.new("name" => "Apple") - final_cut.firm = apple - assert final_cut.new_record? - assert apple.new_record? - assert final_cut.save - assert !final_cut.new_record? - assert !apple.new_record? - assert_equal apple, final_cut.firm - assert_equal apple, final_cut.firm(true) - end - - def test_new_record_with_foreign_key_but_no_object - c = Client.new("firm_id" => 1) - assert_equal Firm.find(:first), c.firm_with_basic_id - end - - def test_forgetting_the_load_when_foreign_key_enters_late - c = Client.new - assert_nil c.firm_with_basic_id - - c.firm_id = 1 - assert_equal Firm.find(:first), c.firm_with_basic_id - 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" # ' - end - - def test_counter_cache - topic = Topic.create :title => "Zoom-zoom-zoom" - assert_equal 0, topic[:replies_count] - - reply = Reply.create(:title => "re: zoom", :content => "speedy quick!") - reply.topic = topic - - assert_equal 1, topic.reload[:replies_count] - assert_equal 1, topic.replies.size - - topic[:replies_count] = 15 - assert_equal 15, topic.replies.size - end - - def test_custom_counter_cache - reply = Reply.create(:title => "re: zoom", :content => "speedy quick!") - assert_equal 0, reply[:replies_count] - - silly = SillyReply.create(:title => "gaga", :content => "boo-boo") - silly.reply = reply - - assert_equal 1, reply.reload[:replies_count] - assert_equal 1, reply.replies.size - - reply[:replies_count] = 17 - assert_equal 17, reply.replies.size - end - - def test_store_two_association_with_one_save - num_orders = Order.count - num_customers = Customer.count - order = Order.new - - customer1 = order.billing = Customer.new - customer2 = order.shipping = Customer.new - assert order.save - assert_equal customer1, order.billing - assert_equal customer2, order.shipping - - order.reload - - assert_equal customer1, order.billing - assert_equal customer2, order.shipping - - assert_equal num_orders +1, Order.count - assert_equal num_customers +2, Customer.count - end - - - def test_store_association_in_two_relations_with_one_save - num_orders = Order.count - num_customers = Customer.count - order = Order.new - - customer = order.billing = order.shipping = Customer.new - assert order.save - assert_equal customer, order.billing - assert_equal customer, order.shipping - - order.reload - - assert_equal customer, order.billing - assert_equal customer, order.shipping - - assert_equal num_orders +1, Order.count - assert_equal num_customers +1, Customer.count - end - - def test_store_association_in_two_relations_with_one_save_in_existing_object - num_orders = Order.count - num_customers = Customer.count - order = Order.create - - customer = order.billing = order.shipping = Customer.new - assert order.save - assert_equal customer, order.billing - assert_equal customer, order.shipping - - order.reload - - assert_equal customer, order.billing - assert_equal customer, order.shipping - - assert_equal num_orders +1, Order.count - assert_equal num_customers +1, Customer.count - end - - def test_store_association_in_two_relations_with_one_save_in_existing_object_with_values - num_orders = Order.count - num_customers = Customer.count - order = Order.create - - customer = order.billing = order.shipping = Customer.new - assert order.save - assert_equal customer, order.billing - assert_equal customer, order.shipping - - order.reload - - customer = order.billing = order.shipping = Customer.new - - assert order.save - order.reload - - assert_equal customer, order.billing - assert_equal customer, order.shipping - - assert_equal num_orders +1, Order.count - assert_equal num_customers +2, Customer.count - end - - - def test_association_assignment_sticks - post = Post.find(:first) - - author1, author2 = Author.find(:all, :limit => 2) - assert_not_nil author1 - assert_not_nil author2 - - # make sure the association is loaded - post.author - - # set the association by id, directly - post.author_id = author2.id - - # save and reload - post.save! - post.reload - - # the author id of the post should be the id we set - assert_equal post.author_id, author2.id - end - -end - - -class ProjectWithAfterCreateHook < ActiveRecord::Base - set_table_name 'projects' - has_and_belongs_to_many :developers, - :class_name => "DeveloperForProjectWithAfterCreateHook", - :join_table => "developers_projects", - :foreign_key => "project_id", - :association_foreign_key => "developer_id" - - after_create :add_david - - def add_david - david = DeveloperForProjectWithAfterCreateHook.find_by_name('David') - david.projects << self - end -end - -class DeveloperForProjectWithAfterCreateHook < ActiveRecord::Base - set_table_name 'developers' - has_and_belongs_to_many :projects, - :class_name => "ProjectWithAfterCreateHook", - :join_table => "developers_projects", - :association_foreign_key => "project_id", - :foreign_key => "developer_id" -end - - -class HasAndBelongsToManyAssociationsTest < ActiveSupport::TestCase - fixtures :accounts, :companies, :categories, :posts, :categories_posts, :developers, :projects, :developers_projects - - def test_has_and_belongs_to_many - david = Developer.find(1) - - assert !david.projects.empty? - assert_equal 2, david.projects.size - - active_record = Project.find(1) - assert !active_record.developers.empty? - assert_equal 3, active_record.developers.size - 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 - action_controller = Project.find(2) - assert_equal 1, jamis.projects.size - assert_equal 1, action_controller.developers.size - - jamis.projects << action_controller - - assert_equal 2, jamis.projects.size - assert_equal 2, jamis.projects(true).size - assert_equal 2, action_controller.developers(true).size - end - - def test_adding_type_mismatch - jamis = Developer.find(2) - assert_raise(ActiveRecord::AssociationTypeMismatch) { jamis.projects << nil } - assert_raise(ActiveRecord::AssociationTypeMismatch) { jamis.projects << 1 } - end - - def test_adding_from_the_project - jamis = Developer.find(2) - action_controller = Project.find(2) - action_controller.developers.reload - assert_equal 1, jamis.projects.size - assert_equal 1, action_controller.developers.size - - action_controller.developers << jamis - - assert_equal 2, jamis.projects(true).size - assert_equal 2, action_controller.developers.size - assert_equal 2, action_controller.developers(true).size - end - - def test_adding_from_the_project_fixed_timestamp - jamis = Developer.find(2) - action_controller = Project.find(2) - action_controller.developers.reload - assert_equal 1, jamis.projects.size - assert_equal 1, action_controller.developers.size - updated_at = jamis.updated_at - - action_controller.developers << jamis - - assert_equal updated_at, jamis.updated_at - assert_equal 2, jamis.projects(true).size - assert_equal 2, action_controller.developers.size - assert_equal 2, action_controller.developers(true).size - end - - def test_adding_multiple - aredridel = Developer.new("name" => "Aredridel") - aredridel.save - aredridel.projects.reload - aredridel.projects.push(Project.find(1), Project.find(2)) - assert_equal 2, aredridel.projects.size - assert_equal 2, aredridel.projects(true).size - end - - def test_adding_a_collection - aredridel = Developer.new("name" => "Aredridel") - aredridel.save - aredridel.projects.reload - aredridel.projects.concat([Project.find(1), Project.find(2)]) - assert_equal 2, aredridel.projects.size - assert_equal 2, aredridel.projects(true).size - end - - def test_adding_uses_default_values_on_join_table - ac = projects(:action_controller) - assert !developers(:jamis).projects.include?(ac) - developers(:jamis).projects << ac - - assert developers(:jamis, :reload).projects.include?(ac) - project = developers(:jamis).projects.detect { |p| p == ac } - assert_equal 1, project.access_level.to_i - end - - def test_habtm_attribute_access_and_respond_to - project = developers(:jamis).projects[0] - assert project.has_attribute?("name") - assert project.has_attribute?("joined_on") - assert project.has_attribute?("access_level") - assert project.respond_to?("name") - assert project.respond_to?("name=") - assert project.respond_to?("name?") - assert project.respond_to?("joined_on") - # given that the 'join attribute' won't be persisted, I don't - # think we should define the mutators - #assert project.respond_to?("joined_on=") - assert project.respond_to?("joined_on?") - assert project.respond_to?("access_level") - #assert project.respond_to?("access_level=") - assert project.respond_to?("access_level?") - end - - def test_habtm_adding_before_save - no_of_devels = Developer.count - no_of_projects = Project.count - aredridel = Developer.new("name" => "Aredridel") - aredridel.projects.concat([Project.find(1), p = Project.new("name" => "Projekt")]) - assert aredridel.new_record? - assert p.new_record? - assert aredridel.save - assert !aredridel.new_record? - assert_equal no_of_devels+1, Developer.count - assert_equal no_of_projects+1, Project.count - assert_equal 2, aredridel.projects.size - assert_equal 2, aredridel.projects(true).size - end - - def test_habtm_saving_multiple_relationships - new_project = Project.new("name" => "Grimetime") - amount_of_developers = 4 - developers = (0...amount_of_developers).collect {|i| Developer.create(:name => "JME #{i}") }.reverse - - new_project.developer_ids = [developers[0].id, developers[1].id] - new_project.developers_with_callback_ids = [developers[2].id, developers[3].id] - assert new_project.save - - new_project.reload - assert_equal amount_of_developers, new_project.developers.size - assert_equal developers, new_project.developers - end - - def test_habtm_unique_order_preserved - assert_equal developers(:poor_jamis, :jamis, :david), projects(:active_record).non_unique_developers - assert_equal developers(:poor_jamis, :jamis, :david), projects(:active_record).developers - end - - def test_build - devel = Developer.find(1) - proj = devel.projects.build("name" => "Projekt") - assert_equal devel.projects.last, proj - assert proj.new_record? - devel.save - assert !proj.new_record? - assert_equal devel.projects.last, proj - assert_equal Developer.find(1).projects.sort_by(&:id).last, proj # prove join table is updated - end - - def test_build_by_new_record - devel = Developer.new(:name => "Marcel", :salary => 75000) - proj1 = devel.projects.build(:name => "Make bed") - proj2 = devel.projects.build(:name => "Lie in it") - assert_equal devel.projects.last, proj2 - assert proj2.new_record? - devel.save - assert !devel.new_record? - assert !proj2.new_record? - assert_equal devel.projects.last, proj2 - assert_equal Developer.find_by_name("Marcel").projects.last, proj2 # prove join table is updated - end - - def test_create - devel = Developer.find(1) - proj = devel.projects.create("name" => "Projekt") - assert_equal devel.projects.last, proj - assert !proj.new_record? - assert_equal Developer.find(1).projects.sort_by(&:id).last, proj # prove join table is updated - end - - def test_create_by_new_record - devel = Developer.new(:name => "Marcel", :salary => 75000) - proj1 = devel.projects.build(:name => "Make bed") - proj2 = devel.projects.build(:name => "Lie in it") - assert_equal devel.projects.last, proj2 - assert proj2.new_record? - devel.save - assert !devel.new_record? - assert !proj2.new_record? - assert_equal devel.projects.last, proj2 - assert_equal Developer.find_by_name("Marcel").projects.last, proj2 # prove join table is updated - end - - def test_uniq_after_the_fact - developers(:jamis).projects << projects(:active_record) - developers(:jamis).projects << projects(:active_record) - assert_equal 3, developers(:jamis).projects.size - assert_equal 1, developers(:jamis).projects.uniq.size - end - - def test_uniq_before_the_fact - projects(:active_record).developers << developers(:jamis) - projects(:active_record).developers << developers(:david) - assert_equal 3, projects(:active_record, :reload).developers.size - end - - def test_deleting - david = Developer.find(1) - active_record = Project.find(1) - david.projects.reload - assert_equal 2, david.projects.size - assert_equal 3, active_record.developers.size - - david.projects.delete(active_record) - - assert_equal 1, david.projects.size - assert_equal 1, david.projects(true).size - assert_equal 2, active_record.developers(true).size - end - - def test_deleting_array - david = Developer.find(1) - david.projects.reload - david.projects.delete(Project.find(:all)) - assert_equal 0, david.projects.size - assert_equal 0, david.projects(true).size - end - - def test_deleting_with_sql - david = Developer.find(1) - active_record = Project.find(1) - active_record.developers.reload - assert_equal 3, active_record.developers_by_sql.size - - active_record.developers_by_sql.delete(david) - assert_equal 2, active_record.developers_by_sql(true).size - end - - def test_deleting_array_with_sql - active_record = Project.find(1) - active_record.developers.reload - assert_equal 3, active_record.developers_by_sql.size - - active_record.developers_by_sql.delete(Developer.find(:all)) - assert_equal 0, active_record.developers_by_sql(true).size - end - - def test_deleting_all - david = Developer.find(1) - david.projects.reload - david.projects.clear - assert_equal 0, david.projects.size - assert_equal 0, david.projects(true).size - end - - def test_removing_associations_on_destroy - david = DeveloperWithBeforeDestroyRaise.find(1) - assert !david.projects.empty? - assert_nothing_raised { david.destroy } - assert david.projects.empty? - assert DeveloperWithBeforeDestroyRaise.connection.select_all("SELECT * FROM developers_projects WHERE developer_id = 1").empty? - end - - def test_additional_columns_from_join_table - assert_date_from_db Date.new(2004, 10, 10), Developer.find(1).projects.first.joined_on.to_date - end - - def test_destroy_all - david = Developer.find(1) - david.projects.reload - assert !david.projects.empty? - david.projects.destroy_all - assert david.projects.empty? - assert david.projects(true).empty? - end - - def test_deprecated_push_with_attributes_was_removed - jamis = developers(:jamis) - assert_raise(NoMethodError) do - jamis.projects.push_with_attributes(projects(:action_controller), :joined_on => Date.today) - end - end - - def test_associations_with_conditions - assert_equal 3, projects(:active_record).developers.size - assert_equal 1, projects(:active_record).developers_named_david.size - assert_equal 1, projects(:active_record).developers_named_david_with_hash_conditions.size - - assert_equal developers(:david), projects(:active_record).developers_named_david.find(developers(:david).id) - assert_equal developers(:david), projects(:active_record).developers_named_david_with_hash_conditions.find(developers(:david).id) - assert_equal developers(:david), projects(:active_record).salaried_developers.find(developers(:david).id) - - projects(:active_record).developers_named_david.clear - assert_equal 2, projects(:active_record, :reload).developers.size - end - - def test_find_in_association - # Using sql - assert_equal developers(:david), projects(:active_record).developers.find(developers(:david).id), "SQL find" - - # Using ruby - active_record = projects(:active_record) - active_record.developers.reload - assert_equal developers(:david), active_record.developers.find(developers(:david).id), "Ruby find" - end - - def test_find_in_association_with_custom_finder_sql - assert_equal developers(:david), projects(:active_record).developers_with_finder_sql.find(developers(:david).id), "SQL find" - - active_record = projects(:active_record) - active_record.developers_with_finder_sql.reload - assert_equal developers(:david), active_record.developers_with_finder_sql.find(developers(:david).id), "Ruby find" - end - - def test_find_in_association_with_custom_finder_sql_and_string_id - assert_equal developers(:david), projects(:active_record).developers_with_finder_sql.find(developers(:david).id.to_s), "SQL find" - end - - 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 - end - - def test_dynamic_find_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, projects(:active_record).developers.find(:first, :conditions => "name = 'Jamis'") - assert_equal high_id_jamis, projects(:active_record).developers.find_by_name('Jamis') - end - - def test_dynamic_find_order_should_override_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 low_id_jamis, projects(:active_record).developers.find(:first, :conditions => "name = 'Jamis'", :order => 'id') - assert_equal low_id_jamis, projects(:active_record).developers.find_by_name('Jamis', :order => 'id') - 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_dynamic_find_all_order_should_override_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 [low_id_jamis, middle_id_jamis, high_id_jamis], projects(:active_record).developers.find(:all, :conditions => "name = 'Jamis'", :order => 'id') - assert_equal [low_id_jamis, middle_id_jamis, high_id_jamis], projects(:active_record).developers.find_all_by_name('Jamis', :order => 'id') - 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_new_with_values_in_collection - jamis = DeveloperForProjectWithAfterCreateHook.find_by_name('Jamis') - david = DeveloperForProjectWithAfterCreateHook.find_by_name('David') - project = ProjectWithAfterCreateHook.new(:name => "Cooking with Bertie") - project.developers << jamis - project.save! - project.reload - - assert project.developers.include?(jamis) - assert project.developers.include?(david) - end - - def test_find_in_association_with_options - developers = projects(:active_record).developers.find(:all) - assert_equal 3, developers.size - - assert_equal developers(:poor_jamis), projects(:active_record).developers.find(:first, :conditions => "salary < 10000") - assert_equal developers(:jamis), projects(:active_record).developers.find(:first, :order => "salary DESC") - end - - def test_replace_with_less - david = developers(:david) - david.projects = [projects(:action_controller)] - assert david.save - assert_equal 1, david.projects.length - end - - def test_replace_with_new - david = developers(:david) - david.projects = [projects(:action_controller), Project.new("name" => "ActionWebSearch")] - david.save - assert_equal 2, david.projects.length - assert !david.projects.include?(projects(:active_record)) - end - - def test_replace_on_new_object - new_developer = Developer.new("name" => "Matz") - new_developer.projects = [projects(:action_controller), Project.new("name" => "ActionWebSearch")] - new_developer.save - assert_equal 2, new_developer.projects.length - end - - def test_consider_type - developer = Developer.find(:first) - special_project = SpecialProject.create("name" => "Special Project") - - other_project = developer.projects.first - developer.special_projects << special_project - developer.reload - - assert developer.projects.include?(special_project) - assert developer.special_projects.include?(special_project) - assert !developer.special_projects.include?(other_project) - end - - def test_update_attributes_after_push_without_duplicate_join_table_rows - developer = Developer.new("name" => "Kano") - project = SpecialProject.create("name" => "Special Project") - assert developer.save - developer.projects << project - developer.update_attribute("name", "Bruza") - assert_equal 1, Developer.connection.select_value(<<-end_sql).to_i - SELECT count(*) FROM developers_projects - WHERE project_id = #{project.id} - AND developer_id = #{developer.id} - end_sql - end - - def test_updating_attributes_on_non_rich_associations - welcome = categories(:technology).posts.first - welcome.title = "Something else" - assert welcome.save! - end - - def test_habtm_respects_select - 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 - end - - def test_updating_attributes_on_rich_associations - david = projects(:action_controller).developers.first - david.name = "DHH" - assert_raises(ActiveRecord::ReadOnlyRecord) { david.save! } - end - - def test_updating_attributes_on_rich_associations_with_limited_find_from_reflection - david = projects(:action_controller).selected_developers.first - david.name = "DHH" - assert_nothing_raised { david.save! } - end - - - def test_updating_attributes_on_rich_associations_with_limited_find - david = projects(:action_controller).developers.find(:all, :select => "developers.*").first - david.name = "DHH" - assert david.save! - 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 - end - - def test_join_with_group - group = Developer.columns.inject([]) do |g, c| - g << "developers.#{c.name}" - g << "developers_projects_2.#{c.name}" - 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 - end - - def test_get_ids - assert_equal projects(:active_record, :action_controller).map(&:id).sort, developers(:david).project_ids.sort - assert_equal [projects(:active_record).id], developers(:jamis).project_ids - end - - def test_assign_ids - developer = Developer.new("name" => "Joe") - developer.project_ids = projects(:active_record, :action_controller).map(&:id) - developer.save - developer.reload - assert_equal 2, developer.projects.length - assert_equal projects(:active_record), developer.projects[0] - assert_equal projects(:action_controller), developer.projects[1] - end - - def test_assign_ids_ignoring_blanks - developer = Developer.new("name" => "Joe") - developer.project_ids = [projects(:active_record).id, nil, projects(:action_controller).id, ''] - developer.save - developer.reload - assert_equal 2, developer.projects.length - assert_equal projects(:active_record), developer.projects[0] - assert_equal projects(:action_controller), developer.projects[1] - end - - def test_select_limited_ids_list - # Set timestamps - Developer.transaction do - Developer.find(:all, :order => 'id').each_with_index do |record, i| - record.update_attributes(:created_at => 5.years.ago + (i * 5.minutes)) - end - end - - join_base = ActiveRecord::Associations::ClassMethods::JoinDependency::JoinBase.new(Project) - join_dep = ActiveRecord::Associations::ClassMethods::JoinDependency.new(join_base, :developers, nil) - projects = Project.send(:select_limited_ids_list, {:order => 'developers.created_at'}, join_dep) - assert !projects.include?("'"), projects - assert_equal %w(1 2), projects.scan(/\d/).sort - end - - def test_scoped_find_on_through_association_doesnt_return_read_only_records - tag = Post.find(1).tags.find_by_name("General") - - assert_nothing_raised do - tag.save! - end - end -end - - -class OverridingAssociationsTest < ActiveSupport::TestCase - class Person < ActiveRecord::Base; end - class DifferentPerson < ActiveRecord::Base; end - - class PeopleList < ActiveRecord::Base - has_and_belongs_to_many :has_and_belongs_to_many, :before_add => :enlist - has_many :has_many, :before_add => :enlist - belongs_to :belongs_to - has_one :has_one - end - - class DifferentPeopleList < PeopleList - # Different association with the same name, callbacks should be omitted here. - has_and_belongs_to_many :has_and_belongs_to_many, :class_name => 'DifferentPerson' - has_many :has_many, :class_name => 'DifferentPerson' - belongs_to :belongs_to, :class_name => 'DifferentPerson' - has_one :has_one, :class_name => 'DifferentPerson' - end - - def test_habtm_association_redefinition_callbacks_should_differ_and_not_inherited - # redeclared association on AR descendant should not inherit callbacks from superclass - callbacks = PeopleList.read_inheritable_attribute(:before_add_for_has_and_belongs_to_many) - assert_equal([:enlist], callbacks) - callbacks = DifferentPeopleList.read_inheritable_attribute(:before_add_for_has_and_belongs_to_many) - assert_equal([], callbacks) - end - - def test_has_many_association_redefinition_callbacks_should_differ_and_not_inherited - # redeclared association on AR descendant should not inherit callbacks from superclass - callbacks = PeopleList.read_inheritable_attribute(:before_add_for_has_many) - assert_equal([:enlist], callbacks) - callbacks = DifferentPeopleList.read_inheritable_attribute(:before_add_for_has_many) - assert_equal([], callbacks) - end - - def test_habtm_association_redefinition_reflections_should_differ_and_not_inherited - assert_not_equal( - PeopleList.reflect_on_association(:has_and_belongs_to_many), - DifferentPeopleList.reflect_on_association(:has_and_belongs_to_many) - ) - end - - def test_has_many_association_redefinition_reflections_should_differ_and_not_inherited - assert_not_equal( - PeopleList.reflect_on_association(:has_many), - DifferentPeopleList.reflect_on_association(:has_many) - ) - end - - def test_belongs_to_association_redefinition_reflections_should_differ_and_not_inherited - assert_not_equal( - PeopleList.reflect_on_association(:belongs_to), - DifferentPeopleList.reflect_on_association(:belongs_to) - ) - end - - def test_has_one_association_redefinition_reflections_should_differ_and_not_inherited - assert_not_equal( - PeopleList.reflect_on_association(:has_one), - DifferentPeopleList.reflect_on_association(:has_one) - ) - end -end diff --git a/activerecord/test/attribute_methods_test.rb b/activerecord/test/attribute_methods_test.rb deleted file mode 100755 index 8ff52802ac..0000000000 --- a/activerecord/test/attribute_methods_test.rb +++ /dev/null @@ -1,146 +0,0 @@ -require 'abstract_unit' -require 'fixtures/topic' - -class AttributeMethodsTest < ActiveSupport::TestCase - fixtures :topics - def setup - @old_suffixes = ActiveRecord::Base.send(:attribute_method_suffixes).dup - @target = Class.new(ActiveRecord::Base) - @target.table_name = 'topics' - end - - def teardown - ActiveRecord::Base.send(:attribute_method_suffixes).clear - ActiveRecord::Base.attribute_method_suffix *@old_suffixes - end - - - def test_match_attribute_method_query_returns_match_data - assert_not_nil md = @target.match_attribute_method?('title=') - assert_equal 'title', md.pre_match - assert_equal ['='], md.captures - - %w(_hello_world ist! _maybe?).each do |suffix| - @target.class_eval "def attribute#{suffix}(*args) args end" - @target.attribute_method_suffix suffix - - assert_not_nil md = @target.match_attribute_method?("title#{suffix}") - assert_equal 'title', md.pre_match - assert_equal [suffix], md.captures - end - end - - def test_declared_attribute_method_affects_respond_to_and_method_missing - topic = @target.new(:title => 'Budget') - assert topic.respond_to?('title') - assert_equal 'Budget', topic.title - assert !topic.respond_to?('title_hello_world') - assert_raise(NoMethodError) { topic.title_hello_world } - - %w(_hello_world _it! _candidate= able?).each do |suffix| - @target.class_eval "def attribute#{suffix}(*args) args end" - @target.attribute_method_suffix suffix - - meth = "title#{suffix}" - assert topic.respond_to?(meth) - assert_equal ['title'], topic.send(meth) - assert_equal ['title', 'a'], topic.send(meth, 'a') - assert_equal ['title', 1, 2, 3], topic.send(meth, 1, 2, 3) - end - end - - def test_should_unserialize_attributes_for_frozen_records - myobj = {:value1 => :value2} - topic = Topic.create("content" => myobj) - topic.freeze - assert_equal myobj, topic.content - end - - def test_kernel_methods_not_implemented_in_activerecord - %w(test name display y).each do |method| - assert_equal false, ActiveRecord::Base.instance_method_already_implemented?(method), "##{method} is defined" - end - end - - def test_primary_key_implemented - assert_equal true, Class.new(ActiveRecord::Base).instance_method_already_implemented?('id') - 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_equal true, 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_equal true, 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 - klass.class_eval "def #{method}() 'defined #{method}' end" - assert_raises ActiveRecord::DangerousAttributeError do - klass.instance_method_already_implemented?(method) - end - end - end - - def test_only_time_related_columns_are_meant_to_be_cached_by_default - expected = %w(datetime timestamp time date).sort - assert_equal expected, ActiveRecord::Base.attribute_types_cached_by_default.map(&:to_s).sort -end - - def test_declaring_attributes_as_cached_adds_them_to_the_attributes_cached_by_default - default_attributes = Topic.cached_attributes - Topic.cache_attributes :replies_count - expected = default_attributes + ["replies_count"] - assert_equal expected.sort, Topic.cached_attributes.sort - Topic.instance_variable_set "@cached_attributes", nil - end - - def test_time_related_columns_are_actually_cached - column_types = %w(datetime timestamp time date).map(&:to_sym) - column_names = Topic.columns.select{|c| column_types.include?(c.type) }.map(&:name) - - assert_equal column_names.sort, Topic.cached_attributes.sort - assert_equal time_related_columns_on_topic.sort, Topic.cached_attributes.sort - end - - def test_accessing_cached_attributes_caches_the_converted_values_and_nothing_else - t = topics(:first) - cache = t.instance_variable_get "@attributes_cache" - - assert_not_nil cache - assert cache.empty? - - all_columns = Topic.columns.map(&:name) - cached_columns = time_related_columns_on_topic - uncached_columns = all_columns - cached_columns - - all_columns.each do |attr_name| - attribute_gets_cached = Topic.cache_attribute?(attr_name) - val = t.send attr_name unless attr_name == "type" - if attribute_gets_cached - assert cached_columns.include?(attr_name) - assert_equal val, cache[attr_name] - else - assert uncached_columns.include?(attr_name) - assert !cache.include?(attr_name) - end - end - end - - private - def time_related_columns_on_topic - Topic.columns.select{|c| [:time, :date, :datetime, :timestamp].include?(c.type)}.map(&:name) - end -end diff --git a/activerecord/test/base_test.rb b/activerecord/test/base_test.rb deleted file mode 100755 index a62a4d16c2..0000000000 --- a/activerecord/test/base_test.rb +++ /dev/null @@ -1,1831 +0,0 @@ -require 'abstract_unit' -require 'fixtures/topic' -require 'fixtures/reply' -require 'fixtures/company' -require 'fixtures/customer' -require 'fixtures/developer' -require 'fixtures/project' -require 'fixtures/default' -require 'fixtures/auto_id' -require 'fixtures/column_name' -require 'fixtures/subscriber' -require 'fixtures/keyboard' -require 'fixtures/post' -require 'fixtures/minimalistic' -require 'fixtures/warehouse_thing' -require 'rexml/document' - -class Category < ActiveRecord::Base; end -class Smarts < ActiveRecord::Base; end -class CreditCard < ActiveRecord::Base - class PinNumber < ActiveRecord::Base - class CvvCode < ActiveRecord::Base; end - class SubCvvCode < CvvCode; end - end - class SubPinNumber < PinNumber; end - class Brand < Category; end -end -class MasterCreditCard < ActiveRecord::Base; end -class Post < ActiveRecord::Base; end -class Computer < ActiveRecord::Base; end -class NonExistentTable < ActiveRecord::Base; end -class TestOracleDefault < ActiveRecord::Base; end - -class LoosePerson < ActiveRecord::Base - self.table_name = 'people' - self.abstract_class = true - attr_protected :credit_rating, :administrator -end - -class LooseDescendant < LoosePerson - attr_protected :phone_number -end - -class LooseDescendantSecond< LoosePerson - attr_protected :phone_number - attr_protected :name -end - -class TightPerson < ActiveRecord::Base - self.table_name = 'people' - attr_accessible :name, :address -end - -class TightDescendant < TightPerson - attr_accessible :phone_number -end - -class ReadonlyTitlePost < Post - attr_readonly :title -end - -class Booleantest < ActiveRecord::Base; end - -class Task < ActiveRecord::Base - attr_protected :starting -end - -class TopicWithProtectedContentAndAccessibleAuthorName < ActiveRecord::Base - self.table_name = 'topics' - attr_accessible :author_name - attr_protected :content -end - -class BasicsTest < ActiveSupport::TestCase - fixtures :topics, :companies, :developers, :projects, :computers, :accounts, :minimalistics, 'warehouse-things' - - def test_table_exists - assert !NonExistentTable.table_exists? - assert Topic.table_exists? - end - - def test_set_attributes - topic = Topic.find(1) - topic.attributes = { "title" => "Budget", "author_name" => "Jason" } - topic.save - assert_equal("Budget", topic.title) - assert_equal("Jason", topic.author_name) - assert_equal(topics(:first).author_email_address, Topic.find(1).author_email_address) - end - - def test_integers_as_nil - test = AutoId.create('value' => '') - assert_nil AutoId.find(test.id).value - end - - def test_set_attributes_with_block - topic = Topic.new do |t| - t.title = "Budget" - t.author_name = "Jason" - end - - assert_equal("Budget", topic.title) - assert_equal("Jason", topic.author_name) - end - - def test_respond_to? - topic = Topic.find(1) - assert topic.respond_to?("title") - assert topic.respond_to?("title?") - assert topic.respond_to?("title=") - assert topic.respond_to?(:title) - assert topic.respond_to?(:title?) - assert topic.respond_to?(:title=) - assert topic.respond_to?("author_name") - assert topic.respond_to?("attribute_names") - assert !topic.respond_to?("nothingness") - assert !topic.respond_to?(:nothingness) - end - - def test_array_content - topic = Topic.new - topic.content = %w( one two three ) - topic.save - - assert_equal(%w( one two three ), Topic.find(topic.id).content) - end - - def test_hash_content - topic = Topic.new - topic.content = { "one" => 1, "two" => 2 } - topic.save - - assert_equal 2, Topic.find(topic.id).content["two"] - - topic.content["three"] = 3 - topic.save - - assert_equal 3, Topic.find(topic.id).content["three"] - end - - def test_update_array_content - topic = Topic.new - topic.content = %w( one two three ) - - topic.content.push "four" - assert_equal(%w( one two three four ), topic.content) - - topic.save - - topic = Topic.find(topic.id) - topic.content << "five" - assert_equal(%w( one two three four five ), topic.content) - end - - def test_case_sensitive_attributes_hash - # DB2 is not case-sensitive - return true if current_adapter?(:DB2Adapter) - - assert_equal @loaded_fixtures['computers']['workstation'].to_hash, Computer.find(:first).attributes - end - - def test_create - topic = Topic.new - topic.title = "New Topic" - topic.save - topic_reloaded = Topic.find(topic.id) - assert_equal("New Topic", topic_reloaded.title) - end - - def test_save! - topic = Topic.new(:title => "New Topic") - assert topic.save! - - reply = Reply.new - assert_raise(ActiveRecord::RecordInvalid) { reply.save! } - end - - def test_save_null_string_attributes - topic = Topic.find(1) - topic.attributes = { "title" => "null", "author_name" => "null" } - topic.save! - topic.reload - assert_equal("null", topic.title) - assert_equal("null", topic.author_name) - end - - def test_save_nil_string_attributes - topic = Topic.find(1) - topic.title = nil - topic.save! - topic.reload - assert_nil topic.title - end - - def test_save_for_record_with_only_primary_key - minimalistic = Minimalistic.new - assert_nothing_raised { minimalistic.save } - end - - def test_save_for_record_with_only_primary_key_that_is_provided - assert_nothing_raised { Minimalistic.create!(:id => 2) } - end - - def test_hashes_not_mangled - new_topic = { :title => "New Topic" } - new_topic_values = { :title => "AnotherTopic" } - - topic = Topic.new(new_topic) - assert_equal new_topic[:title], topic.title - - topic.attributes= new_topic_values - assert_equal new_topic_values[:title], topic.title - end - - def test_create_many - topics = Topic.create([ { "title" => "first" }, { "title" => "second" }]) - assert_equal 2, topics.size - assert_equal "first", topics.first.title - end - - def test_create_columns_not_equal_attributes - topic = Topic.new - topic.title = 'Another New Topic' - topic.send :write_attribute, 'does_not_exist', 'test' - assert_nothing_raised { topic.save } - end - - def test_create_through_factory - topic = Topic.create("title" => "New Topic") - topicReloaded = Topic.find(topic.id) - assert_equal(topic, topicReloaded) - end - - def test_update - topic = Topic.new - topic.title = "Another New Topic" - topic.written_on = "2003-12-12 23:23:00" - topic.save - topicReloaded = Topic.find(topic.id) - assert_equal("Another New Topic", topicReloaded.title) - - topicReloaded.title = "Updated topic" - topicReloaded.save - - topicReloadedAgain = Topic.find(topic.id) - - assert_equal("Updated topic", topicReloadedAgain.title) - end - - def test_update_columns_not_equal_attributes - topic = Topic.new - 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' - assert_nothing_raised { topicReloaded.save } - end - - def test_update_for_record_with_only_primary_key - minimalistic = minimalistics(:first) - assert_nothing_raised { minimalistic.save } - end - - def test_write_attribute - topic = Topic.new - 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") - assert_equal "Still another topic: part 2", topic.title - end - - def test_read_attribute - topic = Topic.new - topic.title = "Don't change the topic" - assert_equal "Don't change the topic", topic.send(:read_attribute, "title") - assert_equal "Don't change the topic", topic["title"] - - assert_equal "Don't change the topic", topic.send(:read_attribute, :title) - assert_equal "Don't change the topic", topic[:title] - end - - def test_read_attribute_when_false - topic = topics(:first) - topic.approved = false - assert !topic.approved?, "approved should be false" - topic.approved = "false" - assert !topic.approved?, "approved should be false" - end - - def test_read_attribute_when_true - topic = topics(:first) - topic.approved = true - assert topic.approved?, "approved should be true" - topic.approved = "true" - assert topic.approved?, "approved should be true" - end - - def test_read_write_boolean_attribute - topic = Topic.new - # puts "" - # puts "New Topic" - # puts topic.inspect - topic.approved = "false" - # puts "Expecting false" - # puts topic.inspect - assert !topic.approved?, "approved should be false" - topic.approved = "false" - # puts "Expecting false" - # puts topic.inspect - assert !topic.approved?, "approved should be false" - topic.approved = "true" - # puts "Expecting true" - # puts topic.inspect - assert topic.approved?, "approved should be true" - topic.approved = "true" - # puts "Expecting true" - # puts topic.inspect - assert topic.approved?, "approved should be true" - # puts "" - end - - def test_query_attribute_string - [nil, "", " "].each do |value| - assert_equal false, Topic.new(:author_name => value).author_name? - end - - assert_equal true, Topic.new(:author_name => "Name").author_name? - end - - def test_query_attribute_number - [nil, 0, "0"].each do |value| - assert_equal false, Developer.new(:salary => value).salary? - end - - assert_equal true, Developer.new(:salary => 1).salary? - assert_equal true, Developer.new(:salary => "1").salary? - end - - def test_query_attribute_boolean - [nil, "", false, "false", "f", 0].each do |value| - assert_equal false, Topic.new(:approved => value).approved? - end - - [true, "true", "1", 1].each do |value| - assert_equal true, Topic.new(:approved => value).approved? - end - end - - def test_query_attribute_with_custom_fields - object = Company.find_by_sql(<<-SQL).first - SELECT c1.*, c2.ruby_type as string_value, c2.rating as int_value - FROM companies c1, companies c2 - WHERE c1.firm_id = c2.id - AND c1.id = 2 - SQL - - assert_equal "Firm", object.string_value - assert object.string_value? - - object.string_value = " " - assert !object.string_value? - - assert_equal 1, object.int_value.to_i - assert object.int_value? - - object.int_value = "0" - assert !object.int_value? - end - - - def test_reader_for_invalid_column_names - Topic.send(:define_read_method, "mumub-jumbo".to_sym, "mumub-jumbo", nil) - assert !Topic.generated_methods.include?("mumub-jumbo") - end - - def test_non_attribute_access_and_assignment - topic = Topic.new - assert !topic.respond_to?("mumbo") - assert_raises(NoMethodError) { topic.mumbo } - assert_raises(NoMethodError) { topic.mumbo = 5 } - end - - def test_preserving_date_objects - # SQL Server doesn't have a separate column type just for dates, so all are returned as time - return true if current_adapter?(:SQLServerAdapter) - - if current_adapter?(:SybaseAdapter, :OracleAdapter) - # Sybase ctlib does not (yet?) support the date type; use datetime instead. - # Oracle treats all dates/times as Time. - assert_kind_of( - Time, Topic.find(1).last_read, - "The last_read attribute should be of the Time class" - ) - else - assert_kind_of( - Date, Topic.find(1).last_read, - "The last_read attribute should be of the Date class" - ) - end - end - - def test_preserving_time_objects - assert_kind_of( - Time, Topic.find(1).bonus_time, - "The bonus_time attribute should be of the Time class" - ) - - assert_kind_of( - Time, Topic.find(1).written_on, - "The written_on attribute should be of the Time class" - ) - - # For adapters which support microsecond resolution. - if current_adapter?(:PostgreSQLAdapter) - 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 - end - end - - def test_custom_mutator - topic = Topic.find(1) - # This mutator is protected in the class definition - topic.send(:approved=, true) - assert topic.instance_variable_get("@custom_approved") - end - - def test_destroy - topic = Topic.find(1) - assert_equal topic, topic.destroy, 'topic.destroy did not return self' - assert topic.frozen?, 'topic not frozen after destroy' - assert_raise(ActiveRecord::RecordNotFound) { Topic.find(topic.id) } - end - - def test_record_not_found_exception - assert_raises(ActiveRecord::RecordNotFound) { topicReloaded = Topic.find(99999) } - end - - def test_initialize_with_attributes - topic = Topic.new({ - "title" => "initialized from attributes", "written_on" => "2003-12-12 23:23" - }) - - assert_equal("initialized from attributes", topic.title) - end - - def test_initialize_with_invalid_attribute - begin - topic = Topic.new({ "title" => "test", - "last_read(1i)" => "2005", "last_read(2i)" => "2", "last_read(3i)" => "31"}) - rescue ActiveRecord::MultiparameterAssignmentErrors => ex - assert_equal(1, ex.errors.size) - assert_equal("last_read", ex.errors[0].attribute) - end - end - - def test_load - topics = Topic.find(:all, :order => 'id') - assert_equal(2, topics.size) - assert_equal(topics(:first).title, topics.first.title) - end - - def test_load_with_condition - topics = Topic.find(:all, :conditions => "author_name = 'Mary'") - - assert_equal(1, topics.size) - assert_equal(topics(:second).title, topics.first.title) - end - - def test_table_name_guesses - classes = [Category, Smarts, CreditCard, CreditCard::PinNumber, CreditCard::PinNumber::CvvCode, CreditCard::SubPinNumber, CreditCard::Brand, MasterCreditCard] - - assert_equal "topics", Topic.table_name - - assert_equal "categories", Category.table_name - assert_equal "smarts", Smarts.table_name - assert_equal "credit_cards", CreditCard.table_name - assert_equal "credit_card_pin_numbers", CreditCard::PinNumber.table_name - assert_equal "credit_card_pin_number_cvv_codes", CreditCard::PinNumber::CvvCode.table_name - assert_equal "credit_card_pin_numbers", CreditCard::SubPinNumber.table_name - assert_equal "categories", CreditCard::Brand.table_name - assert_equal "master_credit_cards", MasterCreditCard.table_name - - ActiveRecord::Base.pluralize_table_names = false - classes.each(&:reset_table_name) - - assert_equal "category", Category.table_name - assert_equal "smarts", Smarts.table_name - assert_equal "credit_card", CreditCard.table_name - assert_equal "credit_card_pin_number", CreditCard::PinNumber.table_name - assert_equal "credit_card_pin_number_cvv_code", CreditCard::PinNumber::CvvCode.table_name - assert_equal "credit_card_pin_number", CreditCard::SubPinNumber.table_name - assert_equal "category", CreditCard::Brand.table_name - assert_equal "master_credit_card", MasterCreditCard.table_name - - ActiveRecord::Base.pluralize_table_names = true - classes.each(&:reset_table_name) - - ActiveRecord::Base.table_name_prefix = "test_" - Category.reset_table_name - assert_equal "test_categories", Category.table_name - ActiveRecord::Base.table_name_suffix = "_test" - Category.reset_table_name - assert_equal "test_categories_test", Category.table_name - ActiveRecord::Base.table_name_prefix = "" - Category.reset_table_name - assert_equal "categories_test", Category.table_name - ActiveRecord::Base.table_name_suffix = "" - Category.reset_table_name - assert_equal "categories", Category.table_name - - ActiveRecord::Base.pluralize_table_names = false - ActiveRecord::Base.table_name_prefix = "test_" - Category.reset_table_name - assert_equal "test_category", Category.table_name - ActiveRecord::Base.table_name_suffix = "_test" - Category.reset_table_name - assert_equal "test_category_test", Category.table_name - ActiveRecord::Base.table_name_prefix = "" - Category.reset_table_name - assert_equal "category_test", Category.table_name - ActiveRecord::Base.table_name_suffix = "" - Category.reset_table_name - assert_equal "category", Category.table_name - - ActiveRecord::Base.pluralize_table_names = true - classes.each(&:reset_table_name) - end - - def test_destroy_all - assert_equal 2, Topic.count - - Topic.destroy_all "author_name = 'Mary'" - assert_equal 1, Topic.count - end - - def test_destroy_many - assert_equal 3, Client.count - Client.destroy([2, 3]) - assert_equal 1, Client.count - end - - def test_delete_many - Topic.delete([1, 2]) - assert_equal 0, Topic.count - end - - def test_boolean_attributes - assert ! Topic.find(1).approved? - assert Topic.find(2).approved? - end - - def test_increment_counter - Topic.increment_counter("replies_count", 1) - assert_equal 2, Topic.find(1).replies_count - - Topic.increment_counter("replies_count", 1) - assert_equal 3, Topic.find(1).replies_count - end - - def test_decrement_counter - Topic.decrement_counter("replies_count", 2) - assert_equal -1, Topic.find(2).replies_count - - Topic.decrement_counter("replies_count", 2) - assert_equal -2, Topic.find(2).replies_count - end - - def test_update_all - assert_equal 2, Topic.update_all("content = 'bulk updated!'") - assert_equal "bulk updated!", Topic.find(1).content - assert_equal "bulk updated!", Topic.find(2).content - - assert_equal 2, Topic.update_all(['content = ?', 'bulk updated again!']) - assert_equal "bulk updated again!", Topic.find(1).content - assert_equal "bulk updated again!", Topic.find(2).content - - assert_equal 2, Topic.update_all(['content = ?', nil]) - assert_nil Topic.find(1).content - end - - def test_update_all_with_hash - assert_not_nil Topic.find(1).last_read - assert_equal 2, Topic.update_all(:content => 'bulk updated with hash!', :last_read => nil) - assert_equal "bulk updated with hash!", Topic.find(1).content - assert_equal "bulk updated with hash!", Topic.find(2).content - assert_nil Topic.find(1).last_read - assert_nil Topic.find(2).last_read - end - - def test_update_all_with_non_standard_table_name - assert_equal 1, WarehouseThing.update_all(['value = ?', 0], ['id = ?', 1]) - assert_equal 0, WarehouseThing.find(1).value - end - - if current_adapter?(:MysqlAdapter) - def test_update_all_with_order_and_limit - assert_equal 1, Topic.update_all("content = 'bulk updated!'", nil, :limit => 1, :order => 'id DESC') - end - end - - def test_update_all_ignores_order_limit_from_association - author = Author.find(1) - assert_nothing_raised do - assert_equal author.posts_with_comments_and_categories.length, author.posts_with_comments_and_categories.update_all("body = 'bulk update!'") - end - end - - def test_update_many - topic_data = { 1 => { "content" => "1 updated" }, 2 => { "content" => "2 updated" } } - updated = Topic.update(topic_data.keys, topic_data.values) - - assert_equal 2, updated.size - assert_equal "1 updated", Topic.find(1).content - assert_equal "2 updated", Topic.find(2).content - end - - def test_delete_all - assert_equal 2, 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 - end - - def test_attribute_present - t = Topic.new - t.title = "hello there!" - t.written_on = Time.now - assert t.attribute_present?("title") - assert t.attribute_present?("written_on") - assert !t.attribute_present?("content") - end - - def test_attribute_keys_on_new_instance - t = Topic.new - assert_equal nil, t.title, "The topics table has a title column, so it should be nil" - assert_raise(NoMethodError) { t.title2 } - end - - def test_class_name - assert_equal "Firm", ActiveRecord::Base.class_name("firms") - assert_equal "Category", ActiveRecord::Base.class_name("categories") - assert_equal "AccountHolder", ActiveRecord::Base.class_name("account_holder") - - ActiveRecord::Base.pluralize_table_names = false - assert_equal "Firms", ActiveRecord::Base.class_name( "firms" ) - ActiveRecord::Base.pluralize_table_names = true - - ActiveRecord::Base.table_name_prefix = "test_" - assert_equal "Firm", ActiveRecord::Base.class_name( "test_firms" ) - ActiveRecord::Base.table_name_suffix = "_tests" - assert_equal "Firm", ActiveRecord::Base.class_name( "test_firms_tests" ) - ActiveRecord::Base.table_name_prefix = "" - assert_equal "Firm", ActiveRecord::Base.class_name( "firms_tests" ) - ActiveRecord::Base.table_name_suffix = "" - assert_equal "Firm", ActiveRecord::Base.class_name( "firms" ) - end - - def test_null_fields - assert_nil Topic.find(1).parent_id - assert_nil Topic.create("title" => "Hey you").parent_id - end - - def test_default_values - topic = Topic.new - assert topic.approved? - assert_nil topic.written_on - assert_nil topic.bonus_time - assert_nil topic.last_read - - topic.save - - topic = Topic.find(topic.id) - assert topic.approved? - assert_nil topic.last_read - - # Oracle has some funky default handling, so it requires a bit of - # extra testing. See ticket #2788. - if current_adapter?(:OracleAdapter) - test = TestOracleDefault.new - assert_equal "X", test.test_char - assert_equal "hello", test.test_string - assert_equal 3, test.test_int - end - end - - # Oracle, SQLServer, and Sybase do not have a TIME datatype. - unless current_adapter?(:SQLServerAdapter, :OracleAdapter, :SybaseAdapter) - def test_utc_as_time_zone - Topic.default_timezone = :utc - attributes = { "bonus_time" => "5:42:00AM" } - topic = Topic.find(1) - topic.attributes = attributes - assert_equal Time.utc(2000, 1, 1, 5, 42, 0), topic.bonus_time - Topic.default_timezone = :local - end - - def test_utc_as_time_zone_and_new - Topic.default_timezone = :utc - attributes = { "bonus_time(1i)"=>"2000", - "bonus_time(2i)"=>"1", - "bonus_time(3i)"=>"1", - "bonus_time(4i)"=>"10", - "bonus_time(5i)"=>"35", - "bonus_time(6i)"=>"50" } - topic = Topic.new(attributes) - assert_equal Time.utc(2000, 1, 1, 10, 35, 50), topic.bonus_time - Topic.default_timezone = :local - end - end - - def test_default_values_on_empty_strings - topic = Topic.new - topic.approved = nil - topic.last_read = nil - - topic.save - - topic = Topic.find(topic.id) - assert_nil topic.last_read - - # Sybase adapter does not allow nulls in boolean columns - if current_adapter?(:SybaseAdapter) - assert topic.approved == false - else - assert_nil topic.approved - end - end - - def test_equality - assert_equal Topic.find(1), Topic.find(2).topic - end - - def test_equality_of_new_records - assert_not_equal Topic.new, Topic.new - end - - def test_hashing - assert_equal [ Topic.find(1) ], [ Topic.find(2).topic ] & [ Topic.find(1) ] - end - - def test_destroy_new_record - client = Client.new - client.destroy - assert client.frozen? - end - - def test_destroy_record_with_associations - client = Client.find(3) - client.destroy - assert client.frozen? - assert_kind_of Firm, client.firm - assert_raises(ActiveSupport::FrozenObjectError) { client.name = "something else" } - end - - def test_update_attribute - assert !Topic.find(1).approved? - Topic.find(1).update_attribute("approved", true) - assert Topic.find(1).approved? - - Topic.find(1).update_attribute(:approved, false) - assert !Topic.find(1).approved? - end - - def test_update_attributes - topic = Topic.find(1) - assert !topic.approved? - assert_equal "The First Topic", topic.title - - topic.update_attributes("approved" => true, "title" => "The First Topic Updated") - topic.reload - assert topic.approved? - assert_equal "The First Topic Updated", topic.title - - topic.update_attributes(:approved => false, :title => "The First Topic") - topic.reload - assert !topic.approved? - assert_equal "The First Topic", topic.title - end - - def test_update_attributes! - reply = Reply.find(2) - assert_equal "The Second Topic's of the day", reply.title - assert_equal "Have a nice day", reply.content - - reply.update_attributes!("title" => "The Second Topic's of the day updated", "content" => "Have a nice evening") - reply.reload - assert_equal "The Second Topic's of the day updated", reply.title - assert_equal "Have a nice evening", reply.content - - reply.update_attributes!(:title => "The Second Topic's of the day", :content => "Have a nice day") - reply.reload - assert_equal "The Second Topic's of the day", reply.title - assert_equal "Have a nice day", reply.content - - assert_raise(ActiveRecord::RecordInvalid) { reply.update_attributes!(:title => nil, :content => "Have a nice evening") } - end - - def test_mass_assignment_should_raise_exception_if_accessible_and_protected_attribute_writers_are_both_used - topic = TopicWithProtectedContentAndAccessibleAuthorName.new - assert_raises(RuntimeError) { topic.attributes = { "author_name" => "me" } } - assert_raises(RuntimeError) { topic.attributes = { "content" => "stuff" } } - end - - def test_mass_assignment_protection - firm = Firm.new - firm.attributes = { "name" => "Next Angle", "rating" => 5 } - assert_equal 1, firm.rating - end - - def test_mass_assignment_protection_against_class_attribute_writers - [:logger, :configurations, :primary_key_prefix_type, :table_name_prefix, :table_name_suffix, :pluralize_table_names, :colorize_logging, - :default_timezone, :allow_concurrency, :schema_format, :verification_timeout, :lock_optimistically, :record_timestamps].each do |method| - assert Task.respond_to?(method) - assert Task.respond_to?("#{method}=") - assert Task.new.respond_to?(method) - assert !Task.new.respond_to?("#{method}=") - end - end - - def test_customized_primary_key_remains_protected - subscriber = Subscriber.new(:nick => 'webster123', :name => 'nice try') - assert_nil subscriber.id - - keyboard = Keyboard.new(:key_number => 9, :name => 'nice try') - assert_nil keyboard.id - end - - def test_customized_primary_key_remains_protected_when_referred_to_as_id - subscriber = Subscriber.new(:id => 'webster123', :name => 'nice try') - assert_nil subscriber.id - - keyboard = Keyboard.new(:id => 9, :name => 'nice try') - assert_nil keyboard.id - end - - def test_mass_assignment_protection_on_defaults - firm = Firm.new - firm.attributes = { "id" => 5, "type" => "Client" } - assert_nil firm.id - assert_equal "Firm", firm[:type] - end - - def test_mass_assignment_accessible - reply = Reply.new("title" => "hello", "content" => "world", "approved" => true) - reply.save - - assert reply.approved? - - reply.approved = false - reply.save - - assert !reply.approved? - end - - def test_mass_assignment_protection_inheritance - assert_nil LoosePerson.accessible_attributes - assert_equal Set.new([ 'credit_rating', 'administrator' ]), LoosePerson.protected_attributes - - assert_nil LooseDescendant.accessible_attributes - assert_equal Set.new([ 'credit_rating', 'administrator', 'phone_number' ]), LooseDescendant.protected_attributes - - assert_nil LooseDescendantSecond.accessible_attributes - assert_equal Set.new([ 'credit_rating', 'administrator', 'phone_number', 'name' ]), LooseDescendantSecond.protected_attributes, 'Running attr_protected twice in one class should merge the protections' - - assert_nil TightPerson.protected_attributes - assert_equal Set.new([ 'name', 'address' ]), TightPerson.accessible_attributes - - assert_nil TightDescendant.protected_attributes - assert_equal Set.new([ 'name', 'address', 'phone_number' ]), TightDescendant.accessible_attributes - end - - def test_readonly_attributes - assert_equal Set.new([ 'title' ]), ReadonlyTitlePost.readonly_attributes - - post = ReadonlyTitlePost.create(:title => "cannot change this", :body => "changeable") - post.reload - assert_equal "cannot change this", post.title - - post.update_attributes(:title => "try to change", :body => "changed") - post.reload - assert_equal "cannot change this", post.title - assert_equal "changed", post.body - end - - def test_multiparameter_attributes_on_date - attributes = { "last_read(1i)" => "2004", "last_read(2i)" => "6", "last_read(3i)" => "24" } - topic = Topic.find(1) - topic.attributes = attributes - # note that extra #to_date call allows test to pass for Oracle, which - # treats dates/times the same - assert_date_from_db Date.new(2004, 6, 24), topic.last_read.to_date - end - - def test_multiparameter_attributes_on_date_with_empty_date - attributes = { "last_read(1i)" => "2004", "last_read(2i)" => "6", "last_read(3i)" => "" } - topic = Topic.find(1) - topic.attributes = attributes - # note that extra #to_date call allows test to pass for Oracle, which - # treats dates/times the same - assert_date_from_db Date.new(2004, 6, 1), topic.last_read.to_date - end - - def test_multiparameter_attributes_on_date_with_all_empty - attributes = { "last_read(1i)" => "", "last_read(2i)" => "", "last_read(3i)" => "" } - topic = Topic.find(1) - topic.attributes = attributes - assert_nil topic.last_read - end - - def test_multiparameter_attributes_on_time - attributes = { - "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24", - "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00" - } - topic = Topic.find(1) - topic.attributes = attributes - assert_equal Time.local(2004, 6, 24, 16, 24, 0), topic.written_on - end - - def test_multiparameter_attributes_on_time_with_empty_seconds - attributes = { - "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24", - "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "" - } - topic = Topic.find(1) - topic.attributes = attributes - assert_equal Time.local(2004, 6, 24, 16, 24, 0), topic.written_on - end - - def test_multiparameter_mass_assignment_protector - task = Task.new - time = Time.mktime(2000, 1, 1, 1) - task.starting = time - attributes = { "starting(1i)" => "2004", "starting(2i)" => "6", "starting(3i)" => "24" } - task.attributes = attributes - assert_equal time, task.starting - end - - def test_multiparameter_assignment_of_aggregation - customer = Customer.new - address = Address.new("The Street", "The City", "The Country") - attributes = { "address(1)" => address.street, "address(2)" => address.city, "address(3)" => address.country } - customer.attributes = attributes - assert_equal address, customer.address - end - - def test_attributes_on_dummy_time - # Oracle, SQL Server, and Sybase do not have a TIME datatype. - return true if current_adapter?(:SQLServerAdapter, :OracleAdapter, :SybaseAdapter) - - 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 - end - - def test_boolean - b_false = Booleantest.create({ "value" => false }) - false_id = b_false.id - b_true = Booleantest.create({ "value" => true }) - true_id = b_true.id - - b_false = Booleantest.find(false_id) - assert !b_false.value? - b_true = Booleantest.find(true_id) - assert b_true.value? - end - - def test_boolean_cast_from_string - b_false = Booleantest.create({ "value" => "0" }) - false_id = b_false.id - b_true = Booleantest.create({ "value" => "1" }) - true_id = b_true.id - - b_false = Booleantest.find(false_id) - assert !b_false.value? - b_true = Booleantest.find(true_id) - assert b_true.value? - end - - def test_clone - topic = Topic.find(1) - cloned_topic = nil - assert_nothing_raised { cloned_topic = topic.clone } - assert_equal topic.title, cloned_topic.title - assert cloned_topic.new_record? - - # test if the attributes have been cloned - topic.title = "a" - cloned_topic.title = "b" - assert_equal "a", topic.title - assert_equal "b", cloned_topic.title - - # test if the attribute values have been cloned - topic.title = {"a" => "b"} - cloned_topic = topic.clone - cloned_topic.title["a"] = "c" - assert_equal "b", topic.title["a"] - - #test if attributes set as part of after_initialize are cloned correctly - assert_equal topic.author_email_address, cloned_topic.author_email_address - - # test if saved clone object differs from original - cloned_topic.save - assert !cloned_topic.new_record? - assert cloned_topic.id != topic.id - end - - def test_clone_with_aggregate_of_same_name_as_attribute - dev = DeveloperWithAggregate.find(1) - assert_kind_of DeveloperSalary, dev.salary - - clone = nil - assert_nothing_raised { clone = dev.clone } - assert_kind_of DeveloperSalary, clone.salary - assert_equal dev.salary.amount, clone.salary.amount - assert clone.new_record? - - # test if the attributes have been cloned - original_amount = clone.salary.amount - dev.salary.amount = 1 - assert_equal original_amount, clone.salary.amount - - assert clone.save - assert !clone.new_record? - assert clone.id != dev.id - end - - def test_clone_preserves_subtype - clone = nil - assert_nothing_raised { clone = Company.find(3).clone } - assert_kind_of Client, clone - end - - def test_bignum - company = Company.find(1) - company.rating = 2147483647 - company.save - company = Company.find(1) - assert_equal 2147483647, company.rating - end - - # TODO: extend defaults tests to other databases! - if current_adapter?(:PostgreSQLAdapter) - def test_default - default = Default.new - - # fixed dates / times - assert_equal Date.new(2004, 1, 1), default.fixed_date - assert_equal Time.local(2004, 1,1,0,0,0,0), default.fixed_time - - # char types - assert_equal 'Y', default.char1 - assert_equal 'a varchar field', default.char2 - assert_equal 'a text field', default.char3 - end - - class Geometric < ActiveRecord::Base; end - def test_geometric_content - - # accepted format notes: - # ()'s aren't required - # values can be a mix of float or integer - - g = Geometric.new( - :a_point => '(5.0, 6.1)', - #:a_line => '((2.0, 3), (5.5, 7.0))' # line type is currently unsupported in postgresql - :a_line_segment => '(2.0, 3), (5.5, 7.0)', - :a_box => '2.0, 3, 5.5, 7.0', - :a_path => '[(2.0, 3), (5.5, 7.0), (8.5, 11.0)]', # [ ] is an open path - :a_polygon => '((2.0, 3), (5.5, 7.0), (8.5, 11.0))', - :a_circle => '<(5.3, 10.4), 2>' - ) - - assert g.save - - # Reload and check that we have all the geometric attributes. - h = Geometric.find(g.id) - - assert_equal '(5,6.1)', h.a_point - assert_equal '[(2,3),(5.5,7)]', h.a_line_segment - assert_equal '(5.5,7),(2,3)', h.a_box # reordered to store upper right corner then bottom left corner - assert_equal '[(2,3),(5.5,7),(8.5,11)]', h.a_path - assert_equal '((2,3),(5.5,7),(8.5,11))', h.a_polygon - assert_equal '<(5.3,10.4),2>', h.a_circle - - # 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' - - # test alternate formats when defining the geometric types - - g = Geometric.new( - :a_point => '5.0, 6.1', - #:a_line => '((2.0, 3), (5.5, 7.0))' # line type is currently unsupported in postgresql - :a_line_segment => '((2.0, 3), (5.5, 7.0))', - :a_box => '(2.0, 3), (5.5, 7.0)', - :a_path => '((2.0, 3), (5.5, 7.0), (8.5, 11.0))', # ( ) is a closed path - :a_polygon => '2.0, 3, 5.5, 7.0, 8.5, 11.0', - :a_circle => '((5.3, 10.4), 2)' - ) - - assert g.save - - # Reload and check that we have all the geometric attributes. - h = Geometric.find(g.id) - - assert_equal '(5,6.1)', h.a_point - assert_equal '[(2,3),(5.5,7)]', h.a_line_segment - assert_equal '(5.5,7),(2,3)', h.a_box # reordered to store upper right corner then bottom left corner - assert_equal '((2,3),(5.5,7),(8.5,11))', h.a_path - assert_equal '((2,3),(5.5,7),(8.5,11))', h.a_polygon - assert_equal '<(5.3,10.4),2>', h.a_circle - - # 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' - end - end - - class NumericData < ActiveRecord::Base - self.table_name = 'numeric_data' - end - - def test_numeric_fields - m = NumericData.new( - :bank_balance => 1586.43, - :big_bank_balance => BigDecimal("1000234000567.95"), - :world_population => 6000000000, - :my_house_population => 3 - ) - assert m.save - - m1 = NumericData.find(m.id) - assert_not_nil m1 - - # As with migration_test.rb, we should make world_population >= 2**62 - # to cover 64-bit platforms and test it is a Bignum, but the main thing - # is that it's an Integer. - assert_kind_of Integer, m1.world_population - assert_equal 6000000000, m1.world_population - - assert_kind_of Fixnum, m1.my_house_population - assert_equal 3, m1.my_house_population - - assert_kind_of BigDecimal, m1.bank_balance - assert_equal BigDecimal("1586.43"), m1.bank_balance - - assert_kind_of BigDecimal, m1.big_bank_balance - assert_equal BigDecimal("1000234000567.95"), m1.big_bank_balance - end - - def test_auto_id - auto = AutoId.new - auto.save - 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("", inverted["bar"]) - assert_equal("", inverted["quux"]) - end - - def test_sql_injection_via_find - assert_raises(ActiveRecord::RecordNotFound, ActiveRecord::StatementInvalid) do - Topic.find("123456 OR id > 0") - end - end - - def test_column_name_properly_quoted - col_record = ColumnName.new - col_record.references = 40 - assert col_record.save - col_record.references = 41 - assert col_record.save - assert_not_nil c2 = ColumnName.find(col_record.id) - assert_equal(41, c2.references) - end - - def test_quoting_arrays - replies = Reply.find(:all, :conditions => [ "id IN (?)", topics(:first).replies.collect(&:id) ]) - assert_equal topics(:first).replies.size, replies.size - - replies = Reply.find(:all, :conditions => [ "id IN (?)", [] ]) - assert_equal 0, replies.size - end - - MyObject = Struct.new :attribute1, :attribute2 - - def test_serialized_attribute - myobj = MyObject.new('value1', 'value2') - topic = Topic.create("content" => myobj) - Topic.serialize("content", MyObject) - assert_equal(myobj, topic.content) - end - - def test_nil_serialized_attribute_with_class_constraint - myobj = MyObject.new('value1', 'value2') - topic = Topic.new - assert_nil topic.content - end - - def test_should_raise_exception_on_serialized_attribute_with_type_mismatch - myobj = MyObject.new('value1', 'value2') - topic = Topic.new(:content => myobj) - assert topic.save - Topic.serialize(:content, Hash) - assert_raise(ActiveRecord::SerializationTypeMismatch) { Topic.find(topic.id).content } - ensure - Topic.serialize(:content) - end - - def test_serialized_attribute_with_class_constraint - settings = { "color" => "blue" } - Topic.serialize(:content, Hash) - topic = Topic.new(:content => settings) - assert topic.save - assert_equal(settings, Topic.find(topic.id).content) - ensure - Topic.serialize(:content) - end - - def test_quote - author_name = "\\ \001 ' \n \\n \"" - topic = Topic.create('author_name' => author_name) - assert_equal author_name, Topic.find(topic.id).author_name - end - - if RUBY_VERSION < '1.9' - def test_quote_chars - str = 'The Narrator' - topic = Topic.create(:author_name => str) - assert_equal str, topic.author_name - - assert_kind_of ActiveSupport::Multibyte::Chars, str.chars - topic = Topic.find_by_author_name(str.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 - - def test_class_level_destroy - should_be_destroyed_reply = Reply.create("title" => "hello", "content" => "world") - Topic.find(1).replies << should_be_destroyed_reply - - Topic.destroy(1) - assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1) } - assert_raise(ActiveRecord::RecordNotFound) { Reply.find(should_be_destroyed_reply.id) } - end - - def test_class_level_delete - should_be_destroyed_reply = Reply.create("title" => "hello", "content" => "world") - Topic.find(1).replies << should_be_destroyed_reply - - Topic.delete(1) - assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1) } - assert_nothing_raised { Reply.find(should_be_destroyed_reply.id) } - end - - def test_increment_attribute - assert_equal 50, accounts(:signals37).credit_limit - accounts(:signals37).increment! :credit_limit - assert_equal 51, accounts(:signals37, :reload).credit_limit - - accounts(:signals37).increment(:credit_limit).increment!(:credit_limit) - assert_equal 53, accounts(:signals37, :reload).credit_limit - end - - def test_increment_nil_attribute - assert_nil topics(:first).parent_id - topics(:first).increment! :parent_id - assert_equal 1, topics(:first).parent_id - end - - def test_increment_attribute_by - assert_equal 50, accounts(:signals37).credit_limit - accounts(:signals37).increment! :credit_limit, 5 - assert_equal 55, accounts(:signals37, :reload).credit_limit - - accounts(:signals37).increment(:credit_limit, 1).increment!(:credit_limit, 3) - assert_equal 59, accounts(:signals37, :reload).credit_limit - end - - def test_decrement_attribute - assert_equal 50, accounts(:signals37).credit_limit - - accounts(:signals37).decrement!(:credit_limit) - assert_equal 49, accounts(:signals37, :reload).credit_limit - - accounts(:signals37).decrement(:credit_limit).decrement!(:credit_limit) - assert_equal 47, accounts(:signals37, :reload).credit_limit - end - - def test_decrement_attribute_by - assert_equal 50, accounts(:signals37).credit_limit - accounts(:signals37).decrement! :credit_limit, 5 - assert_equal 45, accounts(:signals37, :reload).credit_limit - - accounts(:signals37).decrement(:credit_limit, 1).decrement!(:credit_limit, 3) - assert_equal 41, accounts(:signals37, :reload).credit_limit - end - - def test_toggle_attribute - assert !topics(:first).approved? - topics(:first).toggle!(:approved) - assert topics(:first).approved? - topic = topics(:first) - topic.toggle(:approved) - assert !topic.approved? - topic.reload - assert topic.approved? - end - - def test_reload - t1 = Topic.find(1) - t2 = Topic.find(1) - t1.title = "something else" - t1.save - t2.reload - assert_equal t1.title, t2.title - 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_define_attr_method_with_block - k = Class.new( ActiveRecord::Base ) - k.send(:define_attr_method, :primary_key) { "sys_" + original_primary_key } - assert_equal "sys_id", k.primary_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 - 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 - k = Class.new( ActiveRecord::Base ) - k.set_primary_key { "sys_" + original_primary_key } - assert_equal "sys_id", k.primary_key - end - - def test_set_inheritance_column_with_value - k = Class.new( ActiveRecord::Base ) - k.inheritance_column = "foo" - assert_equal "foo", k.inheritance_column - k.set_inheritance_column "bar" - assert_equal "bar", k.inheritance_column - end - - def test_set_inheritance_column_with_block - k = Class.new( ActiveRecord::Base ) - k.set_inheritance_column { original_inheritance_column + "_id" } - assert_equal "type_id", k.inheritance_column - 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") - 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") - 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") - end - - assert_equal res4, res5 - - unless current_adapter?(:SQLite2Adapter, :DeprecatedSQLiteAdapter) - 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) - end - assert_equal res6, res7 - end - end - - def test_clear_association_cache_stored - firm = Firm.find(1) - assert_kind_of Firm, firm - - firm.clear_association_cache - assert_equal Firm.find(1).clients.collect{ |x| x.name }.sort, firm.clients.collect{ |x| x.name }.sort - end - - def test_clear_association_cache_new_record - firm = Firm.new - client_stored = Client.find(3) - client_new = Client.new - client_new.name = "The Joneses" - clients = [ client_stored, client_new ] - - firm.clients << clients - assert_equal clients.map(&:name).to_set, firm.clients.map(&:name).to_set - - firm.clear_association_cache - assert_equal clients.map(&:name).to_set, firm.clients.map(&:name).to_set - end - - def test_interpolate_sql - assert_nothing_raised { Category.new.send(:interpolate_sql, 'foo@bar') } - assert_nothing_raised { Category.new.send(:interpolate_sql, 'foo bar) baz') } - assert_nothing_raised { Category.new.send(:interpolate_sql, 'foo bar} baz') } - end - - def test_scoped_find_conditions - scoped_developers = Developer.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_scoped_find_limit_offset - scoped_developers = Developer.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.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.with_scope(:find => { :limit => 1 }) do - Developer.find(:all, :order => 'salary DESC') - end - # Test scope order + find order, find has priority - scoped_developers = Developer.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(:dev_10)) - # Test without scoped find conditions to ensure we get the right thing - developers = Developer.find(:all, :order => 'id', :limit => 1) - assert scoped_developers.include?(developers(:david)) - end - - def test_scoped_find_limit_offset_including_has_many_association - topics = Topic.with_scope(:find => {:limit => 1, :offset => 1, :include => :replies}) do - Topic.find(:all, :order => "topics.id") - end - assert_equal 1, topics.size - assert_equal 2, topics.first.id - end - - def test_scoped_find_order_including_has_many_association - developers = Developer.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_abstract_class - assert !ActiveRecord::Base.abstract_class? - assert LoosePerson.abstract_class? - assert !LooseDescendant.abstract_class? - end - - def test_base_class - assert_equal LoosePerson, LoosePerson.base_class - assert_equal LooseDescendant, LooseDescendant.base_class - assert_equal TightPerson, TightPerson.base_class - assert_equal TightPerson, TightDescendant.base_class - - assert_equal Post, Post.base_class - assert_equal Post, SpecialPost.base_class - assert_equal Post, StiPost.base_class - assert_equal SubStiPost, SubStiPost.base_class - end - - def test_descends_from_active_record - # Tries to call Object.abstract_class? - assert_raise(NoMethodError) do - ActiveRecord::Base.descends_from_active_record? - end - - # Abstract subclass of AR::Base. - assert LoosePerson.descends_from_active_record? - - # Concrete subclass of an abstract class. - assert LooseDescendant.descends_from_active_record? - - # Concrete subclass of AR::Base. - assert TightPerson.descends_from_active_record? - - # Concrete subclass of a concrete class but has no type column. - assert TightDescendant.descends_from_active_record? - - # Concrete subclass of AR::Base. - assert Post.descends_from_active_record? - - # Abstract subclass of a concrete class which has a type column. - # This is pathological, as you'll never have Sub < Abstract < Concrete. - assert !StiPost.descends_from_active_record? - - # Concrete subclasses an abstract class which has a type column. - assert !SubStiPost.descends_from_active_record? - end - - def test_find_on_abstract_base_class_doesnt_use_type_condition - old_class = LooseDescendant - Object.send :remove_const, :LooseDescendant - - descendant = old_class.create! - assert_not_nil LoosePerson.find(descendant.id), "Should have found instance of LooseDescendant when finding abstract LoosePerson: #{descendant.inspect}" - ensure - unless Object.const_defined?(:LooseDescendant) - Object.const_set :LooseDescendant, old_class - end - end - - def test_assert_queries - query = lambda { ActiveRecord::Base.connection.execute 'select count(*) from developers' } - assert_queries(2) { 2.times { query.call } } - assert_queries 1, &query - assert_no_queries { assert true } - end - - def test_to_xml - xml = REXML::Document.new(topics(:first).to_xml(:indent => 0)) - bonus_time_in_current_timezone = topics(:first).bonus_time.xmlschema - written_on_in_current_timezone = topics(:first).written_on.xmlschema - last_read_in_current_timezone = topics(:first).last_read.xmlschema - - assert_equal "topic", xml.root.name - assert_equal "The First Topic" , xml.elements["//title"].text - assert_equal "David" , xml.elements["//author-name"].text - - assert_equal "1", xml.elements["//id"].text - assert_equal "integer" , xml.elements["//id"].attributes['type'] - - assert_equal "1", xml.elements["//replies-count"].text - assert_equal "integer" , xml.elements["//replies-count"].attributes['type'] - - assert_equal written_on_in_current_timezone, xml.elements["//written-on"].text - assert_equal "datetime" , xml.elements["//written-on"].attributes['type'] - - assert_equal "--- Have a nice day\n" , xml.elements["//content"].text - assert_equal "yaml" , xml.elements["//content"].attributes['type'] - - assert_equal "david@loudthinking.com", xml.elements["//author-email-address"].text - - assert_equal nil, xml.elements["//parent-id"].text - assert_equal "integer", xml.elements["//parent-id"].attributes['type'] - assert_equal "true", xml.elements["//parent-id"].attributes['nil'] - - if current_adapter?(:SybaseAdapter, :SQLServerAdapter, :OracleAdapter) - assert_equal last_read_in_current_timezone, xml.elements["//last-read"].text - assert_equal "datetime" , xml.elements["//last-read"].attributes['type'] - else - assert_equal "2004-04-15", xml.elements["//last-read"].text - assert_equal "date" , xml.elements["//last-read"].attributes['type'] - end - - # Oracle and DB2 don't have true boolean or time-only fields - unless current_adapter?(:OracleAdapter, :DB2Adapter) - assert_equal "false", xml.elements["//approved"].text - assert_equal "boolean" , xml.elements["//approved"].attributes['type'] - - assert_equal bonus_time_in_current_timezone, xml.elements["//bonus-time"].text - assert_equal "datetime" , xml.elements["//bonus-time"].attributes['type'] - end - end - - def test_to_xml_skipping_attributes - xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :except => [:title, :replies_count]) - assert_equal "", xml.first(7) - assert !xml.include?(%(The First Topic)) - assert xml.include?(%(David)) - - xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :except => [:title, :author_name, :replies_count]) - assert !xml.include?(%(The First Topic)) - assert !xml.include?(%(David)) - end - - def test_to_xml_including_has_many_association - xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :include => :replies, :except => :replies_count) - assert_equal "", xml.first(7) - assert xml.include?(%()) - assert xml.include?(%(The Second Topic's of the day)) - end - - def test_array_to_xml_including_has_many_association - xml = [ topics(:first), topics(:second) ].to_xml(:indent => 0, :skip_instruct => true, :include => :replies) - assert xml.include?(%()) - end - - def test_array_to_xml_including_methods - xml = [ topics(:first), topics(:second) ].to_xml(:indent => 0, :skip_instruct => true, :methods => [ :topic_id ]) - assert xml.include?(%(#{topics(:first).topic_id})), xml - assert xml.include?(%(#{topics(:second).topic_id})), xml - end - - def test_array_to_xml_including_has_one_association - xml = [ companies(:first_firm), companies(:rails_core) ].to_xml(:indent => 0, :skip_instruct => true, :include => :account) - assert xml.include?(companies(:first_firm).account.to_xml(:indent => 0, :skip_instruct => true)) - assert xml.include?(companies(:rails_core).account.to_xml(:indent => 0, :skip_instruct => true)) - end - - def test_array_to_xml_including_belongs_to_association - xml = [ companies(:first_client), companies(:second_client), companies(:another_client) ].to_xml(:indent => 0, :skip_instruct => true, :include => :firm) - assert xml.include?(companies(:first_client).to_xml(:indent => 0, :skip_instruct => true)) - assert xml.include?(companies(:second_client).firm.to_xml(:indent => 0, :skip_instruct => true)) - assert xml.include?(companies(:another_client).firm.to_xml(:indent => 0, :skip_instruct => true)) - end - - def test_to_xml_including_belongs_to_association - xml = companies(:first_client).to_xml(:indent => 0, :skip_instruct => true, :include => :firm) - assert !xml.include?("") - - xml = companies(:second_client).to_xml(:indent => 0, :skip_instruct => true, :include => :firm) - assert xml.include?("") - end - - def test_to_xml_including_multiple_associations - xml = companies(:first_firm).to_xml(:indent => 0, :skip_instruct => true, :include => [ :clients, :account ]) - assert_equal "", xml.first(6) - assert xml.include?(%()) - assert xml.include?(%()) - end - - def test_to_xml_including_multiple_associations_with_options - xml = companies(:first_firm).to_xml( - :indent => 0, :skip_instruct => true, - :include => { :clients => { :only => :name } } - ) - - assert_equal "", xml.first(6) - assert xml.include?(%(Summit)) - assert xml.include?(%()) - end - - def test_to_xml_including_methods - xml = Company.new.to_xml(:methods => :arbitrary_method, :skip_instruct => true) - assert_equal "", xml.first(9) - assert xml.include?(%(I am Jack's profound disappointment)) - end - - def test_to_xml_with_block - value = "Rockin' the block" - xml = Company.new.to_xml(:skip_instruct => true) do |xml| - xml.tag! "arbitrary-element", value - end - assert_equal "", xml.first(9) - assert xml.include?(%(#{value})) - end - - def test_except_attributes - assert_equal( - %w( author_name type id approved replies_count bonus_time written_on content author_email_address parent_id last_read).sort, - topics(:first).attributes(:except => :title).keys.sort - ) - - assert_equal( - %w( replies_count bonus_time written_on content author_email_address parent_id last_read).sort, - topics(:first).attributes(:except => [ :title, :id, :type, :approved, :author_name ]).keys.sort - ) - end - - def test_include_attributes - assert_equal(%w( title ), topics(:first).attributes(:only => :title).keys) - assert_equal(%w( title author_name type id approved ).sort, topics(:first).attributes(:only => [ :title, :id, :type, :approved, :author_name ]).keys.sort) - end - - def test_type_name_with_module_should_handle_beginning - assert_equal 'ActiveRecord::Person', ActiveRecord::Base.send(:type_name_with_module, 'Person') - assert_equal '::Person', ActiveRecord::Base.send(:type_name_with_module, '::Person') - end - - def test_to_param_should_return_string - assert_kind_of String, Client.find(:first).to_param - end - - def test_inspect_class - assert_equal 'ActiveRecord::Base', ActiveRecord::Base.inspect - assert_equal 'LoosePerson(abstract)', LoosePerson.inspect - assert_match(/^Topic\(id: integer, title: string/, Topic.inspect) - end - - def test_inspect_instance - topic = topics(:first) - assert_equal %(#), topic.inspect - end - - def test_inspect_new_instance - assert_match /Topic id: nil/, Topic.new.inspect - end - - def test_inspect_limited_select_instance - assert_equal %(#), Topic.find(:first, :select => 'id', :conditions => 'id = 1').inspect - assert_equal %(#), Topic.find(:first, :select => 'id, title', :conditions => 'id = 1').inspect - end - - def test_inspect_class_without_table - assert_equal "NonExistentTable(Table doesn't exist)", NonExistentTable.inspect - end - - def test_attribute_for_inspect - t = topics(:first) - t.title = "The First Topic Now Has A Title With\nNewlines And More Than 50 Characters" - - assert_equal %("#{t.written_on.to_s(:db)}"), t.attribute_for_inspect(:written_on) - assert_equal '"The First Topic Now Has A Title With\nNewlines And M..."', t.attribute_for_inspect(:title) - end - - def test_becomes - assert_kind_of Reply, topics(:first).becomes(Reply) - assert_equal "The First Topic", topics(:first).becomes(Reply).title - 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.level = Logger::DEBUG - ActiveRecord::Base.silence do - ActiveRecord::Base.logger.warn "warn" - ActiveRecord::Base.logger.error "error" - end - assert_equal "error\n", log.string - ensure - ActiveRecord::Base.logger = original_logger - end - - 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.level = Logger::WARN - ActiveRecord::Base.silence do - end - assert_equal Logger::WARN, ActiveRecord::Base.logger.level - ensure - ActiveRecord::Base.logger = original_logger - end - - def test_benchmark_with_log_level - original_logger = ActiveRecord::Base.logger - log = StringIO.new - ActiveRecord::Base.logger = Logger.new(log) - ActiveRecord::Base.logger.level = Logger::WARN - ActiveRecord::Base.benchmark("Debug Topic Count", Logger::DEBUG) { Topic.count } - ActiveRecord::Base.benchmark("Warn Topic Count", Logger::WARN) { Topic.count } - ActiveRecord::Base.benchmark("Error Topic Count", Logger::ERROR) { Topic.count } - assert_no_match /Debug Topic Count/, log.string - assert_match /Warn Topic Count/, log.string - assert_match /Error Topic Count/, log.string - ensure - ActiveRecord::Base.logger = original_logger - end - - def test_benchmark_with_use_silence - original_logger = ActiveRecord::Base.logger - log = StringIO.new - ActiveRecord::Base.logger = Logger.new(log) - ActiveRecord::Base.benchmark("Logging", Logger::DEBUG, true) { ActiveRecord::Base.logger.debug "Loud" } - ActiveRecord::Base.benchmark("Logging", Logger::DEBUG, false) { ActiveRecord::Base.logger.debug "Quiet" } - assert_no_match /Loud/, log.string - assert_match /Quiet/, log.string - ensure - ActiveRecord::Base.logger = original_logger - end -end diff --git a/activerecord/test/binary_test.rb b/activerecord/test/binary_test.rb deleted file mode 100644 index f89660e4e1..0000000000 --- a/activerecord/test/binary_test.rb +++ /dev/null @@ -1,32 +0,0 @@ -require 'abstract_unit' - -# Without using prepared statements, it makes no sense to test -# BLOB data with SQL Server, because the length of a statement is -# limited to 8KB. -# -# Without using prepared statements, it makes no sense to test -# BLOB data with DB2 or Firebird, because the length of a statement -# is limited to 32KB. -unless current_adapter?(:SQLServerAdapter, :SybaseAdapter, :DB2Adapter, :FirebirdAdapter) - require 'fixtures/binary' - - class BinaryTest < ActiveSupport::TestCase - FIXTURES = %w(flowers.jpg example.log) - - def test_load_save - Binary.delete_all - - FIXTURES.each do |filename| - data = File.read("#{File.dirname(__FILE__)}/fixtures/#{filename}").freeze - - bin = Binary.new(:data => data) - assert_equal data, bin.data, 'Newly assigned data differs from original' - - bin.save! - assert_equal data, bin.data, 'Data differs from original after save' - - assert_equal data, bin.reload.data, 'Reloaded data differs from original' - end - end - end -end diff --git a/activerecord/test/calculations_test.rb b/activerecord/test/calculations_test.rb deleted file mode 100644 index bce84478b0..0000000000 --- a/activerecord/test/calculations_test.rb +++ /dev/null @@ -1,251 +0,0 @@ -require 'abstract_unit' -require 'fixtures/company' -require 'fixtures/topic' - -Company.has_many :accounts - -class NumericData < ActiveRecord::Base - self.table_name = 'numeric_data' -end - -class CalculationsTest < ActiveSupport::TestCase - fixtures :companies, :accounts, :topics - - def test_should_sum_field - assert_equal 318, Account.sum(:credit_limit) - end - - def test_should_average_field - value = Account.average(:credit_limit) - assert_kind_of Float, value - assert_in_delta 53.0, value, 0.001 - end - - def test_should_return_nil_as_average - assert_nil NumericData.average(:bank_balance) - end - - def test_should_get_maximum_of_field - assert_equal 60, Account.maximum(:credit_limit) - end - - def test_should_get_maximum_of_field_with_include - assert_equal 50, Account.maximum(:credit_limit, :include => :firm, :conditions => "companies.name != 'Summit'") - end - - def test_should_get_maximum_of_field_with_scoped_include - Account.with_scope :find => { :include => :firm, :conditions => "companies.name != 'Summit'" } do - assert_equal 50, Account.maximum(:credit_limit) - end - end - - def test_should_get_minimum_of_field - assert_equal 50, Account.minimum(:credit_limit) - end - - def test_should_group_by_field - c = Account.sum(:credit_limit, :group => :firm_id) - [1,6,2].each { |firm_id| assert c.keys.include?(firm_id) } - end - - def test_should_group_by_summed_field - c = Account.sum(:credit_limit, :group => :firm_id) - 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") - 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") - 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) - 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) - assert_equal [2, 6], c.keys.compact - 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_association - c = Account.sum(:credit_limit, :group => :firm) - 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') - end - - def test_should_group_by_summed_field_with_conditions - c = Account.sum(:credit_limit, :conditions => 'firm_id > 1', - :group => :firm_id) - 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') - 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') - assert_equal 50, c[1] - assert_equal 105, c[6] - assert_equal 60, c[2] - end - - def test_should_calculate_with_invalid_field - assert_equal 6, Account.calculate(:count, '*') - assert_equal 6, Account.calculate(:count, :all) - end - - def test_should_calculate_grouped_with_invalid_field - c = Account.count(:all, :group => 'accounts.firm_id') - 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) - assert_equal 1, c[companies(:first_firm)] - assert_equal 2, c[companies(:rails_core)] - assert_equal 1, c[companies(:first_client)] - end - - uses_mocha 'group_by_non_numeric_foreign_key_association' do - def test_should_group_by_association_with_non_numeric_foreign_key - ActiveRecord::Base.connection.expects(:select_all).returns([{"count_all" => 1, "firm_id" => "ABC"}]) - - firm = mock() - firm.expects(:id).returns("ABC") - firm.expects(:class).returns(Firm) - Company.expects(:find).with(["ABC"]).returns([firm]) - - column = mock() - column.expects(:name).at_least_once.returns(:firm_id) - column.expects(:type_cast).with("ABC").returns("ABC") - Account.expects(:columns).at_least_once.returns([column]) - - c = Account.count(:all, :group => :firm) - assert_equal Firm, c.first.first.class - assert_equal 1, c.first.last - end - 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})") - assert_equal 2, c[nil] - assert_equal 1, c['DEPENDENTFIRM'] - assert_equal 3, c['CLIENT'] - assert_equal 2, c['FIRM'] - end - - def test_should_calculate_grouped_by_function_with_table_alias - c = Company.count(:all, :group => "UPPER(companies.#{QUOTED_TYPE})") - assert_equal 2, c[nil] - assert_equal 1, c['DEPENDENTFIRM'] - assert_equal 3, c['CLIENT'] - assert_equal 2, c['FIRM'] - end - - def test_should_not_overshadow_enumerable_sum - assert_equal 6, [1, 2, 3].sum(&:abs) - end - - def test_should_sum_scoped_field - assert_equal 15, companies(:rails_core).companies.sum(:id) - end - - def test_should_sum_scoped_field_with_conditions - assert_equal 8, companies(:rails_core).companies.sum(:id, :conditions => 'id > 7') - end - - def test_should_group_by_scoped_field - c = companies(:rails_core).companies.sum(:id, :group => :name) - assert_equal 7, c['Leetsoft'] - assert_equal 8, c['Jadedpixel'] - end - - def test_should_group_by_summed_field_with_conditions_and_having - c = companies(:rails_core).companies.sum(:id, :group => :name, - :having => 'sum(id) > 7') - assert_nil c['Leetsoft'] - assert_equal 8, c['Jadedpixel'] - end - - def test_should_reject_invalid_options - assert_nothing_raised do - [:count, :sum].each do |func| - # empty options are valid - Company.send(:validate_calculation_options, func) - # these options are valid for all calculations - [:select, :conditions, :joins, :order, :group, :having, :distinct].each do |opt| - Company.send(:validate_calculation_options, func, opt => true) - end - end - - # :include is only valid on :count - Company.send(:validate_calculation_options, :count, :include => true) - end - - assert_raises(ArgumentError) { Company.send(:validate_calculation_options, :sum, :foo => :bar) } - assert_raises(ArgumentError) { Company.send(:validate_calculation_options, :count, :foo => :bar) } - 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) - end - - def test_count_with_column_parameter - assert_equal 5, Account.count(:firm_id) - end - - def test_count_with_column_and_options_parameter - assert_equal 2, Account.count(:firm_id, :conditions => "credit_limit = 50") - end - - def test_count_with_no_parameters_isnt_deprecated - assert_not_deprecated { Account.count } - end - - def test_count_with_too_many_parameters_raises - assert_raise(ArgumentError) { Account.count(1, 2, 3) } - end -end diff --git a/activerecord/test/callbacks_test.rb b/activerecord/test/callbacks_test.rb deleted file mode 100644 index 5461d8e110..0000000000 --- a/activerecord/test/callbacks_test.rb +++ /dev/null @@ -1,400 +0,0 @@ -require 'abstract_unit' - -class CallbackDeveloper < ActiveRecord::Base - set_table_name 'developers' - - class << self - def callback_string(callback_method) - "history << [#{callback_method.to_sym.inspect}, :string]" - end - - def callback_proc(callback_method) - Proc.new { |model| model.history << [callback_method, :proc] } - end - - def define_callback_method(callback_method) - define_method("#{callback_method}_method") do |model| - model.history << [callback_method, :method] - end - end - - def callback_object(callback_method) - klass = Class.new - klass.send(:define_method, callback_method) do |model| - model.history << [callback_method, :object] - end - klass.new - end - end - - ActiveRecord::Callbacks::CALLBACKS.each do |callback_method| - callback_method_sym = callback_method.to_sym - define_callback_method(callback_method_sym) - send(callback_method, callback_method_sym) - send(callback_method, callback_string(callback_method_sym)) - send(callback_method, callback_proc(callback_method_sym)) - send(callback_method, callback_object(callback_method_sym)) - send(callback_method) { |model| model.history << [callback_method_sym, :block] } - end - - def history - @history ||= [] - end - - # after_initialize and after_find are invoked only if instance methods have been defined. - def after_initialize - end - - def after_find - end -end - -class ParentDeveloper < ActiveRecord::Base - set_table_name 'developers' - attr_accessor :after_save_called - before_validation {|record| record.after_save_called = true} -end - -class ChildDeveloper < ParentDeveloper - -end - -class RecursiveCallbackDeveloper < ActiveRecord::Base - set_table_name 'developers' - - before_save :on_before_save - after_save :on_after_save - - attr_reader :on_before_save_called, :on_after_save_called - - def on_before_save - @on_before_save_called ||= 0 - @on_before_save_called += 1 - save unless @on_before_save_called > 1 - end - - def on_after_save - @on_after_save_called ||= 0 - @on_after_save_called += 1 - save unless @on_after_save_called > 1 - end -end - -class ImmutableDeveloper < ActiveRecord::Base - set_table_name 'developers' - - validates_inclusion_of :salary, :in => 50000..200000 - - before_save :cancel - before_destroy :cancel - - def cancelled? - @cancelled == true - end - - private - def cancel - @cancelled = true - false - end -end - -class ImmutableMethodDeveloper < ActiveRecord::Base - set_table_name 'developers' - - validates_inclusion_of :salary, :in => 50000..200000 - - def cancelled? - @cancelled == true - end - - def before_save - @cancelled = true - false - end - - def before_destroy - @cancelled = true - false - end -end - -class CallbackCancellationDeveloper < ActiveRecord::Base - set_table_name 'developers' - def before_create - false - end -end - -class CallbacksTest < ActiveSupport::TestCase - fixtures :developers - - def test_initialize - david = CallbackDeveloper.new - assert_equal [ - [ :after_initialize, :string ], - [ :after_initialize, :proc ], - [ :after_initialize, :object ], - [ :after_initialize, :block ], - ], david.history - end - - def test_find - david = CallbackDeveloper.find(1) - assert_equal [ - [ :after_find, :string ], - [ :after_find, :proc ], - [ :after_find, :object ], - [ :after_find, :block ], - [ :after_initialize, :string ], - [ :after_initialize, :proc ], - [ :after_initialize, :object ], - [ :after_initialize, :block ], - ], david.history - end - - def test_new_valid? - david = CallbackDeveloper.new - david.valid? - assert_equal [ - [ :after_initialize, :string ], - [ :after_initialize, :proc ], - [ :after_initialize, :object ], - [ :after_initialize, :block ], - [ :before_validation, :string ], - [ :before_validation, :proc ], - [ :before_validation, :object ], - [ :before_validation, :block ], - [ :before_validation_on_create, :string ], - [ :before_validation_on_create, :proc ], - [ :before_validation_on_create, :object ], - [ :before_validation_on_create, :block ], - [ :after_validation, :string ], - [ :after_validation, :proc ], - [ :after_validation, :object ], - [ :after_validation, :block ], - [ :after_validation_on_create, :string ], - [ :after_validation_on_create, :proc ], - [ :after_validation_on_create, :object ], - [ :after_validation_on_create, :block ] - ], david.history - end - - def test_existing_valid? - david = CallbackDeveloper.find(1) - david.valid? - assert_equal [ - [ :after_find, :string ], - [ :after_find, :proc ], - [ :after_find, :object ], - [ :after_find, :block ], - [ :after_initialize, :string ], - [ :after_initialize, :proc ], - [ :after_initialize, :object ], - [ :after_initialize, :block ], - [ :before_validation, :string ], - [ :before_validation, :proc ], - [ :before_validation, :object ], - [ :before_validation, :block ], - [ :before_validation_on_update, :string ], - [ :before_validation_on_update, :proc ], - [ :before_validation_on_update, :object ], - [ :before_validation_on_update, :block ], - [ :after_validation, :string ], - [ :after_validation, :proc ], - [ :after_validation, :object ], - [ :after_validation, :block ], - [ :after_validation_on_update, :string ], - [ :after_validation_on_update, :proc ], - [ :after_validation_on_update, :object ], - [ :after_validation_on_update, :block ] - ], david.history - end - - def test_create - david = CallbackDeveloper.create('name' => 'David', 'salary' => 1000000) - assert_equal [ - [ :after_initialize, :string ], - [ :after_initialize, :proc ], - [ :after_initialize, :object ], - [ :after_initialize, :block ], - [ :before_validation, :string ], - [ :before_validation, :proc ], - [ :before_validation, :object ], - [ :before_validation, :block ], - [ :before_validation_on_create, :string ], - [ :before_validation_on_create, :proc ], - [ :before_validation_on_create, :object ], - [ :before_validation_on_create, :block ], - [ :after_validation, :string ], - [ :after_validation, :proc ], - [ :after_validation, :object ], - [ :after_validation, :block ], - [ :after_validation_on_create, :string ], - [ :after_validation_on_create, :proc ], - [ :after_validation_on_create, :object ], - [ :after_validation_on_create, :block ], - [ :before_save, :string ], - [ :before_save, :proc ], - [ :before_save, :object ], - [ :before_save, :block ], - [ :before_create, :string ], - [ :before_create, :proc ], - [ :before_create, :object ], - [ :before_create, :block ], - [ :after_create, :string ], - [ :after_create, :proc ], - [ :after_create, :object ], - [ :after_create, :block ], - [ :after_save, :string ], - [ :after_save, :proc ], - [ :after_save, :object ], - [ :after_save, :block ] - ], david.history - end - - def test_save - david = CallbackDeveloper.find(1) - david.save - assert_equal [ - [ :after_find, :string ], - [ :after_find, :proc ], - [ :after_find, :object ], - [ :after_find, :block ], - [ :after_initialize, :string ], - [ :after_initialize, :proc ], - [ :after_initialize, :object ], - [ :after_initialize, :block ], - [ :before_validation, :string ], - [ :before_validation, :proc ], - [ :before_validation, :object ], - [ :before_validation, :block ], - [ :before_validation_on_update, :string ], - [ :before_validation_on_update, :proc ], - [ :before_validation_on_update, :object ], - [ :before_validation_on_update, :block ], - [ :after_validation, :string ], - [ :after_validation, :proc ], - [ :after_validation, :object ], - [ :after_validation, :block ], - [ :after_validation_on_update, :string ], - [ :after_validation_on_update, :proc ], - [ :after_validation_on_update, :object ], - [ :after_validation_on_update, :block ], - [ :before_save, :string ], - [ :before_save, :proc ], - [ :before_save, :object ], - [ :before_save, :block ], - [ :before_update, :string ], - [ :before_update, :proc ], - [ :before_update, :object ], - [ :before_update, :block ], - [ :after_update, :string ], - [ :after_update, :proc ], - [ :after_update, :object ], - [ :after_update, :block ], - [ :after_save, :string ], - [ :after_save, :proc ], - [ :after_save, :object ], - [ :after_save, :block ] - ], david.history - end - - def test_destroy - david = CallbackDeveloper.find(1) - david.destroy - assert_equal [ - [ :after_find, :string ], - [ :after_find, :proc ], - [ :after_find, :object ], - [ :after_find, :block ], - [ :after_initialize, :string ], - [ :after_initialize, :proc ], - [ :after_initialize, :object ], - [ :after_initialize, :block ], - [ :before_destroy, :string ], - [ :before_destroy, :proc ], - [ :before_destroy, :object ], - [ :before_destroy, :block ], - [ :after_destroy, :string ], - [ :after_destroy, :proc ], - [ :after_destroy, :object ], - [ :after_destroy, :block ] - ], david.history - end - - def test_delete - david = CallbackDeveloper.find(1) - CallbackDeveloper.delete(david.id) - assert_equal [ - [ :after_find, :string ], - [ :after_find, :proc ], - [ :after_find, :object ], - [ :after_find, :block ], - [ :after_initialize, :string ], - [ :after_initialize, :proc ], - [ :after_initialize, :object ], - [ :after_initialize, :block ], - ], david.history - end - - def test_before_save_returning_false - david = ImmutableDeveloper.find(1) - assert david.valid? - assert !david.save - assert_raises(ActiveRecord::RecordNotSaved) { david.save! } - - david = ImmutableDeveloper.find(1) - david.salary = 10_000_000 - assert !david.valid? - assert !david.save - assert_raises(ActiveRecord::RecordInvalid) { david.save! } - end - - def test_before_create_returning_false - someone = CallbackCancellationDeveloper.new - assert someone.valid? - assert !someone.save - end - - def test_before_destroy_returning_false - david = ImmutableDeveloper.find(1) - assert !david.destroy - assert_not_nil ImmutableDeveloper.find_by_id(1) - end - - def test_zzz_callback_returning_false # must be run last since we modify CallbackDeveloper - david = CallbackDeveloper.find(1) - CallbackDeveloper.before_validation proc { |model| model.history << [:before_validation, :returning_false]; return false } - CallbackDeveloper.before_validation proc { |model| model.history << [:before_validation, :should_never_get_here] } - david.save - assert_equal [ - [ :after_find, :string ], - [ :after_find, :proc ], - [ :after_find, :object ], - [ :after_find, :block ], - [ :after_initialize, :string ], - [ :after_initialize, :proc ], - [ :after_initialize, :object ], - [ :after_initialize, :block ], - [ :before_validation, :string ], - [ :before_validation, :proc ], - [ :before_validation, :object ], - [ :before_validation, :block ], - [ :before_validation, :returning_false ] - ], david.history - end - - def test_inheritence_of_callbacks - parent = ParentDeveloper.new - assert !parent.after_save_called - parent.save - assert parent.after_save_called - - child = ChildDeveloper.new - assert !child.after_save_called - child.save - assert child.after_save_called - end - -end diff --git a/activerecord/test/cases/aaa_create_tables_test.rb b/activerecord/test/cases/aaa_create_tables_test.rb new file mode 100644 index 0000000000..56c9cabac1 --- /dev/null +++ b/activerecord/test/cases/aaa_create_tables_test.rb @@ -0,0 +1,72 @@ +# The filename begins with "aaa" to ensure this is the first test. +require 'abstract_unit' + +class AAACreateTablesTest < ActiveSupport::TestCase + self.use_transactional_fixtures = false + + def setup + @base_path = "#{File.dirname(__FILE__)}/fixtures/db_definitions" + end + + def test_drop_and_create_main_tables + recreate ActiveRecord::Base unless use_migrations? + assert true + end + + def test_load_schema + if ActiveRecord::Base.connection.supports_migrations? + eval(File.read("#{File.dirname(__FILE__)}/fixtures/db_definitions/schema.rb")) + else + recreate ActiveRecord::Base, '3' + end + assert true + end + + def test_drop_and_create_courses_table + if Course.connection.supports_migrations? + eval(File.read("#{File.dirname(__FILE__)}/fixtures/db_definitions/schema2.rb")) + end + recreate Course, '2' unless use_migrations_for_courses? + assert true + end + + private + def use_migrations? + unittest_sql_filename = ActiveRecord::Base.connection.adapter_name.downcase + ".sql" + not File.exist? "#{@base_path}/#{unittest_sql_filename}" + end + + def use_migrations_for_courses? + unittest2_sql_filename = ActiveRecord::Base.connection.adapter_name.downcase + "2.sql" + not File.exist? "#{@base_path}/#{unittest2_sql_filename}" + end + + def recreate(base, suffix = nil) + connection = base.connection + adapter_name = connection.adapter_name.downcase + suffix.to_s + execute_sql_file "#{@base_path}/#{adapter_name}.drop.sql", connection + execute_sql_file "#{@base_path}/#{adapter_name}.sql", connection + end + + def execute_sql_file(path, connection) + # OpenBase has a different format for sql files + if current_adapter?(:OpenBaseAdapter) then + File.read(path).split("go").each_with_index do |sql, i| + begin + # OpenBase does not support comments embedded in sql + connection.execute(sql,"SQL statement ##{i}") unless sql.blank? + rescue ActiveRecord::StatementInvalid + #$stderr.puts "warning: #{$!}" + end + end + else + File.read(path).split(';').each_with_index do |sql, i| + begin + connection.execute("\n\n-- statement ##{i}\n#{sql}\n") unless sql.blank? + rescue ActiveRecord::StatementInvalid + #$stderr.puts "warning: #{$!}" + end + end + end + end +end diff --git a/activerecord/test/cases/active_schema_test_mysql.rb b/activerecord/test/cases/active_schema_test_mysql.rb new file mode 100644 index 0000000000..2611153fda --- /dev/null +++ b/activerecord/test/cases/active_schema_test_mysql.rb @@ -0,0 +1,46 @@ +require 'abstract_unit' + +class ActiveSchemaTest < ActiveSupport::TestCase + def setup + ActiveRecord::ConnectionAdapters::MysqlAdapter.class_eval do + alias_method :execute_without_stub, :execute + def execute(sql, name = nil) return sql end + end + end + + def teardown + ActiveRecord::ConnectionAdapters::MysqlAdapter.class_eval do + remove_method :execute + alias_method :execute, :execute_without_stub + end + end + + def test_drop_table + assert_equal "DROP TABLE `people`", drop_table(:people) + end + + if current_adapter?(:MysqlAdapter) + def test_create_mysql_database_with_encoding + assert_equal "CREATE DATABASE `matt` DEFAULT CHARACTER SET `utf8`", create_database(:matt) + assert_equal "CREATE DATABASE `aimonetti` DEFAULT CHARACTER SET `latin1`", create_database(:aimonetti, {:charset => 'latin1'}) + assert_equal "CREATE DATABASE `matt_aimonetti` DEFAULT CHARACTER SET `big5` COLLATE `big5_chinese_ci`", create_database(:matt_aimonetti, {:charset => :big5, :collation => :big5_chinese_ci}) + end + end + + def test_add_column + assert_equal "ALTER TABLE `people` ADD `last_name` varchar(255)", add_column(:people, :last_name, :string) + end + + def test_add_column_with_limit + assert_equal "ALTER TABLE `people` ADD `key` varchar(32)", add_column(:people, :key, :string, :limit => 32) + end + + def test_drop_table_with_specific_database + assert_equal "DROP TABLE `otherdb`.`people`", drop_table('otherdb.people') + end + + private + def method_missing(method_symbol, *arguments) + ActiveRecord::Base.connection.send(method_symbol, *arguments) + end +end diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb new file mode 100644 index 0000000000..d93a99d7cd --- /dev/null +++ b/activerecord/test/cases/adapter_test.rb @@ -0,0 +1,106 @@ +require 'abstract_unit' + +class AdapterTest < ActiveSupport::TestCase + def setup + @connection = ActiveRecord::Base.connection + end + + def test_tables + if @connection.respond_to?(:tables) + tables = @connection.tables + assert tables.include?("accounts") + assert tables.include?("authors") + assert tables.include?("tasks") + assert tables.include?("topics") + else + warn "#{@connection.class} does not respond to #tables" + end + 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 + + ensure + @connection.remove_index(:accounts, :name => idx_name) rescue nil + end + + def test_current_database + if @connection.respond_to?(:current_database) + assert_equal ENV['ARUNIT_DB_NAME'] || "activerecord_unittest", @connection.current_database + 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 + 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_show_nonexistent_variable_returns_nil + assert_nil @connection.show_variable('foo_bar_baz') + 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 + + 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 + end + end + + # test resetting sequences in odd tables in postgreSQL + if ActiveRecord::Base.connection.respond_to?(:reset_pk_sequence!) + require 'fixtures/movie' + require 'fixtures/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 + + 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 diff --git a/activerecord/test/cases/adapter_test_sqlserver.rb b/activerecord/test/cases/adapter_test_sqlserver.rb new file mode 100644 index 0000000000..494fc98a2f --- /dev/null +++ b/activerecord/test/cases/adapter_test_sqlserver.rb @@ -0,0 +1,95 @@ +require 'abstract_unit' +require 'fixtures/default' +require 'fixtures/post' +require 'fixtures/task' + +class SqlServerAdapterTest < ActiveSupport::TestCase + class TableWithRealColumn < ActiveRecord::Base; end + + fixtures :posts, :tasks + + def setup + @connection = ActiveRecord::Base.connection + end + + def teardown + @connection.execute("SET LANGUAGE us_english") rescue nil + end + + def test_real_column_has_float_type + assert_equal :float, TableWithRealColumn.columns_hash["real_number"].type + end + + # SQL Server 2000 has a bug where some unambiguous date formats are not + # correctly identified if the session language is set to german + def test_date_insertion_when_language_is_german + @connection.execute("SET LANGUAGE deutsch") + + assert_nothing_raised do + Task.create(:starting => Time.utc(2000, 1, 31, 5, 42, 0), :ending => Date.new(2006, 12, 31)) + end + end + + def test_indexes_with_descending_order + # Make sure we have an index with descending order + @connection.execute "CREATE INDEX idx_credit_limit ON accounts (credit_limit DESC)" rescue nil + assert_equal ["credit_limit"], @connection.indexes('accounts').first.columns + ensure + @connection.execute "DROP INDEX accounts.idx_credit_limit" + end + + def test_execute_without_block_closes_statement + assert_all_statements_used_are_closed do + @connection.execute("SELECT 1") + end + end + + def test_execute_with_block_closes_statement + assert_all_statements_used_are_closed do + @connection.execute("SELECT 1") do |sth| + assert !sth.finished?, "Statement should still be alive within block" + end + end + end + + def test_insert_with_identity_closes_statement + assert_all_statements_used_are_closed do + @connection.insert("INSERT INTO accounts ([id], [firm_id],[credit_limit]) values (999, 1, 50)") + end + end + + def test_insert_without_identity_closes_statement + assert_all_statements_used_are_closed do + @connection.insert("INSERT INTO accounts ([firm_id],[credit_limit]) values (1, 50)") + end + end + + def test_active_closes_statement + assert_all_statements_used_are_closed do + @connection.active? + end + end + + def assert_all_statements_used_are_closed(&block) + existing_handles = [] + ObjectSpace.each_object(DBI::StatementHandle) {|handle| existing_handles << handle} + GC.disable + + yield + + used_handles = [] + ObjectSpace.each_object(DBI::StatementHandle) {|handle| used_handles << handle unless existing_handles.include? handle} + + assert_block "No statements were used within given block" do + used_handles.size > 0 + end + + ObjectSpace.each_object(DBI::StatementHandle) do |handle| + assert_block "Statement should have been closed within given block" do + handle.finished? + end + end + ensure + GC.enable + end +end diff --git a/activerecord/test/cases/aggregations_test.rb b/activerecord/test/cases/aggregations_test.rb new file mode 100644 index 0000000000..66fa46b3f0 --- /dev/null +++ b/activerecord/test/cases/aggregations_test.rb @@ -0,0 +1,128 @@ +require 'abstract_unit' +require 'models/customer' + +class AggregationsTest < ActiveSupport::TestCase + fixtures :customers + + def test_find_single_value_object + assert_equal 50, customers(:david).balance.amount + assert_kind_of Money, customers(:david).balance + assert_equal 300, customers(:david).balance.exchange_to("DKK").amount + end + + def test_find_multiple_value_object + assert_equal customers(:david).address_street, customers(:david).address.street + assert( + customers(:david).address.close_to?(Address.new("Different Street", customers(:david).address_city, customers(:david).address_country)) + ) + end + + def test_change_single_value_object + customers(:david).balance = Money.new(100) + customers(:david).save + assert_equal 100, customers(:david).reload.balance.amount + end + + def test_immutable_value_objects + customers(:david).balance = Money.new(100) + assert_raise(ActiveSupport::FrozenObjectError) { customers(:david).balance.instance_eval { @amount = 20 } } + end + + def test_inferred_mapping + assert_equal "35.544623640962634", customers(:david).gps_location.latitude + assert_equal "-105.9309951055148", customers(:david).gps_location.longitude + + customers(:david).gps_location = GpsLocation.new("39x-110") + + assert_equal "39", customers(:david).gps_location.latitude + assert_equal "-110", customers(:david).gps_location.longitude + + customers(:david).save + + customers(:david).reload + + assert_equal "39", customers(:david).gps_location.latitude + assert_equal "-110", customers(:david).gps_location.longitude + end + + def test_reloaded_instance_refreshes_aggregations + assert_equal "35.544623640962634", customers(:david).gps_location.latitude + assert_equal "-105.9309951055148", customers(:david).gps_location.longitude + + Customer.update_all("gps_location = '24x113'") + customers(:david).reload + assert_equal '24x113', customers(:david)['gps_location'] + + assert_equal GpsLocation.new('24x113'), customers(:david).gps_location + end + + def test_gps_equality + assert GpsLocation.new('39x110') == GpsLocation.new('39x110') + end + + def test_gps_inequality + assert GpsLocation.new('39x110') != GpsLocation.new('39x111') + end + + def test_allow_nil_gps_is_nil + assert_equal nil, customers(:zaphod).gps_location + end + + def test_allow_nil_gps_set_to_nil + customers(:david).gps_location = nil + customers(:david).save + customers(:david).reload + assert_equal nil, customers(:david).gps_location + end + + def test_allow_nil_set_address_attributes_to_nil + customers(:zaphod).address = nil + assert_equal nil, customers(:zaphod).attributes[:address_street] + assert_equal nil, customers(:zaphod).attributes[:address_city] + assert_equal nil, customers(:zaphod).attributes[:address_country] + end + + def test_allow_nil_address_set_to_nil + customers(:zaphod).address = nil + customers(:zaphod).save + customers(:zaphod).reload + assert_equal nil, customers(:zaphod).address + end + + def test_nil_raises_error_when_allow_nil_is_false + assert_raise(NoMethodError) { customers(:david).balance = nil } + end + + def test_allow_nil_address_loaded_when_only_some_attributes_are_nil + customers(:zaphod).address_street = nil + customers(:zaphod).save + customers(:zaphod).reload + assert_kind_of Address, customers(:zaphod).address + assert customers(:zaphod).address.street.nil? + end + + def test_nil_assignment_results_in_nil + customers(:david).gps_location = GpsLocation.new('39x111') + assert_not_equal nil, customers(:david).gps_location + customers(:david).gps_location = nil + assert_equal nil, customers(:david).gps_location + end +end + +class OverridingAggregationsTest < ActiveSupport::TestCase + class Name; end + class DifferentName; end + + class Person < ActiveRecord::Base + composed_of :composed_of, :mapping => %w(person_first_name first_name) + end + + class DifferentPerson < Person + composed_of :composed_of, :class_name => 'DifferentName', :mapping => %w(different_person_first_name first_name) + end + + def test_composed_of_aggregation_redefinition_reflections_should_differ_and_not_inherited + assert_not_equal Person.reflect_on_aggregation(:composed_of), + DifferentPerson.reflect_on_aggregation(:composed_of) + end +end diff --git a/activerecord/test/cases/ar_schema_test.rb b/activerecord/test/cases/ar_schema_test.rb new file mode 100644 index 0000000000..3b290333e9 --- /dev/null +++ b/activerecord/test/cases/ar_schema_test.rb @@ -0,0 +1,33 @@ +require 'abstract_unit' +require 'active_record/schema' + +if ActiveRecord::Base.connection.supports_migrations? + + class ActiveRecordSchemaTest < ActiveSupport::TestCase + self.use_transactional_fixtures = false + + def setup + @connection = ActiveRecord::Base.connection + end + + def teardown + @connection.drop_table :fruits rescue nil + end + + def test_schema_define + ActiveRecord::Schema.define(:version => 7) do + create_table :fruits do |t| + t.column :color, :string + t.column :fruit_size, :string # NOTE: "size" is reserved in Oracle + t.column :texture, :string + t.column :flavor, :string + end + end + + assert_nothing_raised { @connection.select_all "SELECT * FROM fruits" } + assert_nothing_raised { @connection.select_all "SELECT * FROM schema_info" } + assert_equal 7, @connection.select_one("SELECT version FROM schema_info")['version'].to_i + end + end + +end diff --git a/activerecord/test/cases/associations/callbacks_test.rb b/activerecord/test/cases/associations/callbacks_test.rb new file mode 100644 index 0000000000..0b510df8d9 --- /dev/null +++ b/activerecord/test/cases/associations/callbacks_test.rb @@ -0,0 +1,146 @@ +require 'abstract_unit' +require 'models/post' +require 'models/comment' +require 'models/author' +require 'models/category' +require 'models/project' +require 'models/developer' + +class AssociationCallbacksTest < ActiveSupport::TestCase + fixtures :posts, :authors, :projects, :developers + + def setup + @david = authors(:david) + @thinking = posts(:thinking) + @authorless = posts(:authorless) + assert @david.post_log.empty? + end + + def test_adding_macro_callbacks + @david.posts_with_callbacks << @thinking + assert_equal ["before_adding#{@thinking.id}", "after_adding#{@thinking.id}"], @david.post_log + @david.posts_with_callbacks << @thinking + assert_equal ["before_adding#{@thinking.id}", "after_adding#{@thinking.id}", "before_adding#{@thinking.id}", + "after_adding#{@thinking.id}"], @david.post_log + end + + def test_adding_with_proc_callbacks + @david.posts_with_proc_callbacks << @thinking + assert_equal ["before_adding#{@thinking.id}", "after_adding#{@thinking.id}"], @david.post_log + @david.posts_with_proc_callbacks << @thinking + assert_equal ["before_adding#{@thinking.id}", "after_adding#{@thinking.id}", "before_adding#{@thinking.id}", + "after_adding#{@thinking.id}"], @david.post_log + end + + def test_removing_with_macro_callbacks + first_post, second_post = @david.posts_with_callbacks[0, 2] + @david.posts_with_callbacks.delete(first_post) + assert_equal ["before_removing#{first_post.id}", "after_removing#{first_post.id}"], @david.post_log + @david.posts_with_callbacks.delete(second_post) + assert_equal ["before_removing#{first_post.id}", "after_removing#{first_post.id}", "before_removing#{second_post.id}", + "after_removing#{second_post.id}"], @david.post_log + end + + def test_removing_with_proc_callbacks + first_post, second_post = @david.posts_with_callbacks[0, 2] + @david.posts_with_proc_callbacks.delete(first_post) + assert_equal ["before_removing#{first_post.id}", "after_removing#{first_post.id}"], @david.post_log + @david.posts_with_proc_callbacks.delete(second_post) + assert_equal ["before_removing#{first_post.id}", "after_removing#{first_post.id}", "before_removing#{second_post.id}", + "after_removing#{second_post.id}"], @david.post_log + end + + def test_multiple_callbacks + @david.posts_with_multiple_callbacks << @thinking + assert_equal ["before_adding#{@thinking.id}", "before_adding_proc#{@thinking.id}", "after_adding#{@thinking.id}", + "after_adding_proc#{@thinking.id}"], @david.post_log + @david.posts_with_multiple_callbacks << @thinking + assert_equal ["before_adding#{@thinking.id}", "before_adding_proc#{@thinking.id}", "after_adding#{@thinking.id}", + "after_adding_proc#{@thinking.id}", "before_adding#{@thinking.id}", "before_adding_proc#{@thinking.id}", + "after_adding#{@thinking.id}", "after_adding_proc#{@thinking.id}"], @david.post_log + end + + def test_has_many_callbacks_with_create + morten = Author.create :name => "Morten" + post = morten.posts_with_proc_callbacks.create! :title => "Hello", :body => "How are you doing?" + assert_equal ["before_adding", "after_adding#{post.id}"], morten.post_log + end + + def test_has_many_callbacks_with_create! + morten = Author.create! :name => "Morten" + post = morten.posts_with_proc_callbacks.create :title => "Hello", :body => "How are you doing?" + assert_equal ["before_adding", "after_adding#{post.id}"], morten.post_log + end + + def test_has_many_callbacks_for_save_on_parent + jack = Author.new :name => "Jack" + post = jack.posts_with_callbacks.build :title => "Call me back!", :body => "Before you wake up and after you sleep" + + callback_log = ["before_adding", "after_adding#{jack.posts_with_callbacks.first.id}"] + assert_equal callback_log, jack.post_log + assert jack.save + assert_equal 1, jack.posts_with_callbacks.count + assert_equal callback_log, jack.post_log + end + + def test_has_and_belongs_to_many_add_callback + david = developers(:david) + ar = projects(:active_record) + assert ar.developers_log.empty? + ar.developers_with_callbacks << david + assert_equal ["before_adding#{david.id}", "after_adding#{david.id}"], ar.developers_log + ar.developers_with_callbacks << david + assert_equal ["before_adding#{david.id}", "after_adding#{david.id}", "before_adding#{david.id}", + "after_adding#{david.id}"], ar.developers_log + end + + def test_has_and_belongs_to_many_remove_callback + david = developers(:david) + jamis = developers(:jamis) + activerecord = projects(:active_record) + assert activerecord.developers_log.empty? + activerecord.developers_with_callbacks.delete(david) + assert_equal ["before_removing#{david.id}", "after_removing#{david.id}"], activerecord.developers_log + + activerecord.developers_with_callbacks.delete(jamis) + assert_equal ["before_removing#{david.id}", "after_removing#{david.id}", "before_removing#{jamis.id}", + "after_removing#{jamis.id}"], activerecord.developers_log + end + + def test_has_and_belongs_to_many_remove_callback_on_clear + activerecord = projects(:active_record) + assert activerecord.developers_log.empty? + if activerecord.developers_with_callbacks.size == 0 + activerecord.developers << developers(:david) + activerecord.developers << developers(:jamis) + activerecord.reload + assert activerecord.developers_with_callbacks.size == 2 + end + log_array = activerecord.developers_with_callbacks.collect {|d| ["before_removing#{d.id}","after_removing#{d.id}"]}.flatten.sort + assert activerecord.developers_with_callbacks.clear + assert_equal log_array, activerecord.developers_log.sort + end + + def test_has_many_and_belongs_to_many_callbacks_for_save_on_parent + project = Project.new :name => "Callbacks" + project.developers_with_callbacks.build :name => "Jack", :salary => 95000 + + callback_log = ["before_adding", "after_adding"] + assert_equal callback_log, project.developers_log + assert project.save + assert_equal 1, project.developers_with_callbacks.size + assert_equal callback_log, project.developers_log + end + + def test_dont_add_if_before_callback_raises_exception + assert !@david.unchangable_posts.include?(@authorless) + begin + @david.unchangable_posts << @authorless + rescue Exception => e + end + assert @david.post_log.empty? + assert !@david.unchangable_posts.include?(@authorless) + @david.reload + assert !@david.unchangable_posts.include?(@authorless) + end +end diff --git a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb new file mode 100644 index 0000000000..0ca0b21084 --- /dev/null +++ b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb @@ -0,0 +1,110 @@ +require 'abstract_unit' +require 'models/post' +require 'models/comment' +require 'models/author' +require 'models/category' +require 'models/categorization' +require 'models/company' +require 'models/topic' +require 'models/reply' + +class CascadedEagerLoadingTest < ActiveSupport::TestCase + fixtures :authors, :mixins, :companies, :posts, :topics + + def test_eager_association_loading_with_cascaded_two_levels + authors = Author.find(:all, :include=>{:posts=>:comments}, :order=>"authors.id") + assert_equal 2, authors.size + assert_equal 5, authors[0].posts.size + assert_equal 1, authors[1].posts.size + assert_equal 9, authors[0].posts.collect{|post| post.comments.size }.inject(0){|sum,i| sum+i} + end + + def test_eager_association_loading_with_cascaded_two_levels_and_one_level + authors = Author.find(:all, :include=>[{:posts=>:comments}, :categorizations], :order=>"authors.id") + assert_equal 2, authors.size + assert_equal 5, authors[0].posts.size + assert_equal 1, authors[1].posts.size + assert_equal 9, authors[0].posts.collect{|post| post.comments.size }.inject(0){|sum,i| sum+i} + assert_equal 1, authors[0].categorizations.size + assert_equal 2, authors[1].categorizations.size + 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") + assert_equal 2, authors.size + assert_equal 5, authors[0].posts.size + assert_equal 1, authors[1].posts.size + assert_equal 9, authors[0].posts.collect{|post| post.comments.size }.inject(0){|sum,i| sum+i} + 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") + assert_equal 2, authors.size + assert_equal 5, authors[0].posts.size + assert_equal authors(:david).name, authors[0].name + assert_equal [authors(:david).name], authors[0].posts.collect{|post| post.author.name}.uniq + 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") + 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") + 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 } + assert_equal companies(:first_firm).account.firm.account, assert_no_queries { firms.first.account.firm.account } + end + + def test_eager_association_loading_with_has_many_sti + topics = Topic.find(:all, :include => :replies, :order => 'topics.id') + assert_equal topics(:first, :second), topics + assert_no_queries do + assert_equal 1, topics[0].replies.size + assert_equal 0, topics[1].replies.size + end + end + + def test_eager_association_loading_with_belongs_to_sti + replies = Reply.find(:all, :include => :topic, :order => 'topics.id') + assert_equal [topics(:second)], replies + 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') + assert_equal authors(:david), author + assert_no_queries do + author.posts.first.special_comments + author.posts.first.very_special_comment + end + 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') + assert_equal [authors(:david)], authors + assert_no_queries do + authors.first.posts.first.special_comments.first.post.special_comments + authors.first.posts.first.special_comments.first.post.very_special_comment + end + end +end + +require 'models/vertex' +require 'models/edge' +class CascadedEagerLoadingTest < ActiveSupport::TestCase + fixtures :edges, :vertices + + 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') + 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') + 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_singularization_test.rb b/activerecord/test/cases/associations/eager_singularization_test.rb new file mode 100644 index 0000000000..c1d89a2484 --- /dev/null +++ b/activerecord/test/cases/associations/eager_singularization_test.rb @@ -0,0 +1,145 @@ +require 'abstract_unit' + +class Virus < ActiveRecord::Base + belongs_to :octopus +end +class Octopus < ActiveRecord::Base + has_one :virus +end +class Pass < ActiveRecord::Base + belongs_to :bus +end +class Bus < ActiveRecord::Base + has_many :passes +end +class Mess < ActiveRecord::Base + has_and_belongs_to_many :crises +end +class Crisis < ActiveRecord::Base + has_and_belongs_to_many :messes + has_many :analyses, :dependent => :destroy + has_many :successes, :through => :analyses + has_many :dresses, :dependent => :destroy + has_many :compresses, :through => :dresses +end +class Analysis < ActiveRecord::Base + belongs_to :crisis + belongs_to :success +end +class Success < ActiveRecord::Base + has_many :analyses, :dependent => :destroy + has_many :crises, :through => :analyses +end +class Dress < ActiveRecord::Base + belongs_to :crisis + has_many :compresses +end +class Compress < ActiveRecord::Base + belongs_to :dress +end + + +class EagerSingularizationTest < ActiveSupport::TestCase + + def setup + if ActiveRecord::Base.connection.supports_migrations? + ActiveRecord::Base.connection.create_table :viri do |t| + t.column :octopus_id, :integer + t.column :species, :string + end + ActiveRecord::Base.connection.create_table :octopi do |t| + t.column :species, :string + end + ActiveRecord::Base.connection.create_table :passes do |t| + t.column :bus_id, :integer + t.column :rides, :integer + end + ActiveRecord::Base.connection.create_table :buses do |t| + t.column :name, :string + end + ActiveRecord::Base.connection.create_table :crises_messes, :id => false do |t| + t.column :crisis_id, :integer + t.column :mess_id, :integer + end + ActiveRecord::Base.connection.create_table :messes do |t| + t.column :name, :string + end + ActiveRecord::Base.connection.create_table :crises do |t| + t.column :name, :string + end + ActiveRecord::Base.connection.create_table :successes do |t| + t.column :name, :string + end + ActiveRecord::Base.connection.create_table :analyses do |t| + t.column :crisis_id, :integer + t.column :success_id, :integer + end + ActiveRecord::Base.connection.create_table :dresses do |t| + t.column :crisis_id, :integer + end + ActiveRecord::Base.connection.create_table :compresses do |t| + t.column :dress_id, :integer + end + @have_tables = true + else + @have_tables = false + end + end + + def teardown + ActiveRecord::Base.connection.drop_table :viri + ActiveRecord::Base.connection.drop_table :octopi + ActiveRecord::Base.connection.drop_table :passes + ActiveRecord::Base.connection.drop_table :buses + ActiveRecord::Base.connection.drop_table :crises_messes + ActiveRecord::Base.connection.drop_table :messes + ActiveRecord::Base.connection.drop_table :crises + ActiveRecord::Base.connection.drop_table :successes + ActiveRecord::Base.connection.drop_table :analyses + ActiveRecord::Base.connection.drop_table :dresses + ActiveRecord::Base.connection.drop_table :compresses + end + + def test_eager_no_extra_singularization_belongs_to + return unless @have_tables + assert_nothing_raised do + Virus.find(:all, :include => :octopus) + end + end + + def test_eager_no_extra_singularization_has_one + return unless @have_tables + assert_nothing_raised do + Octopus.find(:all, :include => :virus) + end + end + + def test_eager_no_extra_singularization_has_many + return unless @have_tables + assert_nothing_raised do + Bus.find(:all, :include => :passes) + 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) + 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) + 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) + end + end +end diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb new file mode 100644 index 0000000000..0e351816d3 --- /dev/null +++ b/activerecord/test/cases/associations/eager_test.rb @@ -0,0 +1,447 @@ +require 'abstract_unit' +require 'models/post' +require 'models/comment' +require 'models/author' +require 'models/category' +require 'models/company' +require 'models/person' +require 'models/reader' + +class EagerAssociationTest < ActiveSupport::TestCase + fixtures :posts, :comments, :authors, :categories, :categories_posts, + :companies, :accounts, :tags, :people, :readers + + def test_loading_with_one_association + posts = Post.find(:all, :include => :comments) + 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'") + assert_equal 2, post.comments.size + assert post.comments.include?(comments(:greetings)) + 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'") + 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") + [:eager_other, :sti_habtm, :sti_post_and_comments, :sti_comments, + :authorless, :thinking, :welcome + ].each_with_index do |post, index| + assert_equal posts(post), list[index] + end + end + + def test_with_two_tables_in_from_without_getting_double_quoted + posts = Post.find(:all, + :select => "posts.*", + :from => "authors, posts", + :include => :comments, + :conditions => "posts.author_id = authors.id", + :order => "posts.id" + ) + + assert_equal 2, posts.first.comments.size + end + + def test_loading_with_multiple_associations + posts = Post.find(:all, :include => [ :comments, :author, :categories ], :order => "posts.id") + assert_equal 2, posts.first.comments.size + assert_equal 2, posts.first.categories.size + assert posts.first.comments.include?(comments(:greetings)) + end + + def test_loading_from_an_association + posts = authors(:david).posts.find(:all, :include => :comments, :order => "posts.id") + assert_equal 2, posts.first.comments.size + end + + def test_loading_with_no_associations + assert_nil Post.find(posts(:authorless).id, :include => :author).author + end + + def test_eager_association_loading_with_belongs_to + comments = Comment.find(:all, :include => :post) + assert_equal 10, comments.length + titles = comments.map { |c| c.post.title } + assert titles.include?(posts(:welcome).title) + assert titles.include?(posts(:sti_post_and_comments).title) + end + + def test_eager_association_loading_with_belongs_to_and_limit + comments = Comment.find(:all, :include => :post, :limit => 5, :order => 'comments.id') + 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') + 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') + 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') + 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') + 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_multiple_associations + posts = Post.find(:all, :include => [:author, :very_special_comment], :limit => 1, :order => 'posts.id') + 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') + 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) + assert_equal authors(:mary), assert_no_queries { author_favorite.favorite_author } + 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') + assert_equal 1, posts.length + end + + def test_eager_with_has_many_through + posts_with_comments = people(:michael).posts.find(:all, :include => :comments) + posts_with_author = people(:michael).posts.find(:all, :include => :author ) + posts_with_comments_and_author = people(:michael).posts.find(:all, :include => [ :comments, :author ]) + 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 } + end + + def test_eager_with_has_many_through_an_sti_join_model + author = Author.find(:first, :include => :special_post_comments, :order => 'authors.id') + 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') + 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) + end + + def test_eager_with_has_many_and_limit + posts = Post.find(:all, :order => 'posts.id asc', :include => [ :author, :comments ], :limit => 2) + 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") + else + posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :conditions => "posts.body = 'hello'", :order => "posts.id") + 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 + if current_adapter?(:OpenBaseAdapter) + posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :conditions => [ "FETCHBLOB(posts.body) = ?", 'hello' ], :order => "posts.id") + else + posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :conditions => [ "posts.body = ?", 'hello' ], :order => "posts.id") + 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' ]) + assert_equal 2, posts.size + + count = Post.count(:include => [ :author, :comments ], :limit => 2, :conditions => [ "authors.name = ?", 'David' ]) + assert_equal count, posts.size + end + + def test_eager_with_has_many_and_limit_ond_high_offset + posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :offset => 10, :conditions => [ "authors.name = ?", 'David' ]) + assert_equal 0, posts.size + end + + def test_count_eager_with_has_many_and_limit_ond_high_offset + posts = Post.count(:all, :include => [ :author, :comments ], :limit => 2, :offset => 10, :conditions => [ "authors.name = ?", 'David' ]) + 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'") + 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') + end + + def test_eager_with_has_and_belongs_to_many_and_limit + posts = Post.find(:all, :include => :categories, :order => "posts.id", :limit => 3) + assert_equal 3, posts.size + assert_equal 2, posts[0].categories.size + assert_equal 1, posts[1].categories.size + assert_equal 0, posts[2].categories.size + assert posts[0].categories.include?(categories(:technology)) + assert posts[1].categories.include?(categories(:general)) + 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 + ) + 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 + ) + assert_equal count, posts.size + end + + def test_eager_with_has_many_and_limit_and_scoped_conditions_on_the_eagers + posts = nil + Post.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.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 + end + end + + def test_eager_with_has_many_and_limit_and_scoped_and_explicit_conditions_on_the_eagers + Post.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 + + count = Post.count( + :include => [ :comments, :author ], + :conditions => "authors.name = 'David' AND (comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment')", + :limit => 2 + ) + 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.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") + assert_equal 2, posts[0].categories.size + assert_equal 1, posts[1].categories.size + assert_equal 0, posts[2].categories.size + assert posts[0].categories.include?(categories(:technology)) + assert posts[1].categories.include?(categories(:general)) + end + + def test_eager_with_inheritance + posts = SpecialPost.find(:all, :include => [ :comments ]) + end + + def test_eager_has_one_with_association_inheritance + post = Post.find(4, :include => [ :very_special_comment ]) + 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.special_comments.each do |special_comment| + assert_equal "SpecialComment", special_comment.class.to_s + end + end + + def test_eager_habtm_with_association_inheritance + post = Post.find(6, :include => [ :special_categories ]) + assert_equal 1, post.special_categories.size + post.special_categories.each do |special_category| + assert_equal "SpecialCategory", special_category.class.to_s + end + end + + 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"]) + assert_not_nil f.account + assert_equal companies(:first_firm, :reload).account, f.account + end + + def test_eager_with_multi_table_conditional_properly_counts_the_records_when_using_size + author = authors(:david) + posts_with_no_comments = author.posts.select { |post| post.comments.blank? } + assert_equal posts_with_no_comments.size, author.posts_with_no_comments.size + assert_equal posts_with_no_comments, author.posts_with_no_comments + end + + def test_eager_with_invalid_association_reference + assert_raises(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") { + post = Post.find(6, :include=> :monkeys ) + } + assert_raises(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") { + post = Post.find(6, :include=>[ :monkeys ]) + } + assert_raises(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") { + post = Post.find(6, :include=>[ 'monkeys' ]) + } + assert_raises(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys, :elephants") { + post = Post.find(6, :include=>[ :monkeys, :elephants ]) + } + end + + def find_all_ordered(className, include=nil) + className.find(:all, :order=>"#{className.table_name}.#{className.primary_key}", :include=>include) + 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) + 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) + end + + def test_eager_with_multiple_associations_with_same_table_has_many_and_habtm + # Eager includes of has many and habtm associations aren't necessarily sorted in the same way + def assert_equal_after_sort(item1, item2, item3 = nil) + assert_equal(item1.sort{|a,b| a.id <=> b.id}, item2.sort{|a,b| a.id <=> b.id}) + assert_equal(item3.sort{|a,b| a.id <=> b.id}, item2.sort{|a,b| a.id <=> b.id}) if item3 + end + # Test regular association, association with conditions, association with + # STI, and association with conditions assured not to be true + post_types = [:posts, :other_posts, :special_posts] + # test both has_many and has_and_belongs_to_many + [Author, Category].each do |className| + d1 = find_all_ordered(className) + # test including all post types at once + d2 = find_all_ordered(className, post_types) + d1.each_index do |i| + assert_equal(d1[i], d2[i]) + assert_equal_after_sort(d1[i].posts, d2[i].posts) + post_types[1..-1].each do |post_type| + # test including post_types together + d3 = find_all_ordered(className, [:posts, post_type]) + assert_equal(d1[i], d3[i]) + assert_equal_after_sort(d1[i].posts, d3[i].posts) + assert_equal_after_sort(d1[i].send(post_type), d2[i].send(post_type), d3[i].send(post_type)) + end + end + end + end + + def test_eager_with_multiple_associations_with_same_table_has_one + d1 = find_all_ordered(Firm) + d2 = find_all_ordered(Firm, :account) + d1.each_index do |i| + assert_equal(d1[i], d2[i]) + assert_equal(d1[i].account, d2[i].account) + end + end + + def test_eager_with_multiple_associations_with_same_table_belongs_to + firm_types = [:firm, :firm_with_basic_id, :firm_with_other_name, :firm_with_condition] + d1 = find_all_ordered(Client) + d2 = find_all_ordered(Client, firm_types) + d1.each_index do |i| + assert_equal(d1[i], d2[i]) + firm_types.each { |type| assert_equal(d1[i].send(type), d2[i].send(type)) } + end + end + def test_eager_with_valid_association_as_string_not_symbol + assert_nothing_raised { Post.find(:all, :include => 'comments') } + end + + def test_preconfigured_includes_with_belongs_to + author = posts(:welcome).author_with_posts + assert_equal 5, author.posts.size + end + + def test_preconfigured_includes_with_has_one + comment = posts(:sti_comments).very_special_comment_with_post + assert_equal posts(:sti_comments), comment.post + end + + def test_preconfigured_includes_with_has_many + posts = authors(:david).posts_with_comments + one = posts.detect { |p| p.id == 1 } + assert_equal 5, posts.size + assert_equal 2, one.comments.size + end + + def test_preconfigured_includes_with_habtm + posts = authors(:david).posts_with_categories + one = posts.detect { |p| p.id == 1 } + assert_equal 5, posts.size + assert_equal 2, one.categories.size + end + + def test_preconfigured_includes_with_has_many_and_habtm + posts = authors(:david).posts_with_comments_and_categories + one = posts.detect { |p| p.id == 1 } + assert_equal 5, posts.size + assert_equal 2, one.comments.size + assert_equal 2, one.categories.size + end + + def test_count_with_include + if current_adapter?(:SQLServerAdapter, :SybaseAdapter) + assert_equal 3, authors(:david).posts_with_comments.count(:conditions => "len(comments.body) > 15") + elsif current_adapter?(:OpenBaseAdapter) + assert_equal 3, authors(:david).posts_with_comments.count(:conditions => "length(FETCHBLOB(comments.body)) > 15") + else + assert_equal 3, authors(:david).posts_with_comments.count(:conditions => "length(comments.body) > 15") + end + end +end diff --git a/activerecord/test/cases/associations/extension_test.rb b/activerecord/test/cases/associations/extension_test.rb new file mode 100644 index 0000000000..a34f1bf2bf --- /dev/null +++ b/activerecord/test/cases/associations/extension_test.rb @@ -0,0 +1,47 @@ +require 'abstract_unit' +require 'models/post' +require 'models/comment' +require 'models/project' +require 'models/developer' + +class AssociationsExtensionsTest < ActiveSupport::TestCase + fixtures :projects, :developers, :developers_projects, :comments, :posts + + def test_extension_on_has_many + assert_equal comments(:more_greetings), posts(:welcome).comments.find_most_recent + end + + def test_extension_on_habtm + assert_equal projects(:action_controller), developers(:david).projects.find_most_recent + end + + def test_named_extension_on_habtm + assert_equal projects(:action_controller), developers(:david).projects_extended_by_name.find_most_recent + end + + def test_named_two_extensions_on_habtm + assert_equal projects(:action_controller), developers(:david).projects_extended_by_name_twice.find_most_recent + assert_equal projects(:active_record), developers(:david).projects_extended_by_name_twice.find_least_recent + end + + def test_named_extension_and_block_on_habtm + assert_equal projects(:action_controller), developers(:david).projects_extended_by_name_and_block.find_most_recent + assert_equal projects(:active_record), developers(:david).projects_extended_by_name_and_block.find_least_recent + end + + def test_marshalling_extensions + david = developers(:david) + assert_equal projects(:action_controller), david.projects.find_most_recent + + david = Marshal.load(Marshal.dump(david)) + assert_equal projects(:action_controller), david.projects.find_most_recent + end + + def test_marshalling_named_extensions + david = developers(:david) + assert_equal projects(:action_controller), david.projects_extended_by_name.find_most_recent + + david = Marshal.load(Marshal.dump(david)) + assert_equal projects(:action_controller), david.projects_extended_by_name.find_most_recent + end +end diff --git a/activerecord/test/cases/associations/inner_join_association_test.rb b/activerecord/test/cases/associations/inner_join_association_test.rb new file mode 100644 index 0000000000..da594e459e --- /dev/null +++ b/activerecord/test/cases/associations/inner_join_association_test.rb @@ -0,0 +1,88 @@ +require 'abstract_unit' +require 'fixtures/post' +require 'fixtures/comment' +require 'fixtures/author' +require 'fixtures/category' +require 'fixtures/categorization' + +class InnerJoinAssociationTest < ActiveSupport::TestCase + fixtures :authors, :posts, :comments, :categories, :categories_posts, :categorizations + + def test_construct_finder_sql_creates_inner_joins + sql = Author.send(:construct_finder_sql, :joins => :posts) + assert_match /INNER JOIN .?posts.? ON .?posts.?.author_id = authors.id/, sql + end + + def test_construct_finder_sql_cascades_inner_joins + sql = Author.send(:construct_finder_sql, :joins => {:posts => :comments}) + assert_match /INNER JOIN .?posts.? ON .?posts.?.author_id = authors.id/, sql + assert_match /INNER JOIN .?comments.? ON .?comments.?.post_id = posts.id/, sql + end + + def test_construct_finder_sql_inner_joins_through_associations + sql = Author.send(:construct_finder_sql, :joins => :categorized_posts) + assert_match /INNER JOIN .?categorizations.?.*INNER JOIN .?posts.?/, sql + end + + def test_construct_finder_sql_applies_association_conditions + sql = Author.send(:construct_finder_sql, :joins => :categories_like_general, :conditions => "TERMINATING_MARKER") + assert_match /INNER JOIN .?categories.? ON.*AND.*.?General.?.*TERMINATING_MARKER/, sql + end + + def test_construct_finder_sql_unpacks_nested_joins + sql = Author.send(:construct_finder_sql, :joins => {:posts => [[:comments]]}) + assert_no_match /inner join.*inner join.*inner join/i, sql, "only two join clauses should be present" + assert_match /INNER JOIN .?posts.? ON .?posts.?.author_id = authors.id/, sql + assert_match /INNER JOIN .?comments.? ON .?comments.?.post_id = .?posts.?.id/, sql + end + + def test_construct_finder_sql_ignores_empty_joins_hash + sql = Author.send(:construct_finder_sql, :joins => {}) + assert_no_match /JOIN/i, sql + end + + def test_construct_finder_sql_ignores_empty_joins_array + sql = Author.send(:construct_finder_sql, :joins => []) + assert_no_match /JOIN/i, sql + end + + def test_find_with_implicit_inner_joins_honors_readonly_without_select + authors = Author.find(:all, :joins => :posts) + assert !authors.empty?, "expected authors to be non-empty" + assert authors.all? {|a| a.readonly? }, "expected all authors to be readonly" + end + + def test_find_with_implicit_inner_joins_honors_readonly_with_select + authors = Author.find(:all, :select => 'authors.*', :joins => :posts) + assert !authors.empty?, "expected authors to be non-empty" + assert authors.all? {|a| !a.readonly? }, "expected no authors to be readonly" + end + + def test_find_with_implicit_inner_joins_honors_readonly_false + authors = Author.find(:all, :joins => :posts, :readonly => false) + assert !authors.empty?, "expected authors to be non-empty" + assert authors.all? {|a| !a.readonly? }, "expected no authors to be readonly" + end + + def test_find_with_implicit_inner_joins_does_not_set_associations + authors = Author.find(:all, :select => 'authors.*', :joins => :posts) + 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" + end + + def test_count_honors_implicit_inner_joins + real_count = Author.find(:all).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" + end + + def test_calculate_honors_implicit_inner_joins + real_count = Author.find(:all).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" + end + + def test_calculate_honors_implicit_inner_joins_and_distinct_and_conditions + real_count = Author.find(:all).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%'") + assert_equal real_count, authors_with_welcoming_post_titles, "inner join and conditions should have only returned authors posting titles starting with 'Welcome'" + end +end diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb new file mode 100644 index 0000000000..c6a1e356a5 --- /dev/null +++ b/activerecord/test/cases/associations/join_model_test.rb @@ -0,0 +1,559 @@ +require 'abstract_unit' +require 'fixtures/tag' +require 'fixtures/tagging' +require 'fixtures/post' +require 'fixtures/item' +require 'fixtures/comment' +require 'fixtures/author' +require 'fixtures/category' +require 'fixtures/categorization' +require 'fixtures/vertex' +require 'fixtures/edge' +require 'fixtures/book' +require 'fixtures/citation' + +class AssociationsJoinModelTest < ActiveSupport::TestCase + self.use_transactional_fixtures = false + fixtures :posts, :authors, :categories, :categorizations, :comments, :tags, :taggings, :author_favorites, :vertices, :items, :books + + def test_has_many + assert authors(:david).categories.include?(categories(:general)) + end + + def test_has_many_inherited + assert authors(:mary).categories.include?(categories(:sti_test)) + end + + def test_inherited_has_many + assert categories(:sti_test).authors.include?(authors(:mary)) + end + + def test_has_many_uniq_through_join_model + assert_equal 2, authors(:mary).categorized_posts.size + assert_equal 1, authors(:mary).unique_categorized_posts.size + end + + def test_has_many_uniq_through_count + author = authors(:mary) + 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 !authors(:mary).unique_categorized_posts.loaded? + end + + def test_polymorphic_has_many + assert posts(:welcome).taggings.include?(taggings(:welcome_general)) + end + + def test_polymorphic_has_one + assert_equal taggings(:welcome_general), posts(:welcome).tagging + end + + def test_polymorphic_belongs_to + assert_equal posts(:welcome), posts(:welcome).taggings.first.taggable + end + + def test_polymorphic_has_many_going_through_join_model + assert_equal tags(:general), tag = posts(:welcome).tags.first + assert_no_queries do + tag.tagging + end + end + + def test_count_polymorphic_has_many + assert_equal 1, posts(:welcome).taggings.count + assert_equal 1, posts(:welcome).tags.count + end + + def test_polymorphic_has_many_going_through_join_model_with_find + assert_equal tags(:general), tag = posts(:welcome).tags.find(:first) + assert_no_queries do + tag.tagging + end + end + + def test_polymorphic_has_many_going_through_join_model_with_include_on_source_reflection + assert_equal tags(:general), tag = posts(:welcome).funky_tags.first + assert_no_queries do + tag.tagging + end + 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_no_queries do + tag.tagging + end + end + + def test_polymorphic_has_many_going_through_join_model_with_disabled_include + assert_equal tags(:general), tag = posts(:welcome).tags.add_joins_and_select.first + assert_queries 1 do + tag.tagging + end + end + + def test_polymorphic_has_many_going_through_join_model_with_custom_select_and_joins + assert_equal tags(:general), tag = posts(:welcome).tags.add_joins_and_select.first + tag.author_id + end + + def test_polymorphic_has_many_going_through_join_model_with_custom_foreign_key + assert_equal tags(:misc), taggings(:welcome_general).super_tag + assert_equal tags(:misc), posts(:welcome).super_tags.first + end + + def test_polymorphic_has_many_create_model_with_inheritance_and_custom_base_class + post = SubStiPost.create :title => 'SubStiPost', :body => 'SubStiPost body' + assert_instance_of SubStiPost, post + + tagging = tags(:misc).taggings.create(:taggable => post) + assert_equal "SubStiPost", tagging.taggable_type + end + + def test_polymorphic_has_many_going_through_join_model_with_inheritance + assert_equal tags(:general), posts(:thinking).tags.first + end + + def test_polymorphic_has_many_going_through_join_model_with_inheritance_with_custom_class_name + assert_equal tags(:general), posts(:thinking).funky_tags.first + end + + def test_polymorphic_has_many_create_model_with_inheritance + post = posts(:thinking) + assert_instance_of SpecialPost, post + + tagging = tags(:misc).taggings.create(:taggable => post) + assert_equal "Post", tagging.taggable_type + end + + def test_polymorphic_has_one_create_model_with_inheritance + tagging = tags(:misc).create_tagging(:taggable => posts(:thinking)) + assert_equal "Post", tagging.taggable_type + end + + def test_set_polymorphic_has_many + tagging = tags(:misc).taggings.create + posts(:thinking).taggings << tagging + assert_equal "Post", tagging.taggable_type + end + + def test_set_polymorphic_has_one + tagging = tags(:misc).taggings.create + posts(:thinking).tagging = tagging + assert_equal "Post", tagging.taggable_type + end + + def test_create_polymorphic_has_many_with_scope + old_count = posts(:welcome).taggings.count + tagging = posts(:welcome).taggings.create(:tag => tags(:misc)) + assert_equal "Post", tagging.taggable_type + assert_equal old_count+1, posts(:welcome).taggings.count + end + + def test_create_bang_polymorphic_with_has_many_scope + old_count = posts(:welcome).taggings.count + tagging = posts(:welcome).taggings.create!(:tag => tags(:misc)) + assert_equal "Post", tagging.taggable_type + assert_equal old_count+1, posts(:welcome).taggings.count + end + + def test_create_polymorphic_has_one_with_scope + old_count = Tagging.count + tagging = posts(:welcome).tagging.create(:tag => tags(:misc)) + assert_equal "Post", tagging.taggable_type + assert_equal old_count+1, Tagging.count + end + + def test_delete_polymorphic_has_many_with_delete_all + assert_equal 1, posts(:welcome).taggings.count + posts(:welcome).taggings.first.update_attribute :taggable_type, 'PostWithHasManyDeleteAll' + post = find_post_with_dependency(1, :has_many, :taggings, :delete_all) + + old_count = Tagging.count + post.destroy + assert_equal old_count-1, Tagging.count + assert_equal 0, posts(:welcome).taggings.count + end + + def test_delete_polymorphic_has_many_with_destroy + assert_equal 1, posts(:welcome).taggings.count + posts(:welcome).taggings.first.update_attribute :taggable_type, 'PostWithHasManyDestroy' + post = find_post_with_dependency(1, :has_many, :taggings, :destroy) + + old_count = Tagging.count + post.destroy + assert_equal old_count-1, Tagging.count + assert_equal 0, posts(:welcome).taggings.count + end + + def test_delete_polymorphic_has_many_with_nullify + assert_equal 1, posts(:welcome).taggings.count + posts(:welcome).taggings.first.update_attribute :taggable_type, 'PostWithHasManyNullify' + post = find_post_with_dependency(1, :has_many, :taggings, :nullify) + + old_count = Tagging.count + post.destroy + assert_equal old_count, Tagging.count + assert_equal 0, posts(:welcome).taggings.count + end + + def test_delete_polymorphic_has_one_with_destroy + assert posts(:welcome).tagging + posts(:welcome).tagging.update_attribute :taggable_type, 'PostWithHasOneDestroy' + post = find_post_with_dependency(1, :has_one, :tagging, :destroy) + + old_count = Tagging.count + post.destroy + assert_equal old_count-1, Tagging.count + assert_nil posts(:welcome).tagging(true) + end + + def test_delete_polymorphic_has_one_with_nullify + assert posts(:welcome).tagging + posts(:welcome).tagging.update_attribute :taggable_type, 'PostWithHasOneNullify' + post = find_post_with_dependency(1, :has_one, :tagging, :nullify) + + old_count = Tagging.count + post.destroy + assert_equal old_count, Tagging.count + assert_nil posts(:welcome).tagging(true) + end + + def test_has_many_with_piggyback + assert_equal "2", categories(:sti_test).authors.first.post_id.to_s + 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') + 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 } + end + end + + def test_include_polymorphic_has_one + post = Post.find_by_id(posts(:welcome).id, :include => :tagging) + tagging = taggings(:welcome_general) + assert_no_queries do + assert_equal tagging, post.tagging + end + end + + def test_include_polymorphic_has_one_defined_in_abstract_parent + item = Item.find_by_id(items(:dvd).id, :include => :tagging) + tagging = taggings(:godfather) + assert_no_queries do + assert_equal tagging, item.tagging + end + 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') + 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 } + end + 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') + 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 } + end + end + + def test_has_many_find_all + assert_equal [categories(:general)], authors(:david).categories.find(:all) + end + + def test_has_many_find_first + assert_equal categories(:general), authors(:david).categories.find(:first) + end + + def test_has_many_with_hash_conditions + assert_equal categories(:general), authors(:david).categories_like_general.find(:first) + end + + def test_has_many_find_conditions + assert_equal categories(:general), authors(:david).categories.find(:first, :conditions => "categories.name = 'General'") + assert_equal 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_equal nil, authors(:david).categories.find_by_name('Technology') + end + + def test_has_many_going_through_join_model_with_custom_foreign_key + assert_equal [], posts(:thinking).authors + assert_equal [authors(:mary)], posts(:authorless).authors + end + + def test_belongs_to_polymorphic_with_counter_cache + assert_equal 0, posts(:welcome)[:taggings_count] + tagging = posts(:welcome).taggings.create(:tag => tags(:general)) + assert_equal 1, posts(:welcome, :reload)[:taggings_count] + tagging.destroy + assert posts(:welcome, :reload)[:taggings_count].zero? + end + + def test_unavailable_through_reflection + assert_raise(ActiveRecord::HasManyThroughAssociationNotFoundError) { authors(:david).nothings } + end + + def test_has_many_through_join_model_with_conditions + assert_equal [], posts(:welcome).invalid_taggings + assert_equal [], posts(:welcome).invalid_tags + end + + def test_has_many_polymorphic + assert_raise ActiveRecord::HasManyThroughAssociationPolymorphicError do + assert_equal posts(:welcome, :thinking), tags(:general).taggables + end + assert_raise ActiveRecord::EagerLoadPolymorphicError do + assert_equal posts(:welcome, :thinking), tags(:general).taggings.find(:all, :include => :taggable) + end + end + + def test_has_many_polymorphic_with_source_type + assert_equal posts(:welcome, :thinking), tags(:general).tagged_posts + end + + def test_eager_has_many_polymorphic_with_source_type + tag_with_include = Tag.find(tags(:general).id, :include => :tagged_posts) + desired = posts(:welcome, :thinking) + assert_no_queries do + assert_equal desired, tag_with_include.tagged_posts + end + end + + def test_has_many_through_has_many_find_all + assert_equal comments(:greetings), authors(:david).comments.find(:all, :order => 'comments.id').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 + end + + def test_has_many_through_has_many_find_first + assert_equal comments(:greetings), authors(:david).comments.find(:first, :order => 'comments.id') + 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) + end + + def test_has_many_through_has_many_find_by_id + assert_equal comments(:more_greetings), authors(:david).comments.find(2) + end + + def test_has_many_through_polymorphic_has_one + assert_raise(ActiveRecord::HasManyThroughSourceAssociationMacroError) { authors(:david).tagging } + end + + def test_has_many_through_polymorphic_has_many + assert_equal taggings(:welcome_general, :thinking_general), authors(:david).taggings.uniq.sort_by { |t| t.id } + end + + def test_include_has_many_through_polymorphic_has_many + author = Author.find_by_id(authors(:david).id, :include => :taggings) + expected_taggings = taggings(:welcome_general, :thinking_general) + assert_no_queries do + assert_equal expected_taggings, author.taggings.uniq.sort_by { |t| t.id } + end + end + + def test_has_many_through_has_many_through + assert_raise(ActiveRecord::HasManyThroughSourceAssociationMacroError) { authors(:david).tags } + end + + def test_has_many_through_habtm + assert_raise(ActiveRecord::HasManyThroughSourceAssociationMacroError) { authors(:david).post_categories } + end + + def test_eager_load_has_many_through_has_many + author = Author.find :first, :conditions => ['name = ?', 'David'], :include => :comments, :order => 'comments.id' + SpecialComment.new; VerySpecialComment.new + assert_no_queries do + assert_equal [1,2,3,5,6,7,8,9,10], author.comments.collect(&:id) + end + end + + def test_eager_load_has_many_through_has_many_with_conditions + post = Post.find(:first, :include => :invalid_tags) + assert_no_queries do + post.invalid_tags + end + end + + 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) + end + end + + def test_self_referential_has_many_through + assert_equal [authors(:mary)], authors(:david).favorite_authors + assert_equal [], authors(:mary).favorite_authors + end + + def test_add_to_self_referential_has_many_through + new_author = Author.create(:name => "Bob") + authors(:david).author_favorites.create :favorite_author => new_author + assert_equal new_author, authors(:david).reload.favorite_authors.first + end + + def test_has_many_through_uses_conditions_specified_on_the_has_many_association + author = Author.find(:first) + assert !author.comments.blank? + assert author.nonexistant_comments.blank? + end + + def test_has_many_through_uses_correct_attributes + assert_nil posts(:thinking).tags.find_by_name("General").attributes["tag_id"] + end + + def test_raise_error_when_adding_new_record_to_has_many_through + assert_raise(ActiveRecord::HasManyThroughCantAssociateNewRecords) { posts(:thinking).tags << tags(:general).clone } + assert_raise(ActiveRecord::HasManyThroughCantAssociateNewRecords) { posts(:thinking).clone.tags << tags(:general) } + assert_raise(ActiveRecord::HasManyThroughCantAssociateNewRecords) { posts(:thinking).tags.build } + assert_raise(ActiveRecord::HasManyThroughCantAssociateNewRecords) { posts(:thinking).tags.new } + end + + def test_create_associate_when_adding_to_has_many_through + count = posts(:thinking).tags.count + push = Tag.create!(:name => 'pushme') + post_thinking = posts(:thinking) + assert_nothing_raised { post_thinking.tags << push } + assert_nil( wrong = post_thinking.tags.detect { |t| t.class != Tag }, + message = "Expected a Tag in tags collection, got #{wrong.class}.") + assert_nil( wrong = post_thinking.taggings.detect { |t| t.class != Tagging }, + message = "Expected a Tagging in taggings collection, got #{wrong.class}.") + assert_equal(count + 1, post_thinking.tags.size) + assert_equal(count + 1, post_thinking.tags(true).size) + + assert_kind_of Tag, post_thinking.tags.create!(:name => 'foo') + assert_nil( wrong = post_thinking.tags.detect { |t| t.class != Tag }, + message = "Expected a Tag in tags collection, got #{wrong.class}.") + assert_nil( wrong = post_thinking.taggings.detect { |t| t.class != Tagging }, + message = "Expected a Tagging in taggings collection, got #{wrong.class}.") + assert_equal(count + 2, post_thinking.tags.size) + assert_equal(count + 2, post_thinking.tags(true).size) + + assert_nothing_raised { post_thinking.tags.concat(Tag.create!(:name => 'abc'), Tag.create!(:name => 'def')) } + assert_nil( wrong = post_thinking.tags.detect { |t| t.class != Tag }, + message = "Expected a Tag in tags collection, got #{wrong.class}.") + assert_nil( wrong = post_thinking.taggings.detect { |t| t.class != Tagging }, + message = "Expected a Tagging in taggings collection, got #{wrong.class}.") + assert_equal(count + 4, post_thinking.tags.size) + assert_equal(count + 4, post_thinking.tags(true).size) + + # Raises if the wrong reflection name is used to set the Edge belongs_to + assert_nothing_raised { vertices(:vertex_1).sinks << vertices(:vertex_5) } + end + + def test_has_many_through_collection_size_doesnt_load_target_if_not_loaded + author = authors(:david) + assert_equal 9, author.comments.size + assert !author.comments.loaded? + end + + uses_mocha('has_many_through_collection_size_uses_counter_cache_if_it_exists') do + def test_has_many_through_collection_size_uses_counter_cache_if_it_exists + author = authors(:david) + author.stubs(:read_attribute).with('comments_count').returns(100) + assert_equal 100, author.comments.size + assert !author.comments.loaded? + end + end + + def test_adding_junk_to_has_many_through_should_raise_type_mismatch + assert_raise(ActiveRecord::AssociationTypeMismatch) { posts(:thinking).tags << "Uhh what now?" } + end + + def test_adding_to_has_many_through_should_return_self + tags = posts(:thinking).tags + assert_equal tags, posts(:thinking).tags.push(tags(:general)) + end + + def test_delete_associate_when_deleting_from_has_many_through_with_nonstandard_id + count = books(:awdr).references.count + references_before = books(:awdr).references + book = Book.create!(:name => 'Getting Real') + book_awdr = books(:awdr) + book_awdr.references << book + assert_equal(count + 1, book_awdr.references(true).size) + + assert_nothing_raised { book_awdr.references.delete(book) } + assert_equal(count, book_awdr.references.size) + assert_equal(count, book_awdr.references(true).size) + assert_equal(references_before.sort, book_awdr.references.sort) + end + + def test_delete_associate_when_deleting_from_has_many_through + count = posts(:thinking).tags.count + tags_before = posts(:thinking).tags + tag = Tag.create!(:name => 'doomed') + post_thinking = posts(:thinking) + post_thinking.tags << tag + assert_equal(count + 1, post_thinking.tags(true).size) + + assert_nothing_raised { post_thinking.tags.delete(tag) } + assert_equal(count, post_thinking.tags.size) + assert_equal(count, post_thinking.tags(true).size) + assert_equal(tags_before.sort, post_thinking.tags.sort) + end + + def test_delete_associate_when_deleting_from_has_many_through_with_multiple_tags + count = posts(:thinking).tags.count + tags_before = posts(:thinking).tags + doomed = Tag.create!(:name => 'doomed') + doomed2 = Tag.create!(:name => 'doomed2') + quaked = Tag.create!(:name => 'quaked') + post_thinking = posts(:thinking) + post_thinking.tags << doomed << doomed2 + assert_equal(count + 2, post_thinking.tags(true).size) + + assert_nothing_raised { post_thinking.tags.delete(doomed, doomed2, quaked) } + assert_equal(count, post_thinking.tags.size) + assert_equal(count, post_thinking.tags(true).size) + assert_equal(tags_before.sort, post_thinking.tags.sort) + end + + def test_deleting_junk_from_has_many_through_should_raise_type_mismatch + assert_raise(ActiveRecord::AssociationTypeMismatch) { posts(:thinking).tags.delete("Uhh what now?") } + end + + def test_has_many_through_sum_uses_calculations + assert_nothing_raised { authors(:david).comments.sum(:post_id) } + end + + def test_has_many_through_has_many_with_sti + assert_equal [comments(:does_it_hurt)], authors(:david).special_post_comments + end + + def test_uniq_has_many_through_should_retain_order + comment_ids = authors(:david).comments.map(&:id) + assert_equal comment_ids.sort, authors(:david).ordered_uniq_comments.map(&:id) + assert_equal comment_ids.sort.reverse, authors(:david).ordered_uniq_comments_desc.map(&:id) + end + + private + # create dynamic Post models to allow different dependency options + def find_post_with_dependency(post_id, association, association_name, dependency) + class_name = "PostWith#{association.to_s.classify}#{dependency.to_s.classify}" + Post.find(post_id).update_attribute :type, class_name + klass = Object.const_set(class_name, Class.new(ActiveRecord::Base)) + klass.set_table_name 'posts' + klass.send(association, association_name, :as => :taggable, :dependent => dependency) + klass.find(post_id) + end +end diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb new file mode 100755 index 0000000000..a657da265d --- /dev/null +++ b/activerecord/test/cases/associations_test.rb @@ -0,0 +1,2177 @@ +require 'abstract_unit' +require 'fixtures/developer' +require 'fixtures/project' +require 'fixtures/company' +require 'fixtures/topic' +require 'fixtures/reply' +require 'fixtures/computer' +require 'fixtures/customer' +require 'fixtures/order' +require 'fixtures/categorization' +require 'fixtures/category' +require 'fixtures/post' +require 'fixtures/author' +require 'fixtures/comment' +require 'fixtures/tag' +require 'fixtures/tagging' +require 'fixtures/person' +require 'fixtures/reader' + +class AssociationsTest < ActiveSupport::TestCase + fixtures :accounts, :companies, :developers, :projects, :developers_projects, + :computers + + def test_bad_collection_keys + assert_raise(ArgumentError, 'ActiveRecord should have barked on bad collection keys') do + Class.new(ActiveRecord::Base).has_many(:wheels, :name => 'wheels') + end + end + + def test_should_construct_new_finder_sql_after_create + person = Person.new + assert_equal [], person.readers.find(:all) + person.save! + reader = Reader.create! :person => person, :post => Post.new(:title => "foo", :body => "bar") + assert_equal [reader], person.readers.find(:all) + end + + def test_force_reload + firm = Firm.new("name" => "A New Firm, Inc") + firm.save + firm.clients.each {|c|} # forcing to load all clients + assert firm.clients.empty?, "New firm shouldn't have client objects" + assert_equal 0, firm.clients.size, "New firm should have 0 clients" + + client = Client.new("name" => "TheClient.com", "firm_id" => firm.id) + client.save + + assert firm.clients.empty?, "New firm should have cached no client objects" + assert_equal 0, firm.clients.size, "New firm should have cached 0 clients count" + + assert !firm.clients(true).empty?, "New firm should have reloaded client objects" + assert_equal 1, firm.clients(true).size, "New firm should have reloaded clients count" + end + + def test_storing_in_pstore + require "tmpdir" + store_filename = File.join(Dir.tmpdir, "ar-pstore-association-test") + File.delete(store_filename) if File.exist?(store_filename) + require "pstore" + apple = Firm.create("name" => "Apple") + natural = Client.new("name" => "Natural Company") + apple.clients << natural + + db = PStore.new(store_filename) + db.transaction do + db["apple"] = apple + end + + db = PStore.new(store_filename) + db.transaction do + assert_equal "Natural Company", db["apple"].clients.first.name + end + end +end + +class AssociationProxyTest < ActiveSupport::TestCase + fixtures :authors, :posts, :categorizations, :categories, :developers, :projects, :developers_projects + + def test_proxy_accessors + welcome = posts(:welcome) + assert_equal welcome, welcome.author.proxy_owner + assert_equal welcome.class.reflect_on_association(:author), welcome.author.proxy_reflection + welcome.author.class # force load target + assert_equal welcome.author, welcome.author.proxy_target + + david = authors(:david) + assert_equal david, david.posts.proxy_owner + assert_equal david.class.reflect_on_association(:posts), david.posts.proxy_reflection + david.posts.first # force load target + assert_equal david.posts, david.posts.proxy_target + + assert_equal david, david.posts_with_extension.testing_proxy_owner + assert_equal david.class.reflect_on_association(:posts_with_extension), david.posts_with_extension.testing_proxy_reflection + david.posts_with_extension.first # force load target + assert_equal david.posts_with_extension, david.posts_with_extension.testing_proxy_target + end + + def test_push_does_not_load_target + david = authors(:david) + + david.posts << (post = Post.new(:title => "New on Edge", :body => "More cool stuff!")) + assert !david.posts.loaded? + assert david.posts.include?(post) + end + + def test_push_has_many_through_does_not_load_target + david = authors(:david) + + david.categories << categories(:technology) + assert !david.categories.loaded? + assert david.categories.include?(categories(:technology)) + end + + def test_push_followed_by_save_does_not_load_target + david = authors(:david) + + david.posts << (post = Post.new(:title => "New on Edge", :body => "More cool stuff!")) + assert !david.posts.loaded? + david.save + assert !david.posts.loaded? + assert david.posts.include?(post) + end + + def test_push_does_not_lose_additions_to_new_record + josh = Author.new(:name => "Josh") + josh.posts << Post.new(:title => "New on Edge", :body => "More cool stuff!") + assert josh.posts.loaded? + assert_equal 1, josh.posts.size + end + + def test_save_on_parent_does_not_load_target + david = developers(:david) + + assert !david.projects.loaded? + david.update_attribute(:created_at, Time.now) + assert !david.projects.loaded? + end + + def test_save_on_parent_saves_children + developer = Developer.create :name => "Bryan", :salary => 50_000 + assert_equal 1, developer.reload.audit_logs.size + end + + def test_failed_reload_returns_nil + p = setup_dangling_association + assert_nil p.author.reload + end + + def test_failed_reset_returns_nil + p = setup_dangling_association + assert_nil p.author.reset + end + + def test_reload_returns_assocition + david = developers(:david) + assert_nothing_raised do + assert_equal david.projects, david.projects.reload.reload + end + end + + def setup_dangling_association + josh = Author.create(:name => "Josh") + p = Post.create(:title => "New on Edge", :body => "More cool stuff!", :author => josh) + josh.destroy + p + end +end + +class HasOneAssociationsTest < ActiveSupport::TestCase + fixtures :accounts, :companies, :developers, :projects, :developers_projects + + def setup + Account.destroyed_account_ids.clear + end + + def test_has_one + assert_equal companies(:first_firm).account, Account.find(1) + assert_equal Account.find(1).credit_limit, companies(:first_firm).account.credit_limit + end + + def test_has_one_cache_nils + firm = companies(:another_firm) + assert_queries(1) { assert_nil firm.account } + assert_queries(0) { assert_nil firm.account } + + firms = Firm.find(:all, :include => :account) + assert_queries(0) { firms.each(&:account) } + end + + def test_can_marshal_has_one_association_with_nil_target + firm = Firm.new + assert_nothing_raised do + assert_equal firm.attributes, Marshal.load(Marshal.dump(firm)).attributes + end + + firm.account + assert_nothing_raised do + assert_equal firm.attributes, Marshal.load(Marshal.dump(firm)).attributes + end + end + + def test_proxy_assignment + company = companies(:first_firm) + assert_nothing_raised { company.account = company.account } + end + + def test_triple_equality + assert Account === companies(:first_firm).account + assert companies(:first_firm).account === Account + end + + def test_type_mismatch + assert_raises(ActiveRecord::AssociationTypeMismatch) { companies(:first_firm).account = 1 } + assert_raises(ActiveRecord::AssociationTypeMismatch) { companies(:first_firm).account = Project.find(1) } + end + + def test_natural_assignment + apple = Firm.create("name" => "Apple") + citibank = Account.create("credit_limit" => 10) + apple.account = citibank + assert_equal apple.id, citibank.firm_id + end + + def test_natural_assignment_to_nil + old_account_id = companies(:first_firm).account.id + companies(:first_firm).account = nil + companies(:first_firm).save + assert_nil companies(:first_firm).account + # account is dependent, therefore is destroyed when reference to owner is lost + assert_raises(ActiveRecord::RecordNotFound) { Account.find(old_account_id) } + end + + def test_assignment_without_replacement + apple = Firm.create("name" => "Apple") + citibank = Account.create("credit_limit" => 10) + apple.account = citibank + assert_equal apple.id, citibank.firm_id + + hsbc = apple.build_account({ :credit_limit => 20}, false) + assert_equal apple.id, hsbc.firm_id + hsbc.save + assert_equal apple.id, citibank.firm_id + + nykredit = apple.create_account({ :credit_limit => 30}, false) + assert_equal apple.id, nykredit.firm_id + assert_equal apple.id, citibank.firm_id + assert_equal apple.id, hsbc.firm_id + end + + def test_assignment_without_replacement_on_create + apple = Firm.create("name" => "Apple") + citibank = Account.create("credit_limit" => 10) + apple.account = citibank + assert_equal apple.id, citibank.firm_id + + hsbc = apple.create_account({:credit_limit => 10}, false) + assert_equal apple.id, hsbc.firm_id + hsbc.save + assert_equal apple.id, citibank.firm_id + end + + def test_dependence + num_accounts = Account.count + + firm = Firm.find(1) + assert !firm.account.nil? + account_id = firm.account.id + assert_equal [], Account.destroyed_account_ids[firm.id] + + firm.destroy + assert_equal num_accounts - 1, Account.count + assert_equal [account_id], Account.destroyed_account_ids[firm.id] + end + + def test_exclusive_dependence + num_accounts = Account.count + + firm = ExclusivelyDependentFirm.find(9) + assert !firm.account.nil? + account_id = firm.account.id + assert_equal [], Account.destroyed_account_ids[firm.id] + + firm.destroy + assert_equal num_accounts - 1, Account.count + assert_equal [], Account.destroyed_account_ids[firm.id] + end + + def test_dependence_with_nil_associate + firm = DependentFirm.new(:name => 'nullify') + firm.save! + assert_nothing_raised { firm.destroy } + end + + def test_succesful_build_association + firm = Firm.new("name" => "GlobalMegaCorp") + firm.save + + account = firm.build_account("credit_limit" => 1000) + assert account.save + assert_equal account, firm.account + end + + def test_failing_build_association + firm = Firm.new("name" => "GlobalMegaCorp") + firm.save + + account = firm.build_account + assert !account.save + assert_equal "can't be empty", account.errors.on("credit_limit") + end + + def test_build_association_twice_without_saving_affects_nothing + count_of_account = Account.count + firm = Firm.find(:first) + account1 = firm.build_account("credit_limit" => 1000) + account2 = firm.build_account("credit_limit" => 2000) + + assert_equal count_of_account, Account.count + end + + def test_create_association + firm = Firm.create(:name => "GlobalMegaCorp") + account = firm.create_account(:credit_limit => 1000) + assert_equal account, firm.reload.account + end + + def test_build + firm = Firm.new("name" => "GlobalMegaCorp") + firm.save + + firm.account = account = Account.new("credit_limit" => 1000) + assert_equal account, firm.account + assert account.save + assert_equal account, firm.account + end + + def test_build_before_child_saved + firm = Firm.find(1) + + account = firm.account.build("credit_limit" => 1000) + assert_equal account, firm.account + assert account.new_record? + assert firm.save + assert_equal account, firm.account + assert !account.new_record? + end + + def test_build_before_either_saved + firm = Firm.new("name" => "GlobalMegaCorp") + + firm.account = account = Account.new("credit_limit" => 1000) + assert_equal account, firm.account + assert account.new_record? + assert firm.save + assert_equal account, firm.account + assert !account.new_record? + end + + def test_failing_build_association + firm = Firm.new("name" => "GlobalMegaCorp") + firm.save + + firm.account = account = Account.new + assert_equal account, firm.account + assert !account.save + assert_equal account, firm.account + assert_equal "can't be empty", account.errors.on("credit_limit") + end + + def test_create + firm = Firm.new("name" => "GlobalMegaCorp") + firm.save + firm.account = account = Account.create("credit_limit" => 1000) + assert_equal account, firm.account + end + + def test_create_before_save + firm = Firm.new("name" => "GlobalMegaCorp") + firm.account = account = Account.create("credit_limit" => 1000) + assert_equal account, firm.account + end + + def test_dependence_with_missing_association + Account.destroy_all + firm = Firm.find(1) + assert firm.account.nil? + firm.destroy + end + + def test_dependence_with_missing_association_and_nullify + Account.destroy_all + firm = DependentFirm.find(:first) + assert firm.account.nil? + firm.destroy + end + + def test_assignment_before_parent_saved + firm = Firm.new("name" => "GlobalMegaCorp") + firm.account = a = Account.find(1) + assert firm.new_record? + assert_equal a, firm.account + assert firm.save + assert_equal a, firm.account + assert_equal a, firm.account(true) + end + + def test_finding_with_interpolated_condition + firm = Firm.find(:first) + superior = firm.clients.create(:name => 'SuperiorCo') + superior.rating = 10 + superior.save + assert_equal 10, firm.clients_with_interpolated_conditions.first.rating + end + + def test_assignment_before_child_saved + firm = Firm.find(1) + firm.account = a = Account.new("credit_limit" => 1000) + assert !a.new_record? + assert_equal a, firm.account + assert_equal a, firm.account + assert_equal a, firm.account(true) + end + + def test_assignment_before_either_saved + firm = Firm.new("name" => "GlobalMegaCorp") + firm.account = a = Account.new("credit_limit" => 1000) + assert firm.new_record? + assert a.new_record? + assert_equal a, firm.account + assert firm.save + assert !firm.new_record? + assert !a.new_record? + assert_equal a, firm.account + assert_equal a, firm.account(true) + end + + def test_not_resaved_when_unchanged + firm = Firm.find(:first, :include => :account) + assert_queries(1) { firm.save! } + + firm = Firm.find(:first) + firm.account = Account.find(:first) + assert_queries(1) { firm.save! } + + firm = Firm.find(:first).clone + firm.account = Account.find(:first) + assert_queries(2) { firm.save! } + + firm = Firm.find(:first).clone + firm.account = Account.find(:first).clone + assert_queries(2) { firm.save! } + end + + def test_save_still_works_after_accessing_nil_has_one + jp = Company.new :name => 'Jaded Pixel' + jp.dummy_account.nil? + + assert_nothing_raised do + jp.save! + end + end + +end + + +class HasManyAssociationsTest < ActiveSupport::TestCase + fixtures :accounts, :companies, :developers, :projects, + :developers_projects, :topics, :authors, :comments + + def setup + Client.destroyed_client_ids.clear + end + + def force_signal37_to_load_all_clients_of_firm + companies(:first_firm).clients_of_firm.each {|f| } + end + + def test_counting_with_counter_sql + assert_equal 2, Firm.find(:first).clients.count + end + + def test_counting + assert_equal 2, Firm.find(:first).plain_clients.count + end + + def test_counting_with_single_conditions + assert_equal 2, Firm.find(:first).plain_clients.count(:conditions => '1=1') + end + + def test_counting_with_single_hash + assert_equal 2, Firm.find(:first).plain_clients.count(:conditions => '1=1') + end + + def test_counting_with_column_name_and_hash + assert_equal 2, Firm.find(:first).plain_clients.count(:all, :conditions => '1=1') + end + + def test_finding + assert_equal 2, Firm.find(:first).clients.length + end + + def test_find_many_with_merged_options + assert_equal 1, companies(:first_firm).limited_clients.size + assert_equal 1, companies(:first_firm).limited_clients.find(:all).size + assert_equal 2, companies(:first_firm).limited_clients.find(:all, :limit => nil).size + 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.find_by_type('Client') + end + + def test_dynamic_find_order_should_override_association_order + assert_equal companies(:first_client), companies(:first_firm).clients_sorted_desc.find(:first, :conditions => "type = 'Client'", :order => 'id') + assert_equal companies(:first_client), companies(:first_firm).clients_sorted_desc.find_by_type('Client', :order => 'id') + 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_order_should_override_association_order + assert_equal [companies(:first_client), companies(:second_client)], companies(:first_firm).clients_sorted_desc.find(:all, :conditions => "type = 'Client'", :order => 'id') + assert_equal [companies(:first_client), companies(:second_client)], companies(:first_firm).clients_sorted_desc.find_all_by_type('Client', :order => 'id') + 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_triple_equality + assert !(Array === Firm.find(:first).clients) + assert Firm.find(:first).clients === Array + end + + def test_finding_default_orders + assert_equal "Summit", Firm.find(:first).clients.first.name + end + + def test_finding_with_different_class_name_and_order + assert_equal "Microsoft", Firm.find(:first).clients_sorted_desc.first.name + end + + def test_finding_with_foreign_key + assert_equal "Microsoft", Firm.find(:first).clients_of_firm.first.name + end + + def test_finding_with_condition + assert_equal "Microsoft", Firm.find(:first).clients_like_ms.first.name + end + + def test_finding_with_condition_hash + assert_equal "Microsoft", Firm.find(:first).clients_like_ms_with_hash_conditions.first.name + end + + def test_finding_using_sql + firm = Firm.find(: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).clients_using_sql.size + end + + def test_counting_using_sql + assert_equal 1, Firm.find(:first).clients_using_counter_sql.size + assert Firm.find(:first).clients_using_counter_sql.any? + assert_equal 0, Firm.find(:first).clients_using_zero_counter_sql.size + assert !Firm.find(:first).clients_using_zero_counter_sql.any? + end + + def test_counting_non_existant_items_using_sql + assert_equal 0, Firm.find(:first).no_clients_using_counter_sql.size + end + + def test_belongs_to_sanity + c = Client.new + assert_nil c.firm + + if c.firm + assert false, "belongs_to failed if check" + end + + unless c.firm + else + assert false, "belongs_to failed unless check" + end + end + + def test_find_ids + firm = Firm.find(:first) + + assert_raises(ActiveRecord::RecordNotFound) { firm.clients.find } + + client = firm.clients.find(2) + assert_kind_of Client, client + + client_ary = firm.clients.find([2]) + assert_kind_of Array, client_ary + assert_equal client, client_ary.first + + client_ary = firm.clients.find(2, 3) + assert_kind_of Array, client_ary + assert_equal 2, client_ary.size + assert_equal client, client_ary.first + + assert_raises(ActiveRecord::RecordNotFound) { firm.clients.find(2, 99) } + end + + def test_find_string_ids_when_using_finder_sql + firm = Firm.find(:first) + + client = firm.clients_using_finder_sql.find("2") + assert_kind_of Client, client + + client_ary = firm.clients_using_finder_sql.find(["2"]) + assert_kind_of Array, client_ary + assert_equal client, client_ary.first + + client_ary = firm.clients_using_finder_sql.find("2", "3") + assert_kind_of Array, client_ary + assert_equal 2, client_ary.size + assert client_ary.include?(client) + end + + def test_find_all + firm = Firm.find(:first) + assert_equal 2, firm.clients.find(:all, :conditions => "#{QUOTED_TYPE} = 'Client'").length + assert_equal 1, firm.clients.find(:all, :conditions => "name = 'Summit'").length + end + + def test_find_all_sanitized + firm = Firm.find(:first) + 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" }]) + end + + def test_find_first + firm = Firm.find(:first) + client2 = Client.find(2) + assert_equal firm.clients.first, firm.clients.find(:first) + assert_equal client2, firm.clients.find(:first, :conditions => "#{QUOTED_TYPE} = 'Client'") + end + + def test_find_first_sanitized + firm = Firm.find(:first) + client2 = Client.find(2) + assert_equal client2, firm.clients.find(:first, :conditions => ["#{QUOTED_TYPE} = ?", 'Client']) + assert_equal client2, firm.clients.find(:first, :conditions => ["#{QUOTED_TYPE} = :type", { :type => 'Client' }]) + end + + def test_find_in_collection + assert_equal Client.find(2).name, companies(:first_firm).clients.find(2).name + assert_raises(ActiveRecord::RecordNotFound) { companies(:first_firm).clients.find(6) } + 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') + assert_equal 2, all_clients_of_firm1.size + assert_equal 1, grouped_clients_of_firm1.size + end + + def test_adding + force_signal37_to_load_all_clients_of_firm + natural = Client.new("name" => "Natural Company") + companies(:first_firm).clients_of_firm << natural + assert_equal 2, companies(:first_firm).clients_of_firm.size # checking via the collection + assert_equal 2, companies(:first_firm).clients_of_firm(true).size # checking using the db + assert_equal natural, companies(:first_firm).clients_of_firm.last + end + + def test_adding_using_create + first_firm = companies(:first_firm) + assert_equal 2, first_firm.plain_clients.size + natural = first_firm.plain_clients.create(:name => "Natural Company") + assert_equal 3, first_firm.plain_clients.length + assert_equal 3, first_firm.plain_clients.size + end + + def test_create_with_bang_on_has_many_when_parent_is_new_raises + assert_raises(ActiveRecord::RecordNotSaved) do + firm = Firm.new + firm.plain_clients.create! :name=>"Whoever" + end + end + + def test_regular_create_on_has_many_when_parent_is_new_raises + assert_raises(ActiveRecord::RecordNotSaved) do + firm = Firm.new + firm.plain_clients.create :name=>"Whoever" + end + end + + def test_create_with_bang_on_has_many_raises_when_record_not_saved + assert_raises(ActiveRecord::RecordInvalid) do + firm = Firm.find(:first) + firm.plain_clients.create! + end + end + + def test_create_with_bang_on_habtm_when_parent_is_new_raises + assert_raises(ActiveRecord::RecordNotSaved) do + Developer.new("name" => "Aredridel").projects.create! + end + end + + def test_adding_a_mismatch_class + assert_raises(ActiveRecord::AssociationTypeMismatch) { companies(:first_firm).clients_of_firm << nil } + assert_raises(ActiveRecord::AssociationTypeMismatch) { companies(:first_firm).clients_of_firm << 1 } + assert_raises(ActiveRecord::AssociationTypeMismatch) { companies(:first_firm).clients_of_firm << Topic.find(1) } + end + + def test_adding_a_collection + force_signal37_to_load_all_clients_of_firm + companies(:first_firm).clients_of_firm.concat([Client.new("name" => "Natural Company"), Client.new("name" => "Apple")]) + assert_equal 3, companies(:first_firm).clients_of_firm.size + assert_equal 3, companies(:first_firm).clients_of_firm(true).size + end + + def test_adding_before_save + no_of_firms = Firm.count + no_of_clients = Client.count + + new_firm = Firm.new("name" => "A New Firm, Inc") + c = Client.new("name" => "Apple") + + new_firm.clients_of_firm.push Client.new("name" => "Natural Company") + assert_equal 1, new_firm.clients_of_firm.size + new_firm.clients_of_firm << c + assert_equal 2, new_firm.clients_of_firm.size + + assert_equal no_of_firms, Firm.count # Firm was not saved to database. + assert_equal no_of_clients, Client.count # Clients were not saved to database. + assert new_firm.save + assert !new_firm.new_record? + assert !c.new_record? + assert_equal new_firm, c.firm + assert_equal no_of_firms+1, Firm.count # Firm was saved to database. + assert_equal no_of_clients+2, Client.count # Clients were saved to database. + + assert_equal 2, new_firm.clients_of_firm.size + assert_equal 2, new_firm.clients_of_firm(true).size + end + + def test_invalid_adding + firm = Firm.find(1) + assert !(firm.clients_of_firm << c = Client.new) + assert c.new_record? + assert !firm.valid? + assert !firm.save + assert c.new_record? + end + + def test_invalid_adding_before_save + no_of_firms = Firm.count + no_of_clients = Client.count + new_firm = Firm.new("name" => "A New Firm, Inc") + new_firm.clients_of_firm.concat([c = Client.new, Client.new("name" => "Apple")]) + assert c.new_record? + assert !c.valid? + assert !new_firm.valid? + assert !new_firm.save + assert c.new_record? + assert new_firm.new_record? + end + + def test_build + new_client = companies(:first_firm).clients_of_firm.build("name" => "Another Client") + assert_equal "Another Client", new_client.name + assert new_client.new_record? + assert_equal new_client, companies(:first_firm).clients_of_firm.last + assert companies(:first_firm).save + assert !new_client.new_record? + assert_equal 2, companies(:first_firm).clients_of_firm(true).size + end + + def test_build_many + new_clients = companies(:first_firm).clients_of_firm.build([{"name" => "Another Client"}, {"name" => "Another Client II"}]) + assert_equal 2, new_clients.size + + assert companies(:first_firm).save + assert_equal 3, companies(:first_firm).clients_of_firm(true).size + end + + def test_build_followed_by_save_does_not_load_target + new_client = companies(:first_firm).clients_of_firm.build("name" => "Another Client") + assert companies(:first_firm).save + assert !companies(:first_firm).clients_of_firm.loaded? + end + + def test_build_without_loading_association + first_topic = topics(:first) + Reply.column_names + + assert_equal 1, first_topic.replies.length + + assert_no_queries do + first_topic.replies.build(:title => "Not saved", :content => "Superstars") + assert_equal 2, first_topic.replies.size + end + + assert_equal 2, first_topic.replies.to_ary.size + end + + def test_create_without_loading_association + first_firm = companies(:first_firm) + Firm.column_names + Client.column_names + + assert_equal 1, first_firm.clients_of_firm.size + first_firm.clients_of_firm.reset + + assert_queries(1) do + first_firm.clients_of_firm.create(:name => "Superstars") + end + + assert_equal 2, first_firm.clients_of_firm.size + end + + def test_invalid_build + new_client = companies(:first_firm).clients_of_firm.build + assert new_client.new_record? + assert !new_client.valid? + assert_equal new_client, companies(:first_firm).clients_of_firm.last + assert !companies(:first_firm).save + assert new_client.new_record? + assert_equal 1, companies(:first_firm).clients_of_firm(true).size + end + + def test_create + force_signal37_to_load_all_clients_of_firm + new_client = companies(:first_firm).clients_of_firm.create("name" => "Another Client") + assert !new_client.new_record? + assert_equal new_client, companies(:first_firm).clients_of_firm.last + assert_equal new_client, companies(:first_firm).clients_of_firm(true).last + end + + def test_create_many + companies(:first_firm).clients_of_firm.create([{"name" => "Another Client"}, {"name" => "Another Client II"}]) + assert_equal 3, companies(:first_firm).clients_of_firm(true).size + end + + def test_create_followed_by_save_does_not_load_target + new_client = companies(:first_firm).clients_of_firm.create("name" => "Another Client") + assert companies(:first_firm).save + 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.new_record? + end + + def test_find_or_create + 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_deleting + force_signal37_to_load_all_clients_of_firm + companies(:first_firm).clients_of_firm.delete(companies(:first_firm).clients_of_firm.first) + assert_equal 0, companies(:first_firm).clients_of_firm.size + assert_equal 0, companies(:first_firm).clients_of_firm(true).size + end + + def test_deleting_before_save + new_firm = Firm.new("name" => "A New Firm, Inc.") + new_client = new_firm.clients_of_firm.build("name" => "Another Client") + assert_equal 1, new_firm.clients_of_firm.size + new_firm.clients_of_firm.delete(new_client) + assert_equal 0, new_firm.clients_of_firm.size + end + + def test_deleting_a_collection + force_signal37_to_load_all_clients_of_firm + companies(:first_firm).clients_of_firm.create("name" => "Another Client") + assert_equal 2, companies(:first_firm).clients_of_firm.size + companies(:first_firm).clients_of_firm.delete([companies(:first_firm).clients_of_firm[0], companies(:first_firm).clients_of_firm[1]]) + assert_equal 0, companies(:first_firm).clients_of_firm.size + assert_equal 0, companies(:first_firm).clients_of_firm(true).size + end + + def test_delete_all + force_signal37_to_load_all_clients_of_firm + companies(:first_firm).clients_of_firm.create("name" => "Another Client") + assert_equal 2, companies(:first_firm).clients_of_firm.size + companies(:first_firm).clients_of_firm.delete_all + assert_equal 0, companies(:first_firm).clients_of_firm.size + assert_equal 0, companies(:first_firm).clients_of_firm(true).size + end + + def test_delete_all_with_not_yet_loaded_association_collection + force_signal37_to_load_all_clients_of_firm + companies(:first_firm).clients_of_firm.create("name" => "Another Client") + assert_equal 2, companies(:first_firm).clients_of_firm.size + companies(:first_firm).clients_of_firm.reset + companies(:first_firm).clients_of_firm.delete_all + assert_equal 0, companies(:first_firm).clients_of_firm.size + assert_equal 0, companies(:first_firm).clients_of_firm(true).size + end + + def test_clearing_an_association_collection + firm = companies(:first_firm) + client_id = firm.clients_of_firm.first.id + assert_equal 1, firm.clients_of_firm.size + + 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] + + # Should not be destroyed since the association is not dependent. + assert_nothing_raised do + assert Client.find(client_id).firm.nil? + end + end + + def test_clearing_a_dependent_association_collection + firm = companies(:first_firm) + client_id = firm.dependent_clients_of_firm.first.id + assert_equal 1, firm.dependent_clients_of_firm.size + + # :dependent means destroy is called on each client + firm.dependent_clients_of_firm.clear + + assert_equal 0, firm.dependent_clients_of_firm.size + assert_equal 0, firm.dependent_clients_of_firm(true).size + assert_equal [client_id], Client.destroyed_client_ids[firm.id] + + # Should be destroyed since the association is dependent. + assert Client.find_by_id(client_id).nil? + end + + def test_clearing_an_exclusively_dependent_association_collection + firm = companies(:first_firm) + client_id = firm.exclusively_dependent_clients_of_firm.first.id + assert_equal 1, firm.exclusively_dependent_clients_of_firm.size + + assert_equal [], Client.destroyed_client_ids[firm.id] + + # :exclusively_dependent means each client is deleted directly from + # the database without looping through them calling destroy. + firm.exclusively_dependent_clients_of_firm.clear + + assert_equal 0, firm.exclusively_dependent_clients_of_firm.size + assert_equal 0, firm.exclusively_dependent_clients_of_firm(true).size + # no destroy-filters should have been called + assert_equal [], Client.destroyed_client_ids[firm.id] + + # Should be destroyed since the association is exclusively dependent. + assert Client.find_by_id(client_id).nil? + end + + def test_dependent_association_respects_optional_conditions_on_delete + firm = companies(:odegy) + 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 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 + end + + def test_dependent_association_respects_optional_sanitized_conditions_on_delete + firm = companies(:odegy) + 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 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 + end + + def test_clearing_without_initial_access + firm = companies(:first_firm) + + firm.clients_of_firm.clear + + assert_equal 0, firm.clients_of_firm.size + assert_equal 0, firm.clients_of_firm(true).size + end + + def test_deleting_a_item_which_is_not_in_the_collection + force_signal37_to_load_all_clients_of_firm + summit = Client.find_by_name('Summit') + companies(:first_firm).clients_of_firm.delete(summit) + assert_equal 1, companies(:first_firm).clients_of_firm.size + assert_equal 1, companies(:first_firm).clients_of_firm(true).size + assert_equal 2, summit.client_of + end + + def test_deleting_type_mismatch + david = Developer.find(1) + david.projects.reload + assert_raises(ActiveRecord::AssociationTypeMismatch) { david.projects.delete(1) } + end + + def test_deleting_self_type_mismatch + david = Developer.find(1) + david.projects.reload + assert_raises(ActiveRecord::AssociationTypeMismatch) { david.projects.delete(Project.find(1).developers) } + end + + def test_destroy_all + force_signal37_to_load_all_clients_of_firm + assert !companies(:first_firm).clients_of_firm.empty?, "37signals has clients after load" + companies(:first_firm).clients_of_firm.destroy_all + assert companies(:first_firm).clients_of_firm.empty?, "37signals has no clients after destroy all" + assert companies(:first_firm).clients_of_firm(true).empty?, "37signals has no clients after destroy all and refresh" + end + + def test_dependence + firm = companies(:first_firm) + assert_equal 2, firm.clients.size + firm.destroy + assert Client.find(:all, :conditions => "firm_id=#{firm.id}").empty? + end + + def test_destroy_dependent_when_deleted_from_association + firm = Firm.find(:first) + assert_equal 2, firm.clients.size + + client = firm.clients.first + firm.clients.delete(client) + + assert_raise(ActiveRecord::RecordNotFound) { Client.find(client.id) } + assert_raise(ActiveRecord::RecordNotFound) { firm.clients.find(client.id) } + assert_equal 1, firm.clients.size + end + + def test_three_levels_of_dependence + topic = Topic.create "title" => "neat and simple" + reply = topic.replies.create "title" => "neat and simple", "content" => "still digging it" + silly_reply = reply.replies.create "title" => "neat and simple", "content" => "ain't complaining" + + assert_nothing_raised { topic.destroy } + end + + uses_transaction :test_dependence_with_transaction_support_on_failure + def test_dependence_with_transaction_support_on_failure + firm = companies(:first_firm) + clients = firm.clients + assert_equal 2, clients.length + clients.last.instance_eval { def before_destroy() raise "Trigger rollback" end } + + firm.destroy rescue "do nothing" + + assert_equal 2, Client.find(:all, :conditions => "firm_id=#{firm.id}").size + end + + def test_dependence_on_account + num_accounts = Account.count + companies(:first_firm).destroy + assert_equal num_accounts - 1, Account.count + end + + def test_depends_and_nullify + num_accounts = Account.count + num_companies = Company.count + + core = companies(:rails_core) + assert_equal accounts(:rails_core_account), core.account + assert_equal companies(:leetsoft, :jadedpixel), core.companies + core.destroy + assert_nil accounts(:rails_core_account).reload.firm_id + assert_nil companies(:leetsoft).reload.client_of + assert_nil companies(:jadedpixel).reload.client_of + + + assert_equal num_accounts, Account.count + end + + def test_included_in_collection + assert companies(:first_firm).clients.include?(Client.find(2)) + 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 + end + + def test_replace_with_less + firm = Firm.find(:first) + firm.clients = [companies(:first_client)] + assert firm.save, "Could not save firm" + firm.reload + assert_equal 1, firm.clients.length + end + + def test_replace_with_less_and_dependent_nullify + num_companies = Company.count + companies(:rails_core).companies = [] + assert_equal num_companies, Company.count + end + + def test_replace_with_new + firm = Firm.find(:first) + firm.clients = [companies(:second_client), Client.new("name" => "New Client")] + firm.save + firm.reload + assert_equal 2, firm.clients.length + assert !firm.clients.include?(:first_client) + end + + def test_replace_on_new_object + firm = Firm.new("name" => "New Firm") + firm.clients = [companies(:second_client), Client.new("name" => "New Client")] + assert firm.save + firm.reload + assert_equal 2, firm.clients.length + assert firm.clients.include?(Client.find_by_name("New Client")) + end + + def test_get_ids + assert_equal [companies(:first_client).id, companies(:second_client).id], companies(:first_firm).client_ids + end + + def test_assign_ids + firm = Firm.new("name" => "Apple") + firm.client_ids = [companies(:first_client).id, companies(:second_client).id] + firm.save + firm.reload + assert_equal 2, firm.clients.length + assert firm.clients.include?(companies(:second_client)) + end + + def test_assign_ids_ignoring_blanks + firm = Firm.create!(:name => 'Apple') + firm.client_ids = [companies(:first_client).id, nil, companies(:second_client).id, ''] + firm.save! + + assert_equal 2, firm.clients(true).size + assert firm.clients.include?(companies(:second_client)) + end + + def test_get_ids_for_through + assert_equal [comments(:eager_other_comment1).id], authors(:mary).comment_ids + end + + def test_assign_ids_for_through + assert_raise(NoMethodError) { authors(:mary).comment_ids = [123] } + 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.find_by_type('SpecialComment') + end + + def test_dynamic_find_order_should_override_association_order_for_through + assert_equal Comment.find(3), authors(:david).comments_desc.find(:first, :conditions => "comments.type = 'SpecialComment'", :order => 'comments.id') + assert_equal Comment.find(3), authors(:david).comments_desc.find_by_type('SpecialComment', :order => 'comments.id') + 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_order_should_override_association_order_for_through + assert_equal [Comment.find(3), Comment.find(6), Comment.find(7), Comment.find(10)], authors(:david).comments_desc.find(:all, :conditions => "comments.type = 'SpecialComment'", :order => 'comments.id') + assert_equal [Comment.find(3), Comment.find(6), Comment.find(7), Comment.find(10)], authors(:david).comments_desc.find_all_by_type('SpecialComment', :order => 'comments.id') + 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 + +end + +class BelongsToAssociationsTest < ActiveSupport::TestCase + fixtures :accounts, :companies, :developers, :projects, :topics, + :developers_projects, :computers, :authors, :posts, :tags, :taggings + + def test_belongs_to + Client.find(3).firm.name + assert_equal companies(:first_firm).name, Client.find(3).firm.name + assert !Client.find(3).firm.nil?, "Microsoft should have a firm" + end + + def test_proxy_assignment + account = Account.find(1) + assert_nothing_raised { account.firm = account.firm } + end + + def test_triple_equality + assert Client.find(3).firm === Firm + assert Firm === Client.find(3).firm + end + + def test_type_mismatch + assert_raise(ActiveRecord::AssociationTypeMismatch) { Account.find(1).firm = 1 } + assert_raise(ActiveRecord::AssociationTypeMismatch) { Account.find(1).firm = Project.find(1) } + end + + def test_natural_assignment + apple = Firm.create("name" => "Apple") + citibank = Account.create("credit_limit" => 10) + citibank.firm = apple + assert_equal apple.id, citibank.firm_id + end + + def test_no_unexpected_aliasing + first_firm = companies(:first_firm) + another_firm = companies(:another_firm) + + citibank = Account.create("credit_limit" => 10) + citibank.firm = first_firm + original_proxy = citibank.firm + citibank.firm = another_firm + + assert_equal first_firm.object_id, original_proxy.object_id + assert_equal another_firm.object_id, citibank.firm.object_id + end + + def test_creating_the_belonging_object + citibank = Account.create("credit_limit" => 10) + apple = citibank.create_firm("name" => "Apple") + assert_equal apple, citibank.firm + citibank.save + citibank.reload + assert_equal apple, citibank.firm + end + + def test_building_the_belonging_object + citibank = Account.create("credit_limit" => 10) + apple = citibank.build_firm("name" => "Apple") + citibank.save + assert_equal apple.id, citibank.firm_id + end + + def test_natural_assignment_to_nil + client = Client.find(3) + client.firm = nil + client.save + assert_nil client.firm(true) + assert_nil client.client_of + end + + def test_with_different_class_name + assert_equal Company.find(1).name, Company.find(3).firm_with_other_name.name + assert_not_nil Company.find(3).firm_with_other_name, "Microsoft should have a firm" + end + + def test_with_condition + assert_equal Company.find(1).name, Company.find(3).firm_with_condition.name + assert_not_nil Company.find(3).firm_with_condition, "Microsoft should have a firm" + end + + def test_belongs_to_counter + debate = Topic.create("title" => "debate") + assert_equal 0, debate.send(:read_attribute, "replies_count"), "No replies yet" + + trash = debate.replies.create("title" => "blah!", "content" => "world around!") + assert_equal 1, Topic.find(debate.id).send(:read_attribute, "replies_count"), "First reply created" + + trash.destroy + assert_equal 0, Topic.find(debate.id).send(:read_attribute, "replies_count"), "First reply deleted" + end + + def test_belongs_to_counter_with_reassigning + t1 = Topic.create("title" => "t1") + t2 = Topic.create("title" => "t2") + r1 = Reply.new("title" => "r1", "content" => "r1") + r1.topic = t1 + + assert r1.save + assert_equal 1, Topic.find(t1.id).replies.size + assert_equal 0, Topic.find(t2.id).replies.size + + r1.topic = Topic.find(t2.id) + + assert r1.save + assert_equal 0, Topic.find(t1.id).replies.size + assert_equal 1, Topic.find(t2.id).replies.size + + r1.topic = nil + + assert_equal 0, Topic.find(t1.id).replies.size + assert_equal 0, Topic.find(t2.id).replies.size + + r1.topic = t1 + + assert_equal 1, Topic.find(t1.id).replies.size + assert_equal 0, Topic.find(t2.id).replies.size + + r1.destroy + + assert_equal 0, Topic.find(t1.id).replies.size + assert_equal 0, Topic.find(t2.id).replies.size + end + + def test_belongs_to_counter_after_save + topic = Topic.create!(:title => "monday night") + topic.replies.create!(:title => "re: monday night", :content => "football") + assert_equal 1, Topic.find(topic.id)[:replies_count] + + topic.save! + assert_equal 1, Topic.find(topic.id)[:replies_count] + end + + def test_belongs_to_counter_after_update_attributes + topic = Topic.create!(:title => "37s") + topic.replies.create!(:title => "re: 37s", :content => "rails") + assert_equal 1, Topic.find(topic.id)[:replies_count] + + topic.update_attributes(:title => "37signals") + assert_equal 1, Topic.find(topic.id)[:replies_count] + end + + def test_belongs_to_counter_after_save + topic = Topic.create("title" => "monday night") + topic.replies.create("title" => "re: monday night", "content" => "football") + assert_equal 1, Topic.find(topic.id).send(:read_attribute, "replies_count") + + topic.save + assert_equal 1, Topic.find(topic.id).send(:read_attribute, "replies_count") + end + + def test_belongs_to_counter_after_update_attributes + topic = Topic.create("title" => "37s") + topic.replies.create("title" => "re: 37s", "content" => "rails") + assert_equal 1, Topic.find(topic.id).send(:read_attribute, "replies_count") + + topic.update_attributes("title" => "37signals") + assert_equal 1, Topic.find(topic.id).send(:read_attribute, "replies_count") + end + + def test_assignment_before_parent_saved + client = Client.find(:first) + apple = Firm.new("name" => "Apple") + client.firm = apple + assert_equal apple, client.firm + assert apple.new_record? + assert client.save + assert apple.save + assert !apple.new_record? + assert_equal apple, client.firm + assert_equal apple, client.firm(true) + end + + def test_assignment_before_child_saved + final_cut = Client.new("name" => "Final Cut") + firm = Firm.find(1) + final_cut.firm = firm + assert final_cut.new_record? + assert final_cut.save + assert !final_cut.new_record? + assert !firm.new_record? + assert_equal firm, final_cut.firm + assert_equal firm, final_cut.firm(true) + end + + def test_assignment_before_either_saved + final_cut = Client.new("name" => "Final Cut") + apple = Firm.new("name" => "Apple") + final_cut.firm = apple + assert final_cut.new_record? + assert apple.new_record? + assert final_cut.save + assert !final_cut.new_record? + assert !apple.new_record? + assert_equal apple, final_cut.firm + assert_equal apple, final_cut.firm(true) + end + + def test_new_record_with_foreign_key_but_no_object + c = Client.new("firm_id" => 1) + assert_equal Firm.find(:first), c.firm_with_basic_id + end + + def test_forgetting_the_load_when_foreign_key_enters_late + c = Client.new + assert_nil c.firm_with_basic_id + + c.firm_id = 1 + assert_equal Firm.find(:first), c.firm_with_basic_id + 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" # ' + end + + def test_counter_cache + topic = Topic.create :title => "Zoom-zoom-zoom" + assert_equal 0, topic[:replies_count] + + reply = Reply.create(:title => "re: zoom", :content => "speedy quick!") + reply.topic = topic + + assert_equal 1, topic.reload[:replies_count] + assert_equal 1, topic.replies.size + + topic[:replies_count] = 15 + assert_equal 15, topic.replies.size + end + + def test_custom_counter_cache + reply = Reply.create(:title => "re: zoom", :content => "speedy quick!") + assert_equal 0, reply[:replies_count] + + silly = SillyReply.create(:title => "gaga", :content => "boo-boo") + silly.reply = reply + + assert_equal 1, reply.reload[:replies_count] + assert_equal 1, reply.replies.size + + reply[:replies_count] = 17 + assert_equal 17, reply.replies.size + end + + def test_store_two_association_with_one_save + num_orders = Order.count + num_customers = Customer.count + order = Order.new + + customer1 = order.billing = Customer.new + customer2 = order.shipping = Customer.new + assert order.save + assert_equal customer1, order.billing + assert_equal customer2, order.shipping + + order.reload + + assert_equal customer1, order.billing + assert_equal customer2, order.shipping + + assert_equal num_orders +1, Order.count + assert_equal num_customers +2, Customer.count + end + + + def test_store_association_in_two_relations_with_one_save + num_orders = Order.count + num_customers = Customer.count + order = Order.new + + customer = order.billing = order.shipping = Customer.new + assert order.save + assert_equal customer, order.billing + assert_equal customer, order.shipping + + order.reload + + assert_equal customer, order.billing + assert_equal customer, order.shipping + + assert_equal num_orders +1, Order.count + assert_equal num_customers +1, Customer.count + end + + def test_store_association_in_two_relations_with_one_save_in_existing_object + num_orders = Order.count + num_customers = Customer.count + order = Order.create + + customer = order.billing = order.shipping = Customer.new + assert order.save + assert_equal customer, order.billing + assert_equal customer, order.shipping + + order.reload + + assert_equal customer, order.billing + assert_equal customer, order.shipping + + assert_equal num_orders +1, Order.count + assert_equal num_customers +1, Customer.count + end + + def test_store_association_in_two_relations_with_one_save_in_existing_object_with_values + num_orders = Order.count + num_customers = Customer.count + order = Order.create + + customer = order.billing = order.shipping = Customer.new + assert order.save + assert_equal customer, order.billing + assert_equal customer, order.shipping + + order.reload + + customer = order.billing = order.shipping = Customer.new + + assert order.save + order.reload + + assert_equal customer, order.billing + assert_equal customer, order.shipping + + assert_equal num_orders +1, Order.count + assert_equal num_customers +2, Customer.count + end + + + def test_association_assignment_sticks + post = Post.find(:first) + + author1, author2 = Author.find(:all, :limit => 2) + assert_not_nil author1 + assert_not_nil author2 + + # make sure the association is loaded + post.author + + # set the association by id, directly + post.author_id = author2.id + + # save and reload + post.save! + post.reload + + # the author id of the post should be the id we set + assert_equal post.author_id, author2.id + end + +end + + +class ProjectWithAfterCreateHook < ActiveRecord::Base + set_table_name 'projects' + has_and_belongs_to_many :developers, + :class_name => "DeveloperForProjectWithAfterCreateHook", + :join_table => "developers_projects", + :foreign_key => "project_id", + :association_foreign_key => "developer_id" + + after_create :add_david + + def add_david + david = DeveloperForProjectWithAfterCreateHook.find_by_name('David') + david.projects << self + end +end + +class DeveloperForProjectWithAfterCreateHook < ActiveRecord::Base + set_table_name 'developers' + has_and_belongs_to_many :projects, + :class_name => "ProjectWithAfterCreateHook", + :join_table => "developers_projects", + :association_foreign_key => "project_id", + :foreign_key => "developer_id" +end + + +class HasAndBelongsToManyAssociationsTest < ActiveSupport::TestCase + fixtures :accounts, :companies, :categories, :posts, :categories_posts, :developers, :projects, :developers_projects + + def test_has_and_belongs_to_many + david = Developer.find(1) + + assert !david.projects.empty? + assert_equal 2, david.projects.size + + active_record = Project.find(1) + assert !active_record.developers.empty? + assert_equal 3, active_record.developers.size + 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 + action_controller = Project.find(2) + assert_equal 1, jamis.projects.size + assert_equal 1, action_controller.developers.size + + jamis.projects << action_controller + + assert_equal 2, jamis.projects.size + assert_equal 2, jamis.projects(true).size + assert_equal 2, action_controller.developers(true).size + end + + def test_adding_type_mismatch + jamis = Developer.find(2) + assert_raise(ActiveRecord::AssociationTypeMismatch) { jamis.projects << nil } + assert_raise(ActiveRecord::AssociationTypeMismatch) { jamis.projects << 1 } + end + + def test_adding_from_the_project + jamis = Developer.find(2) + action_controller = Project.find(2) + action_controller.developers.reload + assert_equal 1, jamis.projects.size + assert_equal 1, action_controller.developers.size + + action_controller.developers << jamis + + assert_equal 2, jamis.projects(true).size + assert_equal 2, action_controller.developers.size + assert_equal 2, action_controller.developers(true).size + end + + def test_adding_from_the_project_fixed_timestamp + jamis = Developer.find(2) + action_controller = Project.find(2) + action_controller.developers.reload + assert_equal 1, jamis.projects.size + assert_equal 1, action_controller.developers.size + updated_at = jamis.updated_at + + action_controller.developers << jamis + + assert_equal updated_at, jamis.updated_at + assert_equal 2, jamis.projects(true).size + assert_equal 2, action_controller.developers.size + assert_equal 2, action_controller.developers(true).size + end + + def test_adding_multiple + aredridel = Developer.new("name" => "Aredridel") + aredridel.save + aredridel.projects.reload + aredridel.projects.push(Project.find(1), Project.find(2)) + assert_equal 2, aredridel.projects.size + assert_equal 2, aredridel.projects(true).size + end + + def test_adding_a_collection + aredridel = Developer.new("name" => "Aredridel") + aredridel.save + aredridel.projects.reload + aredridel.projects.concat([Project.find(1), Project.find(2)]) + assert_equal 2, aredridel.projects.size + assert_equal 2, aredridel.projects(true).size + end + + def test_adding_uses_default_values_on_join_table + ac = projects(:action_controller) + assert !developers(:jamis).projects.include?(ac) + developers(:jamis).projects << ac + + assert developers(:jamis, :reload).projects.include?(ac) + project = developers(:jamis).projects.detect { |p| p == ac } + assert_equal 1, project.access_level.to_i + end + + def test_habtm_attribute_access_and_respond_to + project = developers(:jamis).projects[0] + assert project.has_attribute?("name") + assert project.has_attribute?("joined_on") + assert project.has_attribute?("access_level") + assert project.respond_to?("name") + assert project.respond_to?("name=") + assert project.respond_to?("name?") + assert project.respond_to?("joined_on") + # given that the 'join attribute' won't be persisted, I don't + # think we should define the mutators + #assert project.respond_to?("joined_on=") + assert project.respond_to?("joined_on?") + assert project.respond_to?("access_level") + #assert project.respond_to?("access_level=") + assert project.respond_to?("access_level?") + end + + def test_habtm_adding_before_save + no_of_devels = Developer.count + no_of_projects = Project.count + aredridel = Developer.new("name" => "Aredridel") + aredridel.projects.concat([Project.find(1), p = Project.new("name" => "Projekt")]) + assert aredridel.new_record? + assert p.new_record? + assert aredridel.save + assert !aredridel.new_record? + assert_equal no_of_devels+1, Developer.count + assert_equal no_of_projects+1, Project.count + assert_equal 2, aredridel.projects.size + assert_equal 2, aredridel.projects(true).size + end + + def test_habtm_saving_multiple_relationships + new_project = Project.new("name" => "Grimetime") + amount_of_developers = 4 + developers = (0...amount_of_developers).collect {|i| Developer.create(:name => "JME #{i}") }.reverse + + new_project.developer_ids = [developers[0].id, developers[1].id] + new_project.developers_with_callback_ids = [developers[2].id, developers[3].id] + assert new_project.save + + new_project.reload + assert_equal amount_of_developers, new_project.developers.size + assert_equal developers, new_project.developers + end + + def test_habtm_unique_order_preserved + assert_equal developers(:poor_jamis, :jamis, :david), projects(:active_record).non_unique_developers + assert_equal developers(:poor_jamis, :jamis, :david), projects(:active_record).developers + end + + def test_build + devel = Developer.find(1) + proj = devel.projects.build("name" => "Projekt") + assert_equal devel.projects.last, proj + assert proj.new_record? + devel.save + assert !proj.new_record? + assert_equal devel.projects.last, proj + assert_equal Developer.find(1).projects.sort_by(&:id).last, proj # prove join table is updated + end + + def test_build_by_new_record + devel = Developer.new(:name => "Marcel", :salary => 75000) + proj1 = devel.projects.build(:name => "Make bed") + proj2 = devel.projects.build(:name => "Lie in it") + assert_equal devel.projects.last, proj2 + assert proj2.new_record? + devel.save + assert !devel.new_record? + assert !proj2.new_record? + assert_equal devel.projects.last, proj2 + assert_equal Developer.find_by_name("Marcel").projects.last, proj2 # prove join table is updated + end + + def test_create + devel = Developer.find(1) + proj = devel.projects.create("name" => "Projekt") + assert_equal devel.projects.last, proj + assert !proj.new_record? + assert_equal Developer.find(1).projects.sort_by(&:id).last, proj # prove join table is updated + end + + def test_create_by_new_record + devel = Developer.new(:name => "Marcel", :salary => 75000) + proj1 = devel.projects.build(:name => "Make bed") + proj2 = devel.projects.build(:name => "Lie in it") + assert_equal devel.projects.last, proj2 + assert proj2.new_record? + devel.save + assert !devel.new_record? + assert !proj2.new_record? + assert_equal devel.projects.last, proj2 + assert_equal Developer.find_by_name("Marcel").projects.last, proj2 # prove join table is updated + end + + def test_uniq_after_the_fact + developers(:jamis).projects << projects(:active_record) + developers(:jamis).projects << projects(:active_record) + assert_equal 3, developers(:jamis).projects.size + assert_equal 1, developers(:jamis).projects.uniq.size + end + + def test_uniq_before_the_fact + projects(:active_record).developers << developers(:jamis) + projects(:active_record).developers << developers(:david) + assert_equal 3, projects(:active_record, :reload).developers.size + end + + def test_deleting + david = Developer.find(1) + active_record = Project.find(1) + david.projects.reload + assert_equal 2, david.projects.size + assert_equal 3, active_record.developers.size + + david.projects.delete(active_record) + + assert_equal 1, david.projects.size + assert_equal 1, david.projects(true).size + assert_equal 2, active_record.developers(true).size + end + + def test_deleting_array + david = Developer.find(1) + david.projects.reload + david.projects.delete(Project.find(:all)) + assert_equal 0, david.projects.size + assert_equal 0, david.projects(true).size + end + + def test_deleting_with_sql + david = Developer.find(1) + active_record = Project.find(1) + active_record.developers.reload + assert_equal 3, active_record.developers_by_sql.size + + active_record.developers_by_sql.delete(david) + assert_equal 2, active_record.developers_by_sql(true).size + end + + def test_deleting_array_with_sql + active_record = Project.find(1) + active_record.developers.reload + assert_equal 3, active_record.developers_by_sql.size + + active_record.developers_by_sql.delete(Developer.find(:all)) + assert_equal 0, active_record.developers_by_sql(true).size + end + + def test_deleting_all + david = Developer.find(1) + david.projects.reload + david.projects.clear + assert_equal 0, david.projects.size + assert_equal 0, david.projects(true).size + end + + def test_removing_associations_on_destroy + david = DeveloperWithBeforeDestroyRaise.find(1) + assert !david.projects.empty? + assert_nothing_raised { david.destroy } + assert david.projects.empty? + assert DeveloperWithBeforeDestroyRaise.connection.select_all("SELECT * FROM developers_projects WHERE developer_id = 1").empty? + end + + def test_additional_columns_from_join_table + assert_date_from_db Date.new(2004, 10, 10), Developer.find(1).projects.first.joined_on.to_date + end + + def test_destroy_all + david = Developer.find(1) + david.projects.reload + assert !david.projects.empty? + david.projects.destroy_all + assert david.projects.empty? + assert david.projects(true).empty? + end + + def test_deprecated_push_with_attributes_was_removed + jamis = developers(:jamis) + assert_raise(NoMethodError) do + jamis.projects.push_with_attributes(projects(:action_controller), :joined_on => Date.today) + end + end + + def test_associations_with_conditions + assert_equal 3, projects(:active_record).developers.size + assert_equal 1, projects(:active_record).developers_named_david.size + assert_equal 1, projects(:active_record).developers_named_david_with_hash_conditions.size + + assert_equal developers(:david), projects(:active_record).developers_named_david.find(developers(:david).id) + assert_equal developers(:david), projects(:active_record).developers_named_david_with_hash_conditions.find(developers(:david).id) + assert_equal developers(:david), projects(:active_record).salaried_developers.find(developers(:david).id) + + projects(:active_record).developers_named_david.clear + assert_equal 2, projects(:active_record, :reload).developers.size + end + + def test_find_in_association + # Using sql + assert_equal developers(:david), projects(:active_record).developers.find(developers(:david).id), "SQL find" + + # Using ruby + active_record = projects(:active_record) + active_record.developers.reload + assert_equal developers(:david), active_record.developers.find(developers(:david).id), "Ruby find" + end + + def test_find_in_association_with_custom_finder_sql + assert_equal developers(:david), projects(:active_record).developers_with_finder_sql.find(developers(:david).id), "SQL find" + + active_record = projects(:active_record) + active_record.developers_with_finder_sql.reload + assert_equal developers(:david), active_record.developers_with_finder_sql.find(developers(:david).id), "Ruby find" + end + + def test_find_in_association_with_custom_finder_sql_and_string_id + assert_equal developers(:david), projects(:active_record).developers_with_finder_sql.find(developers(:david).id.to_s), "SQL find" + end + + 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 + end + + def test_dynamic_find_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, projects(:active_record).developers.find(:first, :conditions => "name = 'Jamis'") + assert_equal high_id_jamis, projects(:active_record).developers.find_by_name('Jamis') + end + + def test_dynamic_find_order_should_override_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 low_id_jamis, projects(:active_record).developers.find(:first, :conditions => "name = 'Jamis'", :order => 'id') + assert_equal low_id_jamis, projects(:active_record).developers.find_by_name('Jamis', :order => 'id') + 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_dynamic_find_all_order_should_override_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 [low_id_jamis, middle_id_jamis, high_id_jamis], projects(:active_record).developers.find(:all, :conditions => "name = 'Jamis'", :order => 'id') + assert_equal [low_id_jamis, middle_id_jamis, high_id_jamis], projects(:active_record).developers.find_all_by_name('Jamis', :order => 'id') + 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_new_with_values_in_collection + jamis = DeveloperForProjectWithAfterCreateHook.find_by_name('Jamis') + david = DeveloperForProjectWithAfterCreateHook.find_by_name('David') + project = ProjectWithAfterCreateHook.new(:name => "Cooking with Bertie") + project.developers << jamis + project.save! + project.reload + + assert project.developers.include?(jamis) + assert project.developers.include?(david) + end + + def test_find_in_association_with_options + developers = projects(:active_record).developers.find(:all) + assert_equal 3, developers.size + + assert_equal developers(:poor_jamis), projects(:active_record).developers.find(:first, :conditions => "salary < 10000") + assert_equal developers(:jamis), projects(:active_record).developers.find(:first, :order => "salary DESC") + end + + def test_replace_with_less + david = developers(:david) + david.projects = [projects(:action_controller)] + assert david.save + assert_equal 1, david.projects.length + end + + def test_replace_with_new + david = developers(:david) + david.projects = [projects(:action_controller), Project.new("name" => "ActionWebSearch")] + david.save + assert_equal 2, david.projects.length + assert !david.projects.include?(projects(:active_record)) + end + + def test_replace_on_new_object + new_developer = Developer.new("name" => "Matz") + new_developer.projects = [projects(:action_controller), Project.new("name" => "ActionWebSearch")] + new_developer.save + assert_equal 2, new_developer.projects.length + end + + def test_consider_type + developer = Developer.find(:first) + special_project = SpecialProject.create("name" => "Special Project") + + other_project = developer.projects.first + developer.special_projects << special_project + developer.reload + + assert developer.projects.include?(special_project) + assert developer.special_projects.include?(special_project) + assert !developer.special_projects.include?(other_project) + end + + def test_update_attributes_after_push_without_duplicate_join_table_rows + developer = Developer.new("name" => "Kano") + project = SpecialProject.create("name" => "Special Project") + assert developer.save + developer.projects << project + developer.update_attribute("name", "Bruza") + assert_equal 1, Developer.connection.select_value(<<-end_sql).to_i + SELECT count(*) FROM developers_projects + WHERE project_id = #{project.id} + AND developer_id = #{developer.id} + end_sql + end + + def test_updating_attributes_on_non_rich_associations + welcome = categories(:technology).posts.first + welcome.title = "Something else" + assert welcome.save! + end + + def test_habtm_respects_select + 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 + end + + def test_updating_attributes_on_rich_associations + david = projects(:action_controller).developers.first + david.name = "DHH" + assert_raises(ActiveRecord::ReadOnlyRecord) { david.save! } + end + + def test_updating_attributes_on_rich_associations_with_limited_find_from_reflection + david = projects(:action_controller).selected_developers.first + david.name = "DHH" + assert_nothing_raised { david.save! } + end + + + def test_updating_attributes_on_rich_associations_with_limited_find + david = projects(:action_controller).developers.find(:all, :select => "developers.*").first + david.name = "DHH" + assert david.save! + 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 + end + + def test_join_with_group + group = Developer.columns.inject([]) do |g, c| + g << "developers.#{c.name}" + g << "developers_projects_2.#{c.name}" + 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 + end + + def test_get_ids + assert_equal projects(:active_record, :action_controller).map(&:id).sort, developers(:david).project_ids.sort + assert_equal [projects(:active_record).id], developers(:jamis).project_ids + end + + def test_assign_ids + developer = Developer.new("name" => "Joe") + developer.project_ids = projects(:active_record, :action_controller).map(&:id) + developer.save + developer.reload + assert_equal 2, developer.projects.length + assert_equal projects(:active_record), developer.projects[0] + assert_equal projects(:action_controller), developer.projects[1] + end + + def test_assign_ids_ignoring_blanks + developer = Developer.new("name" => "Joe") + developer.project_ids = [projects(:active_record).id, nil, projects(:action_controller).id, ''] + developer.save + developer.reload + assert_equal 2, developer.projects.length + assert_equal projects(:active_record), developer.projects[0] + assert_equal projects(:action_controller), developer.projects[1] + end + + def test_select_limited_ids_list + # Set timestamps + Developer.transaction do + Developer.find(:all, :order => 'id').each_with_index do |record, i| + record.update_attributes(:created_at => 5.years.ago + (i * 5.minutes)) + end + end + + join_base = ActiveRecord::Associations::ClassMethods::JoinDependency::JoinBase.new(Project) + join_dep = ActiveRecord::Associations::ClassMethods::JoinDependency.new(join_base, :developers, nil) + projects = Project.send(:select_limited_ids_list, {:order => 'developers.created_at'}, join_dep) + assert !projects.include?("'"), projects + assert_equal %w(1 2), projects.scan(/\d/).sort + end + + def test_scoped_find_on_through_association_doesnt_return_read_only_records + tag = Post.find(1).tags.find_by_name("General") + + assert_nothing_raised do + tag.save! + end + end +end + + +class OverridingAssociationsTest < ActiveSupport::TestCase + class Person < ActiveRecord::Base; end + class DifferentPerson < ActiveRecord::Base; end + + class PeopleList < ActiveRecord::Base + has_and_belongs_to_many :has_and_belongs_to_many, :before_add => :enlist + has_many :has_many, :before_add => :enlist + belongs_to :belongs_to + has_one :has_one + end + + class DifferentPeopleList < PeopleList + # Different association with the same name, callbacks should be omitted here. + has_and_belongs_to_many :has_and_belongs_to_many, :class_name => 'DifferentPerson' + has_many :has_many, :class_name => 'DifferentPerson' + belongs_to :belongs_to, :class_name => 'DifferentPerson' + has_one :has_one, :class_name => 'DifferentPerson' + end + + def test_habtm_association_redefinition_callbacks_should_differ_and_not_inherited + # redeclared association on AR descendant should not inherit callbacks from superclass + callbacks = PeopleList.read_inheritable_attribute(:before_add_for_has_and_belongs_to_many) + assert_equal([:enlist], callbacks) + callbacks = DifferentPeopleList.read_inheritable_attribute(:before_add_for_has_and_belongs_to_many) + assert_equal([], callbacks) + end + + def test_has_many_association_redefinition_callbacks_should_differ_and_not_inherited + # redeclared association on AR descendant should not inherit callbacks from superclass + callbacks = PeopleList.read_inheritable_attribute(:before_add_for_has_many) + assert_equal([:enlist], callbacks) + callbacks = DifferentPeopleList.read_inheritable_attribute(:before_add_for_has_many) + assert_equal([], callbacks) + end + + def test_habtm_association_redefinition_reflections_should_differ_and_not_inherited + assert_not_equal( + PeopleList.reflect_on_association(:has_and_belongs_to_many), + DifferentPeopleList.reflect_on_association(:has_and_belongs_to_many) + ) + end + + def test_has_many_association_redefinition_reflections_should_differ_and_not_inherited + assert_not_equal( + PeopleList.reflect_on_association(:has_many), + DifferentPeopleList.reflect_on_association(:has_many) + ) + end + + def test_belongs_to_association_redefinition_reflections_should_differ_and_not_inherited + assert_not_equal( + PeopleList.reflect_on_association(:belongs_to), + DifferentPeopleList.reflect_on_association(:belongs_to) + ) + end + + def test_has_one_association_redefinition_reflections_should_differ_and_not_inherited + assert_not_equal( + PeopleList.reflect_on_association(:has_one), + DifferentPeopleList.reflect_on_association(:has_one) + ) + end +end diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb new file mode 100755 index 0000000000..4a09a4d63e --- /dev/null +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -0,0 +1,146 @@ +require 'abstract_unit' +require 'fixtures/topic' + +class AttributeMethodsTest < ActiveSupport::TestCase + fixtures :topics + def setup + @old_suffixes = ActiveRecord::Base.send(:attribute_method_suffixes).dup + @target = Class.new(ActiveRecord::Base) + @target.table_name = 'topics' + end + + def teardown + ActiveRecord::Base.send(:attribute_method_suffixes).clear + ActiveRecord::Base.attribute_method_suffix *@old_suffixes + end + + + def test_match_attribute_method_query_returns_match_data + assert_not_nil md = @target.match_attribute_method?('title=') + assert_equal 'title', md.pre_match + assert_equal ['='], md.captures + + %w(_hello_world ist! _maybe?).each do |suffix| + @target.class_eval "def attribute#{suffix}(*args) args end" + @target.attribute_method_suffix suffix + + assert_not_nil md = @target.match_attribute_method?("title#{suffix}") + assert_equal 'title', md.pre_match + assert_equal [suffix], md.captures + end + end + + def test_declared_attribute_method_affects_respond_to_and_method_missing + topic = @target.new(:title => 'Budget') + assert topic.respond_to?('title') + assert_equal 'Budget', topic.title + assert !topic.respond_to?('title_hello_world') + assert_raise(NoMethodError) { topic.title_hello_world } + + %w(_hello_world _it! _candidate= able?).each do |suffix| + @target.class_eval "def attribute#{suffix}(*args) args end" + @target.attribute_method_suffix suffix + + meth = "title#{suffix}" + assert topic.respond_to?(meth) + assert_equal ['title'], topic.send(meth) + assert_equal ['title', 'a'], topic.send(meth, 'a') + assert_equal ['title', 1, 2, 3], topic.send(meth, 1, 2, 3) + end + end + + def test_should_unserialize_attributes_for_frozen_records + myobj = {:value1 => :value2} + topic = Topic.create("content" => myobj) + topic.freeze + assert_equal myobj, topic.content + end + + def test_kernel_methods_not_implemented_in_activerecord + %w(test name display y).each do |method| + assert_equal false, ActiveRecord::Base.instance_method_already_implemented?(method), "##{method} is defined" + end + end + + def test_primary_key_implemented + assert_equal true, Class.new(ActiveRecord::Base).instance_method_already_implemented?('id') + 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_equal true, 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_equal true, 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 + klass.class_eval "def #{method}() 'defined #{method}' end" + assert_raises ActiveRecord::DangerousAttributeError do + klass.instance_method_already_implemented?(method) + end + end + end + + def test_only_time_related_columns_are_meant_to_be_cached_by_default + expected = %w(datetime timestamp time date).sort + assert_equal expected, ActiveRecord::Base.attribute_types_cached_by_default.map(&:to_s).sort +end + + def test_declaring_attributes_as_cached_adds_them_to_the_attributes_cached_by_default + default_attributes = Topic.cached_attributes + Topic.cache_attributes :replies_count + expected = default_attributes + ["replies_count"] + assert_equal expected.sort, Topic.cached_attributes.sort + Topic.instance_variable_set "@cached_attributes", nil + end + + def test_time_related_columns_are_actually_cached + column_types = %w(datetime timestamp time date).map(&:to_sym) + column_names = Topic.columns.select{|c| column_types.include?(c.type) }.map(&:name) + + assert_equal column_names.sort, Topic.cached_attributes.sort + assert_equal time_related_columns_on_topic.sort, Topic.cached_attributes.sort + end + + def test_accessing_cached_attributes_caches_the_converted_values_and_nothing_else + t = topics(:first) + cache = t.instance_variable_get "@attributes_cache" + + assert_not_nil cache + assert cache.empty? + + all_columns = Topic.columns.map(&:name) + cached_columns = time_related_columns_on_topic + uncached_columns = all_columns - cached_columns + + all_columns.each do |attr_name| + attribute_gets_cached = Topic.cache_attribute?(attr_name) + val = t.send attr_name unless attr_name == "type" + if attribute_gets_cached + assert cached_columns.include?(attr_name) + assert_equal val, cache[attr_name] + else + assert uncached_columns.include?(attr_name) + assert !cache.include?(attr_name) + end + end + end + + private + def time_related_columns_on_topic + Topic.columns.select{|c| [:time, :date, :datetime, :timestamp].include?(c.type)}.map(&:name) + end +end diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb new file mode 100755 index 0000000000..00e563aaee --- /dev/null +++ b/activerecord/test/cases/base_test.rb @@ -0,0 +1,1831 @@ +require 'abstract_unit' +require 'fixtures/topic' +require 'fixtures/reply' +require 'fixtures/company' +require 'fixtures/customer' +require 'fixtures/developer' +require 'fixtures/project' +require 'fixtures/default' +require 'fixtures/auto_id' +require 'fixtures/column_name' +require 'fixtures/subscriber' +require 'fixtures/keyboard' +require 'fixtures/post' +require 'fixtures/minimalistic' +require 'fixtures/warehouse_thing' +require 'rexml/document' + +class Category < ActiveRecord::Base; end +class Smarts < ActiveRecord::Base; end +class CreditCard < ActiveRecord::Base + class PinNumber < ActiveRecord::Base + class CvvCode < ActiveRecord::Base; end + class SubCvvCode < CvvCode; end + end + class SubPinNumber < PinNumber; end + class Brand < Category; end +end +class MasterCreditCard < ActiveRecord::Base; end +class Post < ActiveRecord::Base; end +class Computer < ActiveRecord::Base; end +class NonExistentTable < ActiveRecord::Base; end +class TestOracleDefault < ActiveRecord::Base; end + +class LoosePerson < ActiveRecord::Base + self.table_name = 'people' + self.abstract_class = true + attr_protected :credit_rating, :administrator +end + +class LooseDescendant < LoosePerson + attr_protected :phone_number +end + +class LooseDescendantSecond< LoosePerson + attr_protected :phone_number + attr_protected :name +end + +class TightPerson < ActiveRecord::Base + self.table_name = 'people' + attr_accessible :name, :address +end + +class TightDescendant < TightPerson + attr_accessible :phone_number +end + +class ReadonlyTitlePost < Post + attr_readonly :title +end + +class Booleantest < ActiveRecord::Base; end + +class Task < ActiveRecord::Base + attr_protected :starting +end + +class TopicWithProtectedContentAndAccessibleAuthorName < ActiveRecord::Base + self.table_name = 'topics' + attr_accessible :author_name + attr_protected :content +end + +class BasicsTest < ActiveSupport::TestCase + fixtures :topics, :companies, :developers, :projects, :computers, :accounts, :minimalistics, 'warehouse-things' + + def test_table_exists + assert !NonExistentTable.table_exists? + assert Topic.table_exists? + end + + def test_set_attributes + topic = Topic.find(1) + topic.attributes = { "title" => "Budget", "author_name" => "Jason" } + topic.save + assert_equal("Budget", topic.title) + assert_equal("Jason", topic.author_name) + assert_equal(topics(:first).author_email_address, Topic.find(1).author_email_address) + end + + def test_integers_as_nil + test = AutoId.create('value' => '') + assert_nil AutoId.find(test.id).value + end + + def test_set_attributes_with_block + topic = Topic.new do |t| + t.title = "Budget" + t.author_name = "Jason" + end + + assert_equal("Budget", topic.title) + assert_equal("Jason", topic.author_name) + end + + def test_respond_to? + topic = Topic.find(1) + assert topic.respond_to?("title") + assert topic.respond_to?("title?") + assert topic.respond_to?("title=") + assert topic.respond_to?(:title) + assert topic.respond_to?(:title?) + assert topic.respond_to?(:title=) + assert topic.respond_to?("author_name") + assert topic.respond_to?("attribute_names") + assert !topic.respond_to?("nothingness") + assert !topic.respond_to?(:nothingness) + end + + def test_array_content + topic = Topic.new + topic.content = %w( one two three ) + topic.save + + assert_equal(%w( one two three ), Topic.find(topic.id).content) + end + + def test_hash_content + topic = Topic.new + topic.content = { "one" => 1, "two" => 2 } + topic.save + + assert_equal 2, Topic.find(topic.id).content["two"] + + topic.content["three"] = 3 + topic.save + + assert_equal 3, Topic.find(topic.id).content["three"] + end + + def test_update_array_content + topic = Topic.new + topic.content = %w( one two three ) + + topic.content.push "four" + assert_equal(%w( one two three four ), topic.content) + + topic.save + + topic = Topic.find(topic.id) + topic.content << "five" + assert_equal(%w( one two three four five ), topic.content) + end + + def test_case_sensitive_attributes_hash + # DB2 is not case-sensitive + return true if current_adapter?(:DB2Adapter) + + assert_equal @loaded_fixtures['computers']['workstation'].to_hash, Computer.find(:first).attributes + end + + def test_create + topic = Topic.new + topic.title = "New Topic" + topic.save + topic_reloaded = Topic.find(topic.id) + assert_equal("New Topic", topic_reloaded.title) + end + + def test_save! + topic = Topic.new(:title => "New Topic") + assert topic.save! + + reply = Reply.new + assert_raise(ActiveRecord::RecordInvalid) { reply.save! } + end + + def test_save_null_string_attributes + topic = Topic.find(1) + topic.attributes = { "title" => "null", "author_name" => "null" } + topic.save! + topic.reload + assert_equal("null", topic.title) + assert_equal("null", topic.author_name) + end + + def test_save_nil_string_attributes + topic = Topic.find(1) + topic.title = nil + topic.save! + topic.reload + assert_nil topic.title + end + + def test_save_for_record_with_only_primary_key + minimalistic = Minimalistic.new + assert_nothing_raised { minimalistic.save } + end + + def test_save_for_record_with_only_primary_key_that_is_provided + assert_nothing_raised { Minimalistic.create!(:id => 2) } + end + + def test_hashes_not_mangled + new_topic = { :title => "New Topic" } + new_topic_values = { :title => "AnotherTopic" } + + topic = Topic.new(new_topic) + assert_equal new_topic[:title], topic.title + + topic.attributes= new_topic_values + assert_equal new_topic_values[:title], topic.title + end + + def test_create_many + topics = Topic.create([ { "title" => "first" }, { "title" => "second" }]) + assert_equal 2, topics.size + assert_equal "first", topics.first.title + end + + def test_create_columns_not_equal_attributes + topic = Topic.new + topic.title = 'Another New Topic' + topic.send :write_attribute, 'does_not_exist', 'test' + assert_nothing_raised { topic.save } + end + + def test_create_through_factory + topic = Topic.create("title" => "New Topic") + topicReloaded = Topic.find(topic.id) + assert_equal(topic, topicReloaded) + end + + def test_update + topic = Topic.new + topic.title = "Another New Topic" + topic.written_on = "2003-12-12 23:23:00" + topic.save + topicReloaded = Topic.find(topic.id) + assert_equal("Another New Topic", topicReloaded.title) + + topicReloaded.title = "Updated topic" + topicReloaded.save + + topicReloadedAgain = Topic.find(topic.id) + + assert_equal("Updated topic", topicReloadedAgain.title) + end + + def test_update_columns_not_equal_attributes + topic = Topic.new + 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' + assert_nothing_raised { topicReloaded.save } + end + + def test_update_for_record_with_only_primary_key + minimalistic = minimalistics(:first) + assert_nothing_raised { minimalistic.save } + end + + def test_write_attribute + topic = Topic.new + 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") + assert_equal "Still another topic: part 2", topic.title + end + + def test_read_attribute + topic = Topic.new + topic.title = "Don't change the topic" + assert_equal "Don't change the topic", topic.send(:read_attribute, "title") + assert_equal "Don't change the topic", topic["title"] + + assert_equal "Don't change the topic", topic.send(:read_attribute, :title) + assert_equal "Don't change the topic", topic[:title] + end + + def test_read_attribute_when_false + topic = topics(:first) + topic.approved = false + assert !topic.approved?, "approved should be false" + topic.approved = "false" + assert !topic.approved?, "approved should be false" + end + + def test_read_attribute_when_true + topic = topics(:first) + topic.approved = true + assert topic.approved?, "approved should be true" + topic.approved = "true" + assert topic.approved?, "approved should be true" + end + + def test_read_write_boolean_attribute + topic = Topic.new + # puts "" + # puts "New Topic" + # puts topic.inspect + topic.approved = "false" + # puts "Expecting false" + # puts topic.inspect + assert !topic.approved?, "approved should be false" + topic.approved = "false" + # puts "Expecting false" + # puts topic.inspect + assert !topic.approved?, "approved should be false" + topic.approved = "true" + # puts "Expecting true" + # puts topic.inspect + assert topic.approved?, "approved should be true" + topic.approved = "true" + # puts "Expecting true" + # puts topic.inspect + assert topic.approved?, "approved should be true" + # puts "" + end + + def test_query_attribute_string + [nil, "", " "].each do |value| + assert_equal false, Topic.new(:author_name => value).author_name? + end + + assert_equal true, Topic.new(:author_name => "Name").author_name? + end + + def test_query_attribute_number + [nil, 0, "0"].each do |value| + assert_equal false, Developer.new(:salary => value).salary? + end + + assert_equal true, Developer.new(:salary => 1).salary? + assert_equal true, Developer.new(:salary => "1").salary? + end + + def test_query_attribute_boolean + [nil, "", false, "false", "f", 0].each do |value| + assert_equal false, Topic.new(:approved => value).approved? + end + + [true, "true", "1", 1].each do |value| + assert_equal true, Topic.new(:approved => value).approved? + end + end + + def test_query_attribute_with_custom_fields + object = Company.find_by_sql(<<-SQL).first + SELECT c1.*, c2.ruby_type as string_value, c2.rating as int_value + FROM companies c1, companies c2 + WHERE c1.firm_id = c2.id + AND c1.id = 2 + SQL + + assert_equal "Firm", object.string_value + assert object.string_value? + + object.string_value = " " + assert !object.string_value? + + assert_equal 1, object.int_value.to_i + assert object.int_value? + + object.int_value = "0" + assert !object.int_value? + end + + + def test_reader_for_invalid_column_names + Topic.send(:define_read_method, "mumub-jumbo".to_sym, "mumub-jumbo", nil) + assert !Topic.generated_methods.include?("mumub-jumbo") + end + + def test_non_attribute_access_and_assignment + topic = Topic.new + assert !topic.respond_to?("mumbo") + assert_raises(NoMethodError) { topic.mumbo } + assert_raises(NoMethodError) { topic.mumbo = 5 } + end + + def test_preserving_date_objects + # SQL Server doesn't have a separate column type just for dates, so all are returned as time + return true if current_adapter?(:SQLServerAdapter) + + if current_adapter?(:SybaseAdapter, :OracleAdapter) + # Sybase ctlib does not (yet?) support the date type; use datetime instead. + # Oracle treats all dates/times as Time. + assert_kind_of( + Time, Topic.find(1).last_read, + "The last_read attribute should be of the Time class" + ) + else + assert_kind_of( + Date, Topic.find(1).last_read, + "The last_read attribute should be of the Date class" + ) + end + end + + def test_preserving_time_objects + assert_kind_of( + Time, Topic.find(1).bonus_time, + "The bonus_time attribute should be of the Time class" + ) + + assert_kind_of( + Time, Topic.find(1).written_on, + "The written_on attribute should be of the Time class" + ) + + # For adapters which support microsecond resolution. + if current_adapter?(:PostgreSQLAdapter) + 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 + end + end + + def test_custom_mutator + topic = Topic.find(1) + # This mutator is protected in the class definition + topic.send(:approved=, true) + assert topic.instance_variable_get("@custom_approved") + end + + def test_destroy + topic = Topic.find(1) + assert_equal topic, topic.destroy, 'topic.destroy did not return self' + assert topic.frozen?, 'topic not frozen after destroy' + assert_raise(ActiveRecord::RecordNotFound) { Topic.find(topic.id) } + end + + def test_record_not_found_exception + assert_raises(ActiveRecord::RecordNotFound) { topicReloaded = Topic.find(99999) } + end + + def test_initialize_with_attributes + topic = Topic.new({ + "title" => "initialized from attributes", "written_on" => "2003-12-12 23:23" + }) + + assert_equal("initialized from attributes", topic.title) + end + + def test_initialize_with_invalid_attribute + begin + topic = Topic.new({ "title" => "test", + "last_read(1i)" => "2005", "last_read(2i)" => "2", "last_read(3i)" => "31"}) + rescue ActiveRecord::MultiparameterAssignmentErrors => ex + assert_equal(1, ex.errors.size) + assert_equal("last_read", ex.errors[0].attribute) + end + end + + def test_load + topics = Topic.find(:all, :order => 'id') + assert_equal(2, topics.size) + assert_equal(topics(:first).title, topics.first.title) + end + + def test_load_with_condition + topics = Topic.find(:all, :conditions => "author_name = 'Mary'") + + assert_equal(1, topics.size) + assert_equal(topics(:second).title, topics.first.title) + end + + def test_table_name_guesses + classes = [Category, Smarts, CreditCard, CreditCard::PinNumber, CreditCard::PinNumber::CvvCode, CreditCard::SubPinNumber, CreditCard::Brand, MasterCreditCard] + + assert_equal "topics", Topic.table_name + + assert_equal "categories", Category.table_name + assert_equal "smarts", Smarts.table_name + assert_equal "credit_cards", CreditCard.table_name + assert_equal "credit_card_pin_numbers", CreditCard::PinNumber.table_name + assert_equal "credit_card_pin_number_cvv_codes", CreditCard::PinNumber::CvvCode.table_name + assert_equal "credit_card_pin_numbers", CreditCard::SubPinNumber.table_name + assert_equal "categories", CreditCard::Brand.table_name + assert_equal "master_credit_cards", MasterCreditCard.table_name + + ActiveRecord::Base.pluralize_table_names = false + classes.each(&:reset_table_name) + + assert_equal "category", Category.table_name + assert_equal "smarts", Smarts.table_name + assert_equal "credit_card", CreditCard.table_name + assert_equal "credit_card_pin_number", CreditCard::PinNumber.table_name + assert_equal "credit_card_pin_number_cvv_code", CreditCard::PinNumber::CvvCode.table_name + assert_equal "credit_card_pin_number", CreditCard::SubPinNumber.table_name + assert_equal "category", CreditCard::Brand.table_name + assert_equal "master_credit_card", MasterCreditCard.table_name + + ActiveRecord::Base.pluralize_table_names = true + classes.each(&:reset_table_name) + + ActiveRecord::Base.table_name_prefix = "test_" + Category.reset_table_name + assert_equal "test_categories", Category.table_name + ActiveRecord::Base.table_name_suffix = "_test" + Category.reset_table_name + assert_equal "test_categories_test", Category.table_name + ActiveRecord::Base.table_name_prefix = "" + Category.reset_table_name + assert_equal "categories_test", Category.table_name + ActiveRecord::Base.table_name_suffix = "" + Category.reset_table_name + assert_equal "categories", Category.table_name + + ActiveRecord::Base.pluralize_table_names = false + ActiveRecord::Base.table_name_prefix = "test_" + Category.reset_table_name + assert_equal "test_category", Category.table_name + ActiveRecord::Base.table_name_suffix = "_test" + Category.reset_table_name + assert_equal "test_category_test", Category.table_name + ActiveRecord::Base.table_name_prefix = "" + Category.reset_table_name + assert_equal "category_test", Category.table_name + ActiveRecord::Base.table_name_suffix = "" + Category.reset_table_name + assert_equal "category", Category.table_name + + ActiveRecord::Base.pluralize_table_names = true + classes.each(&:reset_table_name) + end + + def test_destroy_all + assert_equal 2, Topic.count + + Topic.destroy_all "author_name = 'Mary'" + assert_equal 1, Topic.count + end + + def test_destroy_many + assert_equal 3, Client.count + Client.destroy([2, 3]) + assert_equal 1, Client.count + end + + def test_delete_many + Topic.delete([1, 2]) + assert_equal 0, Topic.count + end + + def test_boolean_attributes + assert ! Topic.find(1).approved? + assert Topic.find(2).approved? + end + + def test_increment_counter + Topic.increment_counter("replies_count", 1) + assert_equal 2, Topic.find(1).replies_count + + Topic.increment_counter("replies_count", 1) + assert_equal 3, Topic.find(1).replies_count + end + + def test_decrement_counter + Topic.decrement_counter("replies_count", 2) + assert_equal -1, Topic.find(2).replies_count + + Topic.decrement_counter("replies_count", 2) + assert_equal -2, Topic.find(2).replies_count + end + + def test_update_all + assert_equal 2, Topic.update_all("content = 'bulk updated!'") + assert_equal "bulk updated!", Topic.find(1).content + assert_equal "bulk updated!", Topic.find(2).content + + assert_equal 2, Topic.update_all(['content = ?', 'bulk updated again!']) + assert_equal "bulk updated again!", Topic.find(1).content + assert_equal "bulk updated again!", Topic.find(2).content + + assert_equal 2, Topic.update_all(['content = ?', nil]) + assert_nil Topic.find(1).content + end + + def test_update_all_with_hash + assert_not_nil Topic.find(1).last_read + assert_equal 2, Topic.update_all(:content => 'bulk updated with hash!', :last_read => nil) + assert_equal "bulk updated with hash!", Topic.find(1).content + assert_equal "bulk updated with hash!", Topic.find(2).content + assert_nil Topic.find(1).last_read + assert_nil Topic.find(2).last_read + end + + def test_update_all_with_non_standard_table_name + assert_equal 1, WarehouseThing.update_all(['value = ?', 0], ['id = ?', 1]) + assert_equal 0, WarehouseThing.find(1).value + end + + if current_adapter?(:MysqlAdapter) + def test_update_all_with_order_and_limit + assert_equal 1, Topic.update_all("content = 'bulk updated!'", nil, :limit => 1, :order => 'id DESC') + end + end + + def test_update_all_ignores_order_limit_from_association + author = Author.find(1) + assert_nothing_raised do + assert_equal author.posts_with_comments_and_categories.length, author.posts_with_comments_and_categories.update_all("body = 'bulk update!'") + end + end + + def test_update_many + topic_data = { 1 => { "content" => "1 updated" }, 2 => { "content" => "2 updated" } } + updated = Topic.update(topic_data.keys, topic_data.values) + + assert_equal 2, updated.size + assert_equal "1 updated", Topic.find(1).content + assert_equal "2 updated", Topic.find(2).content + end + + def test_delete_all + assert_equal 2, 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 + end + + def test_attribute_present + t = Topic.new + t.title = "hello there!" + t.written_on = Time.now + assert t.attribute_present?("title") + assert t.attribute_present?("written_on") + assert !t.attribute_present?("content") + end + + def test_attribute_keys_on_new_instance + t = Topic.new + assert_equal nil, t.title, "The topics table has a title column, so it should be nil" + assert_raise(NoMethodError) { t.title2 } + end + + def test_class_name + assert_equal "Firm", ActiveRecord::Base.class_name("firms") + assert_equal "Category", ActiveRecord::Base.class_name("categories") + assert_equal "AccountHolder", ActiveRecord::Base.class_name("account_holder") + + ActiveRecord::Base.pluralize_table_names = false + assert_equal "Firms", ActiveRecord::Base.class_name( "firms" ) + ActiveRecord::Base.pluralize_table_names = true + + ActiveRecord::Base.table_name_prefix = "test_" + assert_equal "Firm", ActiveRecord::Base.class_name( "test_firms" ) + ActiveRecord::Base.table_name_suffix = "_tests" + assert_equal "Firm", ActiveRecord::Base.class_name( "test_firms_tests" ) + ActiveRecord::Base.table_name_prefix = "" + assert_equal "Firm", ActiveRecord::Base.class_name( "firms_tests" ) + ActiveRecord::Base.table_name_suffix = "" + assert_equal "Firm", ActiveRecord::Base.class_name( "firms" ) + end + + def test_null_fields + assert_nil Topic.find(1).parent_id + assert_nil Topic.create("title" => "Hey you").parent_id + end + + def test_default_values + topic = Topic.new + assert topic.approved? + assert_nil topic.written_on + assert_nil topic.bonus_time + assert_nil topic.last_read + + topic.save + + topic = Topic.find(topic.id) + assert topic.approved? + assert_nil topic.last_read + + # Oracle has some funky default handling, so it requires a bit of + # extra testing. See ticket #2788. + if current_adapter?(:OracleAdapter) + test = TestOracleDefault.new + assert_equal "X", test.test_char + assert_equal "hello", test.test_string + assert_equal 3, test.test_int + end + end + + # Oracle, SQLServer, and Sybase do not have a TIME datatype. + unless current_adapter?(:SQLServerAdapter, :OracleAdapter, :SybaseAdapter) + def test_utc_as_time_zone + Topic.default_timezone = :utc + attributes = { "bonus_time" => "5:42:00AM" } + topic = Topic.find(1) + topic.attributes = attributes + assert_equal Time.utc(2000, 1, 1, 5, 42, 0), topic.bonus_time + Topic.default_timezone = :local + end + + def test_utc_as_time_zone_and_new + Topic.default_timezone = :utc + attributes = { "bonus_time(1i)"=>"2000", + "bonus_time(2i)"=>"1", + "bonus_time(3i)"=>"1", + "bonus_time(4i)"=>"10", + "bonus_time(5i)"=>"35", + "bonus_time(6i)"=>"50" } + topic = Topic.new(attributes) + assert_equal Time.utc(2000, 1, 1, 10, 35, 50), topic.bonus_time + Topic.default_timezone = :local + end + end + + def test_default_values_on_empty_strings + topic = Topic.new + topic.approved = nil + topic.last_read = nil + + topic.save + + topic = Topic.find(topic.id) + assert_nil topic.last_read + + # Sybase adapter does not allow nulls in boolean columns + if current_adapter?(:SybaseAdapter) + assert topic.approved == false + else + assert_nil topic.approved + end + end + + def test_equality + assert_equal Topic.find(1), Topic.find(2).topic + end + + def test_equality_of_new_records + assert_not_equal Topic.new, Topic.new + end + + def test_hashing + assert_equal [ Topic.find(1) ], [ Topic.find(2).topic ] & [ Topic.find(1) ] + end + + def test_destroy_new_record + client = Client.new + client.destroy + assert client.frozen? + end + + def test_destroy_record_with_associations + client = Client.find(3) + client.destroy + assert client.frozen? + assert_kind_of Firm, client.firm + assert_raises(ActiveSupport::FrozenObjectError) { client.name = "something else" } + end + + def test_update_attribute + assert !Topic.find(1).approved? + Topic.find(1).update_attribute("approved", true) + assert Topic.find(1).approved? + + Topic.find(1).update_attribute(:approved, false) + assert !Topic.find(1).approved? + end + + def test_update_attributes + topic = Topic.find(1) + assert !topic.approved? + assert_equal "The First Topic", topic.title + + topic.update_attributes("approved" => true, "title" => "The First Topic Updated") + topic.reload + assert topic.approved? + assert_equal "The First Topic Updated", topic.title + + topic.update_attributes(:approved => false, :title => "The First Topic") + topic.reload + assert !topic.approved? + assert_equal "The First Topic", topic.title + end + + def test_update_attributes! + reply = Reply.find(2) + assert_equal "The Second Topic's of the day", reply.title + assert_equal "Have a nice day", reply.content + + reply.update_attributes!("title" => "The Second Topic's of the day updated", "content" => "Have a nice evening") + reply.reload + assert_equal "The Second Topic's of the day updated", reply.title + assert_equal "Have a nice evening", reply.content + + reply.update_attributes!(:title => "The Second Topic's of the day", :content => "Have a nice day") + reply.reload + assert_equal "The Second Topic's of the day", reply.title + assert_equal "Have a nice day", reply.content + + assert_raise(ActiveRecord::RecordInvalid) { reply.update_attributes!(:title => nil, :content => "Have a nice evening") } + end + + def test_mass_assignment_should_raise_exception_if_accessible_and_protected_attribute_writers_are_both_used + topic = TopicWithProtectedContentAndAccessibleAuthorName.new + assert_raises(RuntimeError) { topic.attributes = { "author_name" => "me" } } + assert_raises(RuntimeError) { topic.attributes = { "content" => "stuff" } } + end + + def test_mass_assignment_protection + firm = Firm.new + firm.attributes = { "name" => "Next Angle", "rating" => 5 } + assert_equal 1, firm.rating + end + + def test_mass_assignment_protection_against_class_attribute_writers + [:logger, :configurations, :primary_key_prefix_type, :table_name_prefix, :table_name_suffix, :pluralize_table_names, :colorize_logging, + :default_timezone, :allow_concurrency, :schema_format, :verification_timeout, :lock_optimistically, :record_timestamps].each do |method| + assert Task.respond_to?(method) + assert Task.respond_to?("#{method}=") + assert Task.new.respond_to?(method) + assert !Task.new.respond_to?("#{method}=") + end + end + + def test_customized_primary_key_remains_protected + subscriber = Subscriber.new(:nick => 'webster123', :name => 'nice try') + assert_nil subscriber.id + + keyboard = Keyboard.new(:key_number => 9, :name => 'nice try') + assert_nil keyboard.id + end + + def test_customized_primary_key_remains_protected_when_referred_to_as_id + subscriber = Subscriber.new(:id => 'webster123', :name => 'nice try') + assert_nil subscriber.id + + keyboard = Keyboard.new(:id => 9, :name => 'nice try') + assert_nil keyboard.id + end + + def test_mass_assignment_protection_on_defaults + firm = Firm.new + firm.attributes = { "id" => 5, "type" => "Client" } + assert_nil firm.id + assert_equal "Firm", firm[:type] + end + + def test_mass_assignment_accessible + reply = Reply.new("title" => "hello", "content" => "world", "approved" => true) + reply.save + + assert reply.approved? + + reply.approved = false + reply.save + + assert !reply.approved? + end + + def test_mass_assignment_protection_inheritance + assert_nil LoosePerson.accessible_attributes + assert_equal Set.new([ 'credit_rating', 'administrator' ]), LoosePerson.protected_attributes + + assert_nil LooseDescendant.accessible_attributes + assert_equal Set.new([ 'credit_rating', 'administrator', 'phone_number' ]), LooseDescendant.protected_attributes + + assert_nil LooseDescendantSecond.accessible_attributes + assert_equal Set.new([ 'credit_rating', 'administrator', 'phone_number', 'name' ]), LooseDescendantSecond.protected_attributes, 'Running attr_protected twice in one class should merge the protections' + + assert_nil TightPerson.protected_attributes + assert_equal Set.new([ 'name', 'address' ]), TightPerson.accessible_attributes + + assert_nil TightDescendant.protected_attributes + assert_equal Set.new([ 'name', 'address', 'phone_number' ]), TightDescendant.accessible_attributes + end + + def test_readonly_attributes + assert_equal Set.new([ 'title' ]), ReadonlyTitlePost.readonly_attributes + + post = ReadonlyTitlePost.create(:title => "cannot change this", :body => "changeable") + post.reload + assert_equal "cannot change this", post.title + + post.update_attributes(:title => "try to change", :body => "changed") + post.reload + assert_equal "cannot change this", post.title + assert_equal "changed", post.body + end + + def test_multiparameter_attributes_on_date + attributes = { "last_read(1i)" => "2004", "last_read(2i)" => "6", "last_read(3i)" => "24" } + topic = Topic.find(1) + topic.attributes = attributes + # note that extra #to_date call allows test to pass for Oracle, which + # treats dates/times the same + assert_date_from_db Date.new(2004, 6, 24), topic.last_read.to_date + end + + def test_multiparameter_attributes_on_date_with_empty_date + attributes = { "last_read(1i)" => "2004", "last_read(2i)" => "6", "last_read(3i)" => "" } + topic = Topic.find(1) + topic.attributes = attributes + # note that extra #to_date call allows test to pass for Oracle, which + # treats dates/times the same + assert_date_from_db Date.new(2004, 6, 1), topic.last_read.to_date + end + + def test_multiparameter_attributes_on_date_with_all_empty + attributes = { "last_read(1i)" => "", "last_read(2i)" => "", "last_read(3i)" => "" } + topic = Topic.find(1) + topic.attributes = attributes + assert_nil topic.last_read + end + + def test_multiparameter_attributes_on_time + attributes = { + "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24", + "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00" + } + topic = Topic.find(1) + topic.attributes = attributes + assert_equal Time.local(2004, 6, 24, 16, 24, 0), topic.written_on + end + + def test_multiparameter_attributes_on_time_with_empty_seconds + attributes = { + "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24", + "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "" + } + topic = Topic.find(1) + topic.attributes = attributes + assert_equal Time.local(2004, 6, 24, 16, 24, 0), topic.written_on + end + + def test_multiparameter_mass_assignment_protector + task = Task.new + time = Time.mktime(2000, 1, 1, 1) + task.starting = time + attributes = { "starting(1i)" => "2004", "starting(2i)" => "6", "starting(3i)" => "24" } + task.attributes = attributes + assert_equal time, task.starting + end + + def test_multiparameter_assignment_of_aggregation + customer = Customer.new + address = Address.new("The Street", "The City", "The Country") + attributes = { "address(1)" => address.street, "address(2)" => address.city, "address(3)" => address.country } + customer.attributes = attributes + assert_equal address, customer.address + end + + def test_attributes_on_dummy_time + # Oracle, SQL Server, and Sybase do not have a TIME datatype. + return true if current_adapter?(:SQLServerAdapter, :OracleAdapter, :SybaseAdapter) + + 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 + end + + def test_boolean + b_false = Booleantest.create({ "value" => false }) + false_id = b_false.id + b_true = Booleantest.create({ "value" => true }) + true_id = b_true.id + + b_false = Booleantest.find(false_id) + assert !b_false.value? + b_true = Booleantest.find(true_id) + assert b_true.value? + end + + def test_boolean_cast_from_string + b_false = Booleantest.create({ "value" => "0" }) + false_id = b_false.id + b_true = Booleantest.create({ "value" => "1" }) + true_id = b_true.id + + b_false = Booleantest.find(false_id) + assert !b_false.value? + b_true = Booleantest.find(true_id) + assert b_true.value? + end + + def test_clone + topic = Topic.find(1) + cloned_topic = nil + assert_nothing_raised { cloned_topic = topic.clone } + assert_equal topic.title, cloned_topic.title + assert cloned_topic.new_record? + + # test if the attributes have been cloned + topic.title = "a" + cloned_topic.title = "b" + assert_equal "a", topic.title + assert_equal "b", cloned_topic.title + + # test if the attribute values have been cloned + topic.title = {"a" => "b"} + cloned_topic = topic.clone + cloned_topic.title["a"] = "c" + assert_equal "b", topic.title["a"] + + #test if attributes set as part of after_initialize are cloned correctly + assert_equal topic.author_email_address, cloned_topic.author_email_address + + # test if saved clone object differs from original + cloned_topic.save + assert !cloned_topic.new_record? + assert cloned_topic.id != topic.id + end + + def test_clone_with_aggregate_of_same_name_as_attribute + dev = DeveloperWithAggregate.find(1) + assert_kind_of DeveloperSalary, dev.salary + + clone = nil + assert_nothing_raised { clone = dev.clone } + assert_kind_of DeveloperSalary, clone.salary + assert_equal dev.salary.amount, clone.salary.amount + assert clone.new_record? + + # test if the attributes have been cloned + original_amount = clone.salary.amount + dev.salary.amount = 1 + assert_equal original_amount, clone.salary.amount + + assert clone.save + assert !clone.new_record? + assert clone.id != dev.id + end + + def test_clone_preserves_subtype + clone = nil + assert_nothing_raised { clone = Company.find(3).clone } + assert_kind_of Client, clone + end + + def test_bignum + company = Company.find(1) + company.rating = 2147483647 + company.save + company = Company.find(1) + assert_equal 2147483647, company.rating + end + + # TODO: extend defaults tests to other databases! + if current_adapter?(:PostgreSQLAdapter) + def test_default + default = Default.new + + # fixed dates / times + assert_equal Date.new(2004, 1, 1), default.fixed_date + assert_equal Time.local(2004, 1,1,0,0,0,0), default.fixed_time + + # char types + assert_equal 'Y', default.char1 + assert_equal 'a varchar field', default.char2 + assert_equal 'a text field', default.char3 + end + + class Geometric < ActiveRecord::Base; end + def test_geometric_content + + # accepted format notes: + # ()'s aren't required + # values can be a mix of float or integer + + g = Geometric.new( + :a_point => '(5.0, 6.1)', + #:a_line => '((2.0, 3), (5.5, 7.0))' # line type is currently unsupported in postgresql + :a_line_segment => '(2.0, 3), (5.5, 7.0)', + :a_box => '2.0, 3, 5.5, 7.0', + :a_path => '[(2.0, 3), (5.5, 7.0), (8.5, 11.0)]', # [ ] is an open path + :a_polygon => '((2.0, 3), (5.5, 7.0), (8.5, 11.0))', + :a_circle => '<(5.3, 10.4), 2>' + ) + + assert g.save + + # Reload and check that we have all the geometric attributes. + h = Geometric.find(g.id) + + assert_equal '(5,6.1)', h.a_point + assert_equal '[(2,3),(5.5,7)]', h.a_line_segment + assert_equal '(5.5,7),(2,3)', h.a_box # reordered to store upper right corner then bottom left corner + assert_equal '[(2,3),(5.5,7),(8.5,11)]', h.a_path + assert_equal '((2,3),(5.5,7),(8.5,11))', h.a_polygon + assert_equal '<(5.3,10.4),2>', h.a_circle + + # 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' + + # test alternate formats when defining the geometric types + + g = Geometric.new( + :a_point => '5.0, 6.1', + #:a_line => '((2.0, 3), (5.5, 7.0))' # line type is currently unsupported in postgresql + :a_line_segment => '((2.0, 3), (5.5, 7.0))', + :a_box => '(2.0, 3), (5.5, 7.0)', + :a_path => '((2.0, 3), (5.5, 7.0), (8.5, 11.0))', # ( ) is a closed path + :a_polygon => '2.0, 3, 5.5, 7.0, 8.5, 11.0', + :a_circle => '((5.3, 10.4), 2)' + ) + + assert g.save + + # Reload and check that we have all the geometric attributes. + h = Geometric.find(g.id) + + assert_equal '(5,6.1)', h.a_point + assert_equal '[(2,3),(5.5,7)]', h.a_line_segment + assert_equal '(5.5,7),(2,3)', h.a_box # reordered to store upper right corner then bottom left corner + assert_equal '((2,3),(5.5,7),(8.5,11))', h.a_path + assert_equal '((2,3),(5.5,7),(8.5,11))', h.a_polygon + assert_equal '<(5.3,10.4),2>', h.a_circle + + # 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' + end + end + + class NumericData < ActiveRecord::Base + self.table_name = 'numeric_data' + end + + def test_numeric_fields + m = NumericData.new( + :bank_balance => 1586.43, + :big_bank_balance => BigDecimal("1000234000567.95"), + :world_population => 6000000000, + :my_house_population => 3 + ) + assert m.save + + m1 = NumericData.find(m.id) + assert_not_nil m1 + + # As with migration_test.rb, we should make world_population >= 2**62 + # to cover 64-bit platforms and test it is a Bignum, but the main thing + # is that it's an Integer. + assert_kind_of Integer, m1.world_population + assert_equal 6000000000, m1.world_population + + assert_kind_of Fixnum, m1.my_house_population + assert_equal 3, m1.my_house_population + + assert_kind_of BigDecimal, m1.bank_balance + assert_equal BigDecimal("1586.43"), m1.bank_balance + + assert_kind_of BigDecimal, m1.big_bank_balance + assert_equal BigDecimal("1000234000567.95"), m1.big_bank_balance + end + + def test_auto_id + auto = AutoId.new + auto.save + 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("", inverted["bar"]) + assert_equal("", inverted["quux"]) + end + + def test_sql_injection_via_find + assert_raises(ActiveRecord::RecordNotFound, ActiveRecord::StatementInvalid) do + Topic.find("123456 OR id > 0") + end + end + + def test_column_name_properly_quoted + col_record = ColumnName.new + col_record.references = 40 + assert col_record.save + col_record.references = 41 + assert col_record.save + assert_not_nil c2 = ColumnName.find(col_record.id) + assert_equal(41, c2.references) + end + + def test_quoting_arrays + replies = Reply.find(:all, :conditions => [ "id IN (?)", topics(:first).replies.collect(&:id) ]) + assert_equal topics(:first).replies.size, replies.size + + replies = Reply.find(:all, :conditions => [ "id IN (?)", [] ]) + assert_equal 0, replies.size + end + + MyObject = Struct.new :attribute1, :attribute2 + + def test_serialized_attribute + myobj = MyObject.new('value1', 'value2') + topic = Topic.create("content" => myobj) + Topic.serialize("content", MyObject) + assert_equal(myobj, topic.content) + end + + def test_nil_serialized_attribute_with_class_constraint + myobj = MyObject.new('value1', 'value2') + topic = Topic.new + assert_nil topic.content + end + + def test_should_raise_exception_on_serialized_attribute_with_type_mismatch + myobj = MyObject.new('value1', 'value2') + topic = Topic.new(:content => myobj) + assert topic.save + Topic.serialize(:content, Hash) + assert_raise(ActiveRecord::SerializationTypeMismatch) { Topic.find(topic.id).content } + ensure + Topic.serialize(:content) + end + + def test_serialized_attribute_with_class_constraint + settings = { "color" => "blue" } + Topic.serialize(:content, Hash) + topic = Topic.new(:content => settings) + assert topic.save + assert_equal(settings, Topic.find(topic.id).content) + ensure + Topic.serialize(:content) + end + + def test_quote + author_name = "\\ \001 ' \n \\n \"" + topic = Topic.create('author_name' => author_name) + assert_equal author_name, Topic.find(topic.id).author_name + end + + if RUBY_VERSION < '1.9' + def test_quote_chars + str = 'The Narrator' + topic = Topic.create(:author_name => str) + assert_equal str, topic.author_name + + assert_kind_of ActiveSupport::Multibyte::Chars, str.chars + topic = Topic.find_by_author_name(str.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 + + def test_class_level_destroy + should_be_destroyed_reply = Reply.create("title" => "hello", "content" => "world") + Topic.find(1).replies << should_be_destroyed_reply + + Topic.destroy(1) + assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1) } + assert_raise(ActiveRecord::RecordNotFound) { Reply.find(should_be_destroyed_reply.id) } + end + + def test_class_level_delete + should_be_destroyed_reply = Reply.create("title" => "hello", "content" => "world") + Topic.find(1).replies << should_be_destroyed_reply + + Topic.delete(1) + assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1) } + assert_nothing_raised { Reply.find(should_be_destroyed_reply.id) } + end + + def test_increment_attribute + assert_equal 50, accounts(:signals37).credit_limit + accounts(:signals37).increment! :credit_limit + assert_equal 51, accounts(:signals37, :reload).credit_limit + + accounts(:signals37).increment(:credit_limit).increment!(:credit_limit) + assert_equal 53, accounts(:signals37, :reload).credit_limit + end + + def test_increment_nil_attribute + assert_nil topics(:first).parent_id + topics(:first).increment! :parent_id + assert_equal 1, topics(:first).parent_id + end + + def test_increment_attribute_by + assert_equal 50, accounts(:signals37).credit_limit + accounts(:signals37).increment! :credit_limit, 5 + assert_equal 55, accounts(:signals37, :reload).credit_limit + + accounts(:signals37).increment(:credit_limit, 1).increment!(:credit_limit, 3) + assert_equal 59, accounts(:signals37, :reload).credit_limit + end + + def test_decrement_attribute + assert_equal 50, accounts(:signals37).credit_limit + + accounts(:signals37).decrement!(:credit_limit) + assert_equal 49, accounts(:signals37, :reload).credit_limit + + accounts(:signals37).decrement(:credit_limit).decrement!(:credit_limit) + assert_equal 47, accounts(:signals37, :reload).credit_limit + end + + def test_decrement_attribute_by + assert_equal 50, accounts(:signals37).credit_limit + accounts(:signals37).decrement! :credit_limit, 5 + assert_equal 45, accounts(:signals37, :reload).credit_limit + + accounts(:signals37).decrement(:credit_limit, 1).decrement!(:credit_limit, 3) + assert_equal 41, accounts(:signals37, :reload).credit_limit + end + + def test_toggle_attribute + assert !topics(:first).approved? + topics(:first).toggle!(:approved) + assert topics(:first).approved? + topic = topics(:first) + topic.toggle(:approved) + assert !topic.approved? + topic.reload + assert topic.approved? + end + + def test_reload + t1 = Topic.find(1) + t2 = Topic.find(1) + t1.title = "something else" + t1.save + t2.reload + assert_equal t1.title, t2.title + 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_define_attr_method_with_block + k = Class.new( ActiveRecord::Base ) + k.send(:define_attr_method, :primary_key) { "sys_" + original_primary_key } + assert_equal "sys_id", k.primary_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 + 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 + k = Class.new( ActiveRecord::Base ) + k.set_primary_key { "sys_" + original_primary_key } + assert_equal "sys_id", k.primary_key + end + + def test_set_inheritance_column_with_value + k = Class.new( ActiveRecord::Base ) + k.inheritance_column = "foo" + assert_equal "foo", k.inheritance_column + k.set_inheritance_column "bar" + assert_equal "bar", k.inheritance_column + end + + def test_set_inheritance_column_with_block + k = Class.new( ActiveRecord::Base ) + k.set_inheritance_column { original_inheritance_column + "_id" } + assert_equal "type_id", k.inheritance_column + 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") + 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") + 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") + end + + assert_equal res4, res5 + + unless current_adapter?(:SQLite2Adapter, :DeprecatedSQLiteAdapter) + 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) + end + assert_equal res6, res7 + end + end + + def test_clear_association_cache_stored + firm = Firm.find(1) + assert_kind_of Firm, firm + + firm.clear_association_cache + assert_equal Firm.find(1).clients.collect{ |x| x.name }.sort, firm.clients.collect{ |x| x.name }.sort + end + + def test_clear_association_cache_new_record + firm = Firm.new + client_stored = Client.find(3) + client_new = Client.new + client_new.name = "The Joneses" + clients = [ client_stored, client_new ] + + firm.clients << clients + assert_equal clients.map(&:name).to_set, firm.clients.map(&:name).to_set + + firm.clear_association_cache + assert_equal clients.map(&:name).to_set, firm.clients.map(&:name).to_set + end + + def test_interpolate_sql + assert_nothing_raised { Category.new.send(:interpolate_sql, 'foo@bar') } + assert_nothing_raised { Category.new.send(:interpolate_sql, 'foo bar) baz') } + assert_nothing_raised { Category.new.send(:interpolate_sql, 'foo bar} baz') } + end + + def test_scoped_find_conditions + scoped_developers = Developer.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_scoped_find_limit_offset + scoped_developers = Developer.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.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.with_scope(:find => { :limit => 1 }) do + Developer.find(:all, :order => 'salary DESC') + end + # Test scope order + find order, find has priority + scoped_developers = Developer.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(:dev_10)) + # Test without scoped find conditions to ensure we get the right thing + developers = Developer.find(:all, :order => 'id', :limit => 1) + assert scoped_developers.include?(developers(:david)) + end + + def test_scoped_find_limit_offset_including_has_many_association + topics = Topic.with_scope(:find => {:limit => 1, :offset => 1, :include => :replies}) do + Topic.find(:all, :order => "topics.id") + end + assert_equal 1, topics.size + assert_equal 2, topics.first.id + end + + def test_scoped_find_order_including_has_many_association + developers = Developer.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_abstract_class + assert !ActiveRecord::Base.abstract_class? + assert LoosePerson.abstract_class? + assert !LooseDescendant.abstract_class? + end + + def test_base_class + assert_equal LoosePerson, LoosePerson.base_class + assert_equal LooseDescendant, LooseDescendant.base_class + assert_equal TightPerson, TightPerson.base_class + assert_equal TightPerson, TightDescendant.base_class + + assert_equal Post, Post.base_class + assert_equal Post, SpecialPost.base_class + assert_equal Post, StiPost.base_class + assert_equal SubStiPost, SubStiPost.base_class + end + + def test_descends_from_active_record + # Tries to call Object.abstract_class? + assert_raise(NoMethodError) do + ActiveRecord::Base.descends_from_active_record? + end + + # Abstract subclass of AR::Base. + assert LoosePerson.descends_from_active_record? + + # Concrete subclass of an abstract class. + assert LooseDescendant.descends_from_active_record? + + # Concrete subclass of AR::Base. + assert TightPerson.descends_from_active_record? + + # Concrete subclass of a concrete class but has no type column. + assert TightDescendant.descends_from_active_record? + + # Concrete subclass of AR::Base. + assert Post.descends_from_active_record? + + # Abstract subclass of a concrete class which has a type column. + # This is pathological, as you'll never have Sub < Abstract < Concrete. + assert !StiPost.descends_from_active_record? + + # Concrete subclasses an abstract class which has a type column. + assert !SubStiPost.descends_from_active_record? + end + + def test_find_on_abstract_base_class_doesnt_use_type_condition + old_class = LooseDescendant + Object.send :remove_const, :LooseDescendant + + descendant = old_class.create! + assert_not_nil LoosePerson.find(descendant.id), "Should have found instance of LooseDescendant when finding abstract LoosePerson: #{descendant.inspect}" + ensure + unless Object.const_defined?(:LooseDescendant) + Object.const_set :LooseDescendant, old_class + end + end + + def test_assert_queries + query = lambda { ActiveRecord::Base.connection.execute 'select count(*) from developers' } + assert_queries(2) { 2.times { query.call } } + assert_queries 1, &query + assert_no_queries { assert true } + end + + def test_to_xml + xml = REXML::Document.new(topics(:first).to_xml(:indent => 0)) + bonus_time_in_current_timezone = topics(:first).bonus_time.xmlschema + written_on_in_current_timezone = topics(:first).written_on.xmlschema + last_read_in_current_timezone = topics(:first).last_read.xmlschema + + assert_equal "topic", xml.root.name + assert_equal "The First Topic" , xml.elements["//title"].text + assert_equal "David" , xml.elements["//author-name"].text + + assert_equal "1", xml.elements["//id"].text + assert_equal "integer" , xml.elements["//id"].attributes['type'] + + assert_equal "1", xml.elements["//replies-count"].text + assert_equal "integer" , xml.elements["//replies-count"].attributes['type'] + + assert_equal written_on_in_current_timezone, xml.elements["//written-on"].text + assert_equal "datetime" , xml.elements["//written-on"].attributes['type'] + + assert_equal "--- Have a nice day\n" , xml.elements["//content"].text + assert_equal "yaml" , xml.elements["//content"].attributes['type'] + + assert_equal "david@loudthinking.com", xml.elements["//author-email-address"].text + + assert_equal nil, xml.elements["//parent-id"].text + assert_equal "integer", xml.elements["//parent-id"].attributes['type'] + assert_equal "true", xml.elements["//parent-id"].attributes['nil'] + + if current_adapter?(:SybaseAdapter, :SQLServerAdapter, :OracleAdapter) + assert_equal last_read_in_current_timezone, xml.elements["//last-read"].text + assert_equal "datetime" , xml.elements["//last-read"].attributes['type'] + else + assert_equal "2004-04-15", xml.elements["//last-read"].text + assert_equal "date" , xml.elements["//last-read"].attributes['type'] + end + + # Oracle and DB2 don't have true boolean or time-only fields + unless current_adapter?(:OracleAdapter, :DB2Adapter) + assert_equal "false", xml.elements["//approved"].text + assert_equal "boolean" , xml.elements["//approved"].attributes['type'] + + assert_equal bonus_time_in_current_timezone, xml.elements["//bonus-time"].text + assert_equal "datetime" , xml.elements["//bonus-time"].attributes['type'] + end + end + + def test_to_xml_skipping_attributes + xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :except => [:title, :replies_count]) + assert_equal "", xml.first(7) + assert !xml.include?(%(The First Topic)) + assert xml.include?(%(David)) + + xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :except => [:title, :author_name, :replies_count]) + assert !xml.include?(%(The First Topic)) + assert !xml.include?(%(David)) + end + + def test_to_xml_including_has_many_association + xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :include => :replies, :except => :replies_count) + assert_equal "", xml.first(7) + assert xml.include?(%()) + assert xml.include?(%(The Second Topic's of the day)) + end + + def test_array_to_xml_including_has_many_association + xml = [ topics(:first), topics(:second) ].to_xml(:indent => 0, :skip_instruct => true, :include => :replies) + assert xml.include?(%()) + end + + def test_array_to_xml_including_methods + xml = [ topics(:first), topics(:second) ].to_xml(:indent => 0, :skip_instruct => true, :methods => [ :topic_id ]) + assert xml.include?(%(#{topics(:first).topic_id})), xml + assert xml.include?(%(#{topics(:second).topic_id})), xml + end + + def test_array_to_xml_including_has_one_association + xml = [ companies(:first_firm), companies(:rails_core) ].to_xml(:indent => 0, :skip_instruct => true, :include => :account) + assert xml.include?(companies(:first_firm).account.to_xml(:indent => 0, :skip_instruct => true)) + assert xml.include?(companies(:rails_core).account.to_xml(:indent => 0, :skip_instruct => true)) + end + + def test_array_to_xml_including_belongs_to_association + xml = [ companies(:first_client), companies(:second_client), companies(:another_client) ].to_xml(:indent => 0, :skip_instruct => true, :include => :firm) + assert xml.include?(companies(:first_client).to_xml(:indent => 0, :skip_instruct => true)) + assert xml.include?(companies(:second_client).firm.to_xml(:indent => 0, :skip_instruct => true)) + assert xml.include?(companies(:another_client).firm.to_xml(:indent => 0, :skip_instruct => true)) + end + + def test_to_xml_including_belongs_to_association + xml = companies(:first_client).to_xml(:indent => 0, :skip_instruct => true, :include => :firm) + assert !xml.include?("") + + xml = companies(:second_client).to_xml(:indent => 0, :skip_instruct => true, :include => :firm) + assert xml.include?("") + end + + def test_to_xml_including_multiple_associations + xml = companies(:first_firm).to_xml(:indent => 0, :skip_instruct => true, :include => [ :clients, :account ]) + assert_equal "", xml.first(6) + assert xml.include?(%()) + assert xml.include?(%()) + end + + def test_to_xml_including_multiple_associations_with_options + xml = companies(:first_firm).to_xml( + :indent => 0, :skip_instruct => true, + :include => { :clients => { :only => :name } } + ) + + assert_equal "", xml.first(6) + assert xml.include?(%(Summit)) + assert xml.include?(%()) + end + + def test_to_xml_including_methods + xml = Company.new.to_xml(:methods => :arbitrary_method, :skip_instruct => true) + assert_equal "", xml.first(9) + assert xml.include?(%(I am Jack's profound disappointment)) + end + + def test_to_xml_with_block + value = "Rockin' the block" + xml = Company.new.to_xml(:skip_instruct => true) do |xml| + xml.tag! "arbitrary-element", value + end + assert_equal "", xml.first(9) + assert xml.include?(%(#{value})) + end + + def test_except_attributes + assert_equal( + %w( author_name type id approved replies_count bonus_time written_on content author_email_address parent_id last_read).sort, + topics(:first).attributes(:except => :title).keys.sort + ) + + assert_equal( + %w( replies_count bonus_time written_on content author_email_address parent_id last_read).sort, + topics(:first).attributes(:except => [ :title, :id, :type, :approved, :author_name ]).keys.sort + ) + end + + def test_include_attributes + assert_equal(%w( title ), topics(:first).attributes(:only => :title).keys) + assert_equal(%w( title author_name type id approved ).sort, topics(:first).attributes(:only => [ :title, :id, :type, :approved, :author_name ]).keys.sort) + end + + def test_type_name_with_module_should_handle_beginning + assert_equal 'ActiveRecord::Person', ActiveRecord::Base.send(:type_name_with_module, 'Person') + assert_equal '::Person', ActiveRecord::Base.send(:type_name_with_module, '::Person') + end + + def test_to_param_should_return_string + assert_kind_of String, Client.find(:first).to_param + end + + def test_inspect_class + assert_equal 'ActiveRecord::Base', ActiveRecord::Base.inspect + assert_equal 'LoosePerson(abstract)', LoosePerson.inspect + assert_match(/^Topic\(id: integer, title: string/, Topic.inspect) + end + + def test_inspect_instance + topic = topics(:first) + assert_equal %(#), topic.inspect + end + + def test_inspect_new_instance + assert_match /Topic id: nil/, Topic.new.inspect + end + + def test_inspect_limited_select_instance + assert_equal %(#), Topic.find(:first, :select => 'id', :conditions => 'id = 1').inspect + assert_equal %(#), Topic.find(:first, :select => 'id, title', :conditions => 'id = 1').inspect + end + + def test_inspect_class_without_table + assert_equal "NonExistentTable(Table doesn't exist)", NonExistentTable.inspect + end + + def test_attribute_for_inspect + t = topics(:first) + t.title = "The First Topic Now Has A Title With\nNewlines And More Than 50 Characters" + + assert_equal %("#{t.written_on.to_s(:db)}"), t.attribute_for_inspect(:written_on) + assert_equal '"The First Topic Now Has A Title With\nNewlines And M..."', t.attribute_for_inspect(:title) + end + + def test_becomes + assert_kind_of Reply, topics(:first).becomes(Reply) + assert_equal "The First Topic", topics(:first).becomes(Reply).title + 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.level = Logger::DEBUG + ActiveRecord::Base.silence do + ActiveRecord::Base.logger.warn "warn" + ActiveRecord::Base.logger.error "error" + end + assert_equal "error\n", log.string + ensure + ActiveRecord::Base.logger = original_logger + end + + 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.level = Logger::WARN + ActiveRecord::Base.silence do + end + assert_equal Logger::WARN, ActiveRecord::Base.logger.level + ensure + ActiveRecord::Base.logger = original_logger + end + + def test_benchmark_with_log_level + original_logger = ActiveRecord::Base.logger + log = StringIO.new + ActiveRecord::Base.logger = Logger.new(log) + ActiveRecord::Base.logger.level = Logger::WARN + ActiveRecord::Base.benchmark("Debug Topic Count", Logger::DEBUG) { Topic.count } + ActiveRecord::Base.benchmark("Warn Topic Count", Logger::WARN) { Topic.count } + ActiveRecord::Base.benchmark("Error Topic Count", Logger::ERROR) { Topic.count } + assert_no_match /Debug Topic Count/, log.string + assert_match /Warn Topic Count/, log.string + assert_match /Error Topic Count/, log.string + ensure + ActiveRecord::Base.logger = original_logger + end + + def test_benchmark_with_use_silence + original_logger = ActiveRecord::Base.logger + log = StringIO.new + ActiveRecord::Base.logger = Logger.new(log) + ActiveRecord::Base.benchmark("Logging", Logger::DEBUG, true) { ActiveRecord::Base.logger.debug "Loud" } + ActiveRecord::Base.benchmark("Logging", Logger::DEBUG, false) { ActiveRecord::Base.logger.debug "Quiet" } + assert_no_match /Loud/, log.string + assert_match /Quiet/, log.string + ensure + ActiveRecord::Base.logger = original_logger + end +end diff --git a/activerecord/test/cases/binary_test.rb b/activerecord/test/cases/binary_test.rb new file mode 100644 index 0000000000..f89660e4e1 --- /dev/null +++ b/activerecord/test/cases/binary_test.rb @@ -0,0 +1,32 @@ +require 'abstract_unit' + +# Without using prepared statements, it makes no sense to test +# BLOB data with SQL Server, because the length of a statement is +# limited to 8KB. +# +# Without using prepared statements, it makes no sense to test +# BLOB data with DB2 or Firebird, because the length of a statement +# is limited to 32KB. +unless current_adapter?(:SQLServerAdapter, :SybaseAdapter, :DB2Adapter, :FirebirdAdapter) + require 'fixtures/binary' + + class BinaryTest < ActiveSupport::TestCase + FIXTURES = %w(flowers.jpg example.log) + + def test_load_save + Binary.delete_all + + FIXTURES.each do |filename| + data = File.read("#{File.dirname(__FILE__)}/fixtures/#{filename}").freeze + + bin = Binary.new(:data => data) + assert_equal data, bin.data, 'Newly assigned data differs from original' + + bin.save! + assert_equal data, bin.data, 'Data differs from original after save' + + assert_equal data, bin.reload.data, 'Reloaded data differs from original' + end + end + end +end diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb new file mode 100644 index 0000000000..7bee644947 --- /dev/null +++ b/activerecord/test/cases/calculations_test.rb @@ -0,0 +1,251 @@ +require 'abstract_unit' +require 'fixtures/company' +require 'fixtures/topic' + +Company.has_many :accounts + +class NumericData < ActiveRecord::Base + self.table_name = 'numeric_data' +end + +class CalculationsTest < ActiveSupport::TestCase + fixtures :companies, :accounts, :topics + + def test_should_sum_field + assert_equal 318, Account.sum(:credit_limit) + end + + def test_should_average_field + value = Account.average(:credit_limit) + assert_kind_of Float, value + assert_in_delta 53.0, value, 0.001 + end + + def test_should_return_nil_as_average + assert_nil NumericData.average(:bank_balance) + end + + def test_should_get_maximum_of_field + assert_equal 60, Account.maximum(:credit_limit) + end + + def test_should_get_maximum_of_field_with_include + assert_equal 50, Account.maximum(:credit_limit, :include => :firm, :conditions => "companies.name != 'Summit'") + end + + def test_should_get_maximum_of_field_with_scoped_include + Account.with_scope :find => { :include => :firm, :conditions => "companies.name != 'Summit'" } do + assert_equal 50, Account.maximum(:credit_limit) + end + end + + def test_should_get_minimum_of_field + assert_equal 50, Account.minimum(:credit_limit) + end + + def test_should_group_by_field + c = Account.sum(:credit_limit, :group => :firm_id) + [1,6,2].each { |firm_id| assert c.keys.include?(firm_id) } + end + + def test_should_group_by_summed_field + c = Account.sum(:credit_limit, :group => :firm_id) + 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") + 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") + 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) + 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) + assert_equal [2, 6], c.keys.compact + 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_association + c = Account.sum(:credit_limit, :group => :firm) + 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') + end + + def test_should_group_by_summed_field_with_conditions + c = Account.sum(:credit_limit, :conditions => 'firm_id > 1', + :group => :firm_id) + 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') + 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') + assert_equal 50, c[1] + assert_equal 105, c[6] + assert_equal 60, c[2] + end + + def test_should_calculate_with_invalid_field + assert_equal 6, Account.calculate(:count, '*') + assert_equal 6, Account.calculate(:count, :all) + end + + def test_should_calculate_grouped_with_invalid_field + c = Account.count(:all, :group => 'accounts.firm_id') + 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) + assert_equal 1, c[companies(:first_firm)] + assert_equal 2, c[companies(:rails_core)] + assert_equal 1, c[companies(:first_client)] + end + + uses_mocha 'group_by_non_numeric_foreign_key_association' do + def test_should_group_by_association_with_non_numeric_foreign_key + ActiveRecord::Base.connection.expects(:select_all).returns([{"count_all" => 1, "firm_id" => "ABC"}]) + + firm = mock() + firm.expects(:id).returns("ABC") + firm.expects(:class).returns(Firm) + Company.expects(:find).with(["ABC"]).returns([firm]) + + column = mock() + column.expects(:name).at_least_once.returns(:firm_id) + column.expects(:type_cast).with("ABC").returns("ABC") + Account.expects(:columns).at_least_once.returns([column]) + + c = Account.count(:all, :group => :firm) + assert_equal Firm, c.first.first.class + assert_equal 1, c.first.last + end + 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})") + assert_equal 2, c[nil] + assert_equal 1, c['DEPENDENTFIRM'] + assert_equal 3, c['CLIENT'] + assert_equal 2, c['FIRM'] + end + + def test_should_calculate_grouped_by_function_with_table_alias + c = Company.count(:all, :group => "UPPER(companies.#{QUOTED_TYPE})") + assert_equal 2, c[nil] + assert_equal 1, c['DEPENDENTFIRM'] + assert_equal 3, c['CLIENT'] + assert_equal 2, c['FIRM'] + end + + def test_should_not_overshadow_enumerable_sum + assert_equal 6, [1, 2, 3].sum(&:abs) + end + + def test_should_sum_scoped_field + assert_equal 15, companies(:rails_core).companies.sum(:id) + end + + def test_should_sum_scoped_field_with_conditions + assert_equal 8, companies(:rails_core).companies.sum(:id, :conditions => 'id > 7') + end + + def test_should_group_by_scoped_field + c = companies(:rails_core).companies.sum(:id, :group => :name) + assert_equal 7, c['Leetsoft'] + assert_equal 8, c['Jadedpixel'] + end + + def test_should_group_by_summed_field_with_conditions_and_having + c = companies(:rails_core).companies.sum(:id, :group => :name, + :having => 'sum(id) > 7') + assert_nil c['Leetsoft'] + assert_equal 8, c['Jadedpixel'] + end + + def test_should_reject_invalid_options + assert_nothing_raised do + [:count, :sum].each do |func| + # empty options are valid + Company.send(:validate_calculation_options, func) + # these options are valid for all calculations + [:select, :conditions, :joins, :order, :group, :having, :distinct].each do |opt| + Company.send(:validate_calculation_options, func, opt => true) + end + end + + # :include is only valid on :count + Company.send(:validate_calculation_options, :count, :include => true) + end + + assert_raises(ArgumentError) { Company.send(:validate_calculation_options, :sum, :foo => :bar) } + assert_raises(ArgumentError) { Company.send(:validate_calculation_options, :count, :foo => :bar) } + 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) + end + + def test_count_with_column_parameter + assert_equal 5, Account.count(:firm_id) + end + + def test_count_with_column_and_options_parameter + assert_equal 2, Account.count(:firm_id, :conditions => "credit_limit = 50") + end + + def test_count_with_no_parameters_isnt_deprecated + assert_not_deprecated { Account.count } + end + + def test_count_with_too_many_parameters_raises + assert_raise(ArgumentError) { Account.count(1, 2, 3) } + end +end diff --git a/activerecord/test/cases/callbacks_test.rb b/activerecord/test/cases/callbacks_test.rb new file mode 100644 index 0000000000..50c5d4ea62 --- /dev/null +++ b/activerecord/test/cases/callbacks_test.rb @@ -0,0 +1,400 @@ +require 'abstract_unit' + +class CallbackDeveloper < ActiveRecord::Base + set_table_name 'developers' + + class << self + def callback_string(callback_method) + "history << [#{callback_method.to_sym.inspect}, :string]" + end + + def callback_proc(callback_method) + Proc.new { |model| model.history << [callback_method, :proc] } + end + + def define_callback_method(callback_method) + define_method("#{callback_method}_method") do |model| + model.history << [callback_method, :method] + end + end + + def callback_object(callback_method) + klass = Class.new + klass.send(:define_method, callback_method) do |model| + model.history << [callback_method, :object] + end + klass.new + end + end + + ActiveRecord::Callbacks::CALLBACKS.each do |callback_method| + callback_method_sym = callback_method.to_sym + define_callback_method(callback_method_sym) + send(callback_method, callback_method_sym) + send(callback_method, callback_string(callback_method_sym)) + send(callback_method, callback_proc(callback_method_sym)) + send(callback_method, callback_object(callback_method_sym)) + send(callback_method) { |model| model.history << [callback_method_sym, :block] } + end + + def history + @history ||= [] + end + + # after_initialize and after_find are invoked only if instance methods have been defined. + def after_initialize + end + + def after_find + end +end + +class ParentDeveloper < ActiveRecord::Base + set_table_name 'developers' + attr_accessor :after_save_called + before_validation {|record| record.after_save_called = true} +end + +class ChildDeveloper < ParentDeveloper + +end + +class RecursiveCallbackDeveloper < ActiveRecord::Base + set_table_name 'developers' + + before_save :on_before_save + after_save :on_after_save + + attr_reader :on_before_save_called, :on_after_save_called + + def on_before_save + @on_before_save_called ||= 0 + @on_before_save_called += 1 + save unless @on_before_save_called > 1 + end + + def on_after_save + @on_after_save_called ||= 0 + @on_after_save_called += 1 + save unless @on_after_save_called > 1 + end +end + +class ImmutableDeveloper < ActiveRecord::Base + set_table_name 'developers' + + validates_inclusion_of :salary, :in => 50000..200000 + + before_save :cancel + before_destroy :cancel + + def cancelled? + @cancelled == true + end + + private + def cancel + @cancelled = true + false + end +end + +class ImmutableMethodDeveloper < ActiveRecord::Base + set_table_name 'developers' + + validates_inclusion_of :salary, :in => 50000..200000 + + def cancelled? + @cancelled == true + end + + def before_save + @cancelled = true + false + end + + def before_destroy + @cancelled = true + false + end +end + +class CallbackCancellationDeveloper < ActiveRecord::Base + set_table_name 'developers' + def before_create + false + end +end + +class CallbacksTest < ActiveSupport::TestCase + fixtures :developers + + def test_initialize + david = CallbackDeveloper.new + assert_equal [ + [ :after_initialize, :string ], + [ :after_initialize, :proc ], + [ :after_initialize, :object ], + [ :after_initialize, :block ], + ], david.history + end + + def test_find + david = CallbackDeveloper.find(1) + assert_equal [ + [ :after_find, :string ], + [ :after_find, :proc ], + [ :after_find, :object ], + [ :after_find, :block ], + [ :after_initialize, :string ], + [ :after_initialize, :proc ], + [ :after_initialize, :object ], + [ :after_initialize, :block ], + ], david.history + end + + def test_new_valid? + david = CallbackDeveloper.new + david.valid? + assert_equal [ + [ :after_initialize, :string ], + [ :after_initialize, :proc ], + [ :after_initialize, :object ], + [ :after_initialize, :block ], + [ :before_validation, :string ], + [ :before_validation, :proc ], + [ :before_validation, :object ], + [ :before_validation, :block ], + [ :before_validation_on_create, :string ], + [ :before_validation_on_create, :proc ], + [ :before_validation_on_create, :object ], + [ :before_validation_on_create, :block ], + [ :after_validation, :string ], + [ :after_validation, :proc ], + [ :after_validation, :object ], + [ :after_validation, :block ], + [ :after_validation_on_create, :string ], + [ :after_validation_on_create, :proc ], + [ :after_validation_on_create, :object ], + [ :after_validation_on_create, :block ] + ], david.history + end + + def test_existing_valid? + david = CallbackDeveloper.find(1) + david.valid? + assert_equal [ + [ :after_find, :string ], + [ :after_find, :proc ], + [ :after_find, :object ], + [ :after_find, :block ], + [ :after_initialize, :string ], + [ :after_initialize, :proc ], + [ :after_initialize, :object ], + [ :after_initialize, :block ], + [ :before_validation, :string ], + [ :before_validation, :proc ], + [ :before_validation, :object ], + [ :before_validation, :block ], + [ :before_validation_on_update, :string ], + [ :before_validation_on_update, :proc ], + [ :before_validation_on_update, :object ], + [ :before_validation_on_update, :block ], + [ :after_validation, :string ], + [ :after_validation, :proc ], + [ :after_validation, :object ], + [ :after_validation, :block ], + [ :after_validation_on_update, :string ], + [ :after_validation_on_update, :proc ], + [ :after_validation_on_update, :object ], + [ :after_validation_on_update, :block ] + ], david.history + end + + def test_create + david = CallbackDeveloper.create('name' => 'David', 'salary' => 1000000) + assert_equal [ + [ :after_initialize, :string ], + [ :after_initialize, :proc ], + [ :after_initialize, :object ], + [ :after_initialize, :block ], + [ :before_validation, :string ], + [ :before_validation, :proc ], + [ :before_validation, :object ], + [ :before_validation, :block ], + [ :before_validation_on_create, :string ], + [ :before_validation_on_create, :proc ], + [ :before_validation_on_create, :object ], + [ :before_validation_on_create, :block ], + [ :after_validation, :string ], + [ :after_validation, :proc ], + [ :after_validation, :object ], + [ :after_validation, :block ], + [ :after_validation_on_create, :string ], + [ :after_validation_on_create, :proc ], + [ :after_validation_on_create, :object ], + [ :after_validation_on_create, :block ], + [ :before_save, :string ], + [ :before_save, :proc ], + [ :before_save, :object ], + [ :before_save, :block ], + [ :before_create, :string ], + [ :before_create, :proc ], + [ :before_create, :object ], + [ :before_create, :block ], + [ :after_create, :string ], + [ :after_create, :proc ], + [ :after_create, :object ], + [ :after_create, :block ], + [ :after_save, :string ], + [ :after_save, :proc ], + [ :after_save, :object ], + [ :after_save, :block ] + ], david.history + end + + def test_save + david = CallbackDeveloper.find(1) + david.save + assert_equal [ + [ :after_find, :string ], + [ :after_find, :proc ], + [ :after_find, :object ], + [ :after_find, :block ], + [ :after_initialize, :string ], + [ :after_initialize, :proc ], + [ :after_initialize, :object ], + [ :after_initialize, :block ], + [ :before_validation, :string ], + [ :before_validation, :proc ], + [ :before_validation, :object ], + [ :before_validation, :block ], + [ :before_validation_on_update, :string ], + [ :before_validation_on_update, :proc ], + [ :before_validation_on_update, :object ], + [ :before_validation_on_update, :block ], + [ :after_validation, :string ], + [ :after_validation, :proc ], + [ :after_validation, :object ], + [ :after_validation, :block ], + [ :after_validation_on_update, :string ], + [ :after_validation_on_update, :proc ], + [ :after_validation_on_update, :object ], + [ :after_validation_on_update, :block ], + [ :before_save, :string ], + [ :before_save, :proc ], + [ :before_save, :object ], + [ :before_save, :block ], + [ :before_update, :string ], + [ :before_update, :proc ], + [ :before_update, :object ], + [ :before_update, :block ], + [ :after_update, :string ], + [ :after_update, :proc ], + [ :after_update, :object ], + [ :after_update, :block ], + [ :after_save, :string ], + [ :after_save, :proc ], + [ :after_save, :object ], + [ :after_save, :block ] + ], david.history + end + + def test_destroy + david = CallbackDeveloper.find(1) + david.destroy + assert_equal [ + [ :after_find, :string ], + [ :after_find, :proc ], + [ :after_find, :object ], + [ :after_find, :block ], + [ :after_initialize, :string ], + [ :after_initialize, :proc ], + [ :after_initialize, :object ], + [ :after_initialize, :block ], + [ :before_destroy, :string ], + [ :before_destroy, :proc ], + [ :before_destroy, :object ], + [ :before_destroy, :block ], + [ :after_destroy, :string ], + [ :after_destroy, :proc ], + [ :after_destroy, :object ], + [ :after_destroy, :block ] + ], david.history + end + + def test_delete + david = CallbackDeveloper.find(1) + CallbackDeveloper.delete(david.id) + assert_equal [ + [ :after_find, :string ], + [ :after_find, :proc ], + [ :after_find, :object ], + [ :after_find, :block ], + [ :after_initialize, :string ], + [ :after_initialize, :proc ], + [ :after_initialize, :object ], + [ :after_initialize, :block ], + ], david.history + end + + def test_before_save_returning_false + david = ImmutableDeveloper.find(1) + assert david.valid? + assert !david.save + assert_raises(ActiveRecord::RecordNotSaved) { david.save! } + + david = ImmutableDeveloper.find(1) + david.salary = 10_000_000 + assert !david.valid? + assert !david.save + assert_raises(ActiveRecord::RecordInvalid) { david.save! } + end + + def test_before_create_returning_false + someone = CallbackCancellationDeveloper.new + assert someone.valid? + assert !someone.save + end + + def test_before_destroy_returning_false + david = ImmutableDeveloper.find(1) + assert !david.destroy + assert_not_nil ImmutableDeveloper.find_by_id(1) + end + + def test_zzz_callback_returning_false # must be run last since we modify CallbackDeveloper + david = CallbackDeveloper.find(1) + CallbackDeveloper.before_validation proc { |model| model.history << [:before_validation, :returning_false]; return false } + CallbackDeveloper.before_validation proc { |model| model.history << [:before_validation, :should_never_get_here] } + david.save + assert_equal [ + [ :after_find, :string ], + [ :after_find, :proc ], + [ :after_find, :object ], + [ :after_find, :block ], + [ :after_initialize, :string ], + [ :after_initialize, :proc ], + [ :after_initialize, :object ], + [ :after_initialize, :block ], + [ :before_validation, :string ], + [ :before_validation, :proc ], + [ :before_validation, :object ], + [ :before_validation, :block ], + [ :before_validation, :returning_false ] + ], david.history + end + + def test_inheritence_of_callbacks + parent = ParentDeveloper.new + assert !parent.after_save_called + parent.save + assert parent.after_save_called + + child = ChildDeveloper.new + assert !child.after_save_called + child.save + assert child.after_save_called + end + +end diff --git a/activerecord/test/cases/class_inheritable_attributes_test.rb b/activerecord/test/cases/class_inheritable_attributes_test.rb new file mode 100644 index 0000000000..4f04f41e76 --- /dev/null +++ b/activerecord/test/cases/class_inheritable_attributes_test.rb @@ -0,0 +1,32 @@ +require 'test/unit' +require 'abstract_unit' +require 'active_support/core_ext/class/inheritable_attributes' + +class A + include ClassInheritableAttributes +end + +class B < A + write_inheritable_array "first", [ :one, :two ] +end + +class C < A + write_inheritable_array "first", [ :three ] +end + +class D < B + write_inheritable_array "first", [ :four ] +end + + +class ClassInheritableAttributesTest < ActiveSupport::TestCase + def test_first_level + assert_equal [ :one, :two ], B.read_inheritable_attribute("first") + assert_equal [ :three ], C.read_inheritable_attribute("first") + end + + def test_second_level + assert_equal [ :one, :two, :four ], D.read_inheritable_attribute("first") + assert_equal [ :one, :two ], B.read_inheritable_attribute("first") + end +end diff --git a/activerecord/test/cases/column_alias_test.rb b/activerecord/test/cases/column_alias_test.rb new file mode 100644 index 0000000000..da4b66554a --- /dev/null +++ b/activerecord/test/cases/column_alias_test.rb @@ -0,0 +1,17 @@ +require 'abstract_unit' +require 'fixtures/topic' + +class TestColumnAlias < ActiveSupport::TestCase + fixtures :topics + + QUERY = if 'Oracle' == ActiveRecord::Base.connection.adapter_name + 'SELECT id AS pk FROM topics WHERE ROWNUM < 2' + else + 'SELECT id AS pk FROM topics' + end + + def test_column_alias + records = Topic.connection.select_all(QUERY) + assert_equal 'pk', records[0].keys[0] + end +end diff --git a/activerecord/test/cases/connection_test_firebird.rb b/activerecord/test/cases/connection_test_firebird.rb new file mode 100644 index 0000000000..eb0222b66f --- /dev/null +++ b/activerecord/test/cases/connection_test_firebird.rb @@ -0,0 +1,8 @@ +require "#{File.dirname(__FILE__)}/abstract_unit" + +class FirebirdConnectionTest < ActiveSupport::TestCase + def test_charset_properly_set + fb_conn = ActiveRecord::Base.connection.instance_variable_get(:@connection) + assert_equal 'UTF8', fb_conn.database.character_set + end +end diff --git a/activerecord/test/cases/connection_test_mysql.rb b/activerecord/test/cases/connection_test_mysql.rb new file mode 100644 index 0000000000..e49a326d2b --- /dev/null +++ b/activerecord/test/cases/connection_test_mysql.rb @@ -0,0 +1,30 @@ +require "#{File.dirname(__FILE__)}/abstract_unit" + +class MysqlConnectionTest < ActiveSupport::TestCase + def setup + @connection = ActiveRecord::Base.connection + end + + def test_no_automatic_reconnection_after_timeout + assert @connection.active? + @connection.update('set @@wait_timeout=1') + sleep 2 + assert !@connection.active? + end + + def test_successful_reconnection_after_timeout_with_manual_reconnect + assert @connection.active? + @connection.update('set @@wait_timeout=1') + sleep 2 + @connection.reconnect! + assert @connection.active? + end + + def test_successful_reconnection_after_timeout_with_verify + assert @connection.active? + @connection.update('set @@wait_timeout=1') + sleep 2 + @connection.verify!(0) + assert @connection.active? + end +end diff --git a/activerecord/test/cases/copy_table_test_sqlite.rb b/activerecord/test/cases/copy_table_test_sqlite.rb new file mode 100644 index 0000000000..ec58295cd1 --- /dev/null +++ b/activerecord/test/cases/copy_table_test_sqlite.rb @@ -0,0 +1,69 @@ +require 'abstract_unit' + +class CopyTableTest < ActiveSupport::TestCase + fixtures :companies, :comments + + def setup + @connection = ActiveRecord::Base.connection + class << @connection + public :copy_table, :table_structure, :indexes + end + end + + def test_copy_table(from = 'companies', to = 'companies2', options = {}) + assert_nothing_raised {copy_table(from, to, options)} + assert_equal row_count(from), row_count(to) + + if block_given? + yield from, to, options + else + assert_equal column_names(from), column_names(to) + end + + @connection.drop_table(to) rescue nil + end + + def test_copy_table_renaming_column + test_copy_table('companies', 'companies2', + :rename => {'client_of' => 'fan_of'}) do |from, to, options| + expected = column_values(from, 'client_of') + assert expected.any?, 'only nils in resultset; real values are needed' + assert_equal expected, column_values(to, 'fan_of') + end + end + + def test_copy_table_with_index + test_copy_table('comments', 'comments_with_index') do + @connection.add_index('comments_with_index', ['post_id', 'type']) + test_copy_table('comments_with_index', 'comments_with_index2') do + assert_equal table_indexes_without_name('comments_with_index'), + table_indexes_without_name('comments_with_index2') + end + end + end + + def test_copy_table_without_primary_key + test_copy_table('developers_projects', 'programmers_projects') + end + +protected + def copy_table(from, to, options = {}) + @connection.copy_table(from, to, {:temporary => true}.merge(options)) + end + + def column_names(table) + @connection.table_structure(table).map {|column| column['name']} + end + + def column_values(table, column) + @connection.select_all("SELECT #{column} FROM #{table} ORDER BY id").map {|row| row[column]} + end + + def table_indexes_without_name(table) + @connection.indexes('comments_with_index').delete(:name) + end + + def row_count(table) + @connection.select_one("SELECT COUNT(*) AS count FROM #{table}")['count'] + end +end diff --git a/activerecord/test/cases/datatype_test_postgresql.rb b/activerecord/test/cases/datatype_test_postgresql.rb new file mode 100644 index 0000000000..5b882f7a2b --- /dev/null +++ b/activerecord/test/cases/datatype_test_postgresql.rb @@ -0,0 +1,203 @@ +require 'abstract_unit' + +class PostgresqlArray < ActiveRecord::Base +end + +class PostgresqlMoney < ActiveRecord::Base +end + +class PostgresqlNumber < ActiveRecord::Base +end + +class PostgresqlTime < ActiveRecord::Base +end + +class PostgresqlNetworkAddress < ActiveRecord::Base +end + +class PostgresqlBitString < ActiveRecord::Base +end + +class PostgresqlOid < ActiveRecord::Base +end + +class PostgresqlDataTypeTest < ActiveSupport::TestCase + self.use_transactional_fixtures = false + + def setup + @connection = ActiveRecord::Base.connection + + @connection.execute("INSERT INTO postgresql_arrays (commission_by_quarter, nicknames) VALUES ( '{35000,21000,18000,17000}', '{foo,bar,baz}' )") + @first_array = PostgresqlArray.find(1) + + @connection.execute("INSERT INTO postgresql_moneys (wealth) VALUES ('$567.89')") + @connection.execute("INSERT INTO postgresql_moneys (wealth) VALUES ('-$567.89')") + @first_money = PostgresqlMoney.find(1) + @second_money = PostgresqlMoney.find(2) + + @connection.execute("INSERT INTO postgresql_numbers (single, double) VALUES (123.456, 123456.789)") + @first_number = PostgresqlNumber.find(1) + + @connection.execute("INSERT INTO postgresql_times (time_interval) VALUES ('1 year 2 days ago')") + @first_time = PostgresqlTime.find(1) + + @connection.execute("INSERT INTO postgresql_network_addresses (cidr_address, inet_address, mac_address) VALUES('192.168.0/24', '172.16.1.254/32', '01:23:45:67:89:0a')") + @first_network_address = PostgresqlNetworkAddress.find(1) + + @connection.execute("INSERT INTO postgresql_bit_strings (bit_string, bit_string_varying) VALUES (B'00010101', X'15')") + @first_bit_string = PostgresqlBitString.find(1) + + @connection.execute("INSERT INTO postgresql_oids (obj_id) VALUES (1234)") + @first_oid = PostgresqlOid.find(1) + end + + def test_data_type_of_array_types + assert_equal :string, @first_array.column_for_attribute(:commission_by_quarter).type + assert_equal :string, @first_array.column_for_attribute(:nicknames).type + end + + def test_data_type_of_money_types + assert_equal :decimal, @first_money.column_for_attribute(:wealth).type + end + + def test_data_type_of_number_types + assert_equal :float, @first_number.column_for_attribute(:single).type + assert_equal :float, @first_number.column_for_attribute(:double).type + end + + def test_data_type_of_time_types + assert_equal :string, @first_time.column_for_attribute(:time_interval).type + 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 + end + + def test_data_type_of_bit_string_types + assert_equal :string, @first_bit_string.column_for_attribute(:bit_string).type + assert_equal :string, @first_bit_string.column_for_attribute(:bit_string_varying).type + end + + def test_data_type_of_oid_types + assert_equal :integer, @first_oid.column_for_attribute(:obj_id).type + end + + def test_array_values + assert_equal '{35000,21000,18000,17000}', @first_array.commission_by_quarter + assert_equal '{foo,bar,baz}', @first_array.nicknames + end + + def test_money_values + assert_equal 567.89, @first_money.wealth + assert_equal -567.89, @second_money.wealth + end + + def test_number_values + assert_equal 123.456, @first_number.single + assert_equal 123456.789, @first_number.double + end + + def test_time_values + 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 + assert_equal '01:23:45:67:89:0a', @first_network_address.mac_address + end + + def test_bit_string_values + assert_equal '00010101', @first_bit_string.bit_string + assert_equal '00010101', @first_bit_string.bit_string_varying + end + + def test_oid_values + assert_equal 1234, @first_oid.obj_id + end + + def test_update_integer_array + new_value = '{32800,95000,29350,17000}' + assert @first_array.commission_by_quarter = new_value + assert @first_array.save + assert @first_array.reload + assert_equal @first_array.commission_by_quarter, new_value + assert @first_array.commission_by_quarter = new_value + assert @first_array.save + assert @first_array.reload + assert_equal @first_array.commission_by_quarter, new_value + end + + def test_update_text_array + new_value = '{robby,robert,rob,robbie}' + assert @first_array.nicknames = new_value + assert @first_array.save + assert @first_array.reload + assert_equal @first_array.nicknames, new_value + assert @first_array.nicknames = new_value + assert @first_array.save + assert @first_array.reload + assert_equal @first_array.nicknames, new_value + end + + def test_update_money + new_value = 123.45 + assert @first_money.wealth = new_value + assert @first_money.save + assert @first_money.reload + assert_equal @first_money.wealth, new_value + end + + def test_update_number + new_single = 789.012 + new_double = 789012.345 + assert @first_number.single = new_single + assert @first_number.double = new_double + assert @first_number.save + assert @first_number.reload + assert_equal @first_number.single, new_single + assert_equal @first_number.double, new_double + end + + def test_update_time + assert @first_time.time_interval = '2 years 3 minutes' + assert @first_time.save + assert @first_time.reload + assert_equal @first_time.time_interval, '2 years 00:03:00' + end + + def test_update_network_address + new_cidr_address = '10.1.2.3/32' + new_inet_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 + assert @first_network_address.mac_address = new_mac_address + assert @first_network_address.save + assert @first_network_address.reload + assert_equal @first_network_address.cidr_address, new_cidr_address + assert_equal @first_network_address.inet_address, new_inet_address + assert_equal @first_network_address.mac_address, new_mac_address + end + + def test_update_bit_string + new_bit_string = '11111111' + new_bit_string_varying = 'FF' + assert @first_bit_string.bit_string = new_bit_string + assert @first_bit_string.bit_string_varying = new_bit_string_varying + assert @first_bit_string.save + assert @first_bit_string.reload + assert_equal @first_bit_string.bit_string, new_bit_string + assert_equal @first_bit_string.bit_string, @first_bit_string.bit_string_varying + end + + def test_update_oid + new_value = 567890 + assert @first_oid.obj_id = new_value + assert @first_oid.save + assert @first_oid.reload + assert_equal @first_oid.obj_id, new_value + end +end diff --git a/activerecord/test/cases/date_time_test.rb b/activerecord/test/cases/date_time_test.rb new file mode 100644 index 0000000000..20020b24cc --- /dev/null +++ b/activerecord/test/cases/date_time_test.rb @@ -0,0 +1,37 @@ +require 'abstract_unit' +require 'fixtures/topic' +require 'fixtures/task' + +class DateTimeTest < ActiveSupport::TestCase + def test_saves_both_date_and_time + time_values = [1807, 2, 10, 15, 30, 45] + now = DateTime.civil(*time_values) + + task = Task.new + task.starting = now + task.save! + + # check against Time.local_time, since some platforms will return a Time instead of a DateTime + assert_equal Time.local_time(*time_values), Task.find(task.id).starting + end + + def test_assign_empty_date_time + task = Task.new + task.starting = '' + task.ending = nil + assert_nil task.starting + assert_nil task.ending + end + + def test_assign_empty_date + topic = Topic.new + topic.last_read = '' + assert_nil topic.last_read + end + + def test_assign_empty_time + topic = Topic.new + topic.bonus_time = '' + assert_nil topic.bonus_time + end +end diff --git a/activerecord/test/cases/default_test_firebird.rb b/activerecord/test/cases/default_test_firebird.rb new file mode 100644 index 0000000000..841027b175 --- /dev/null +++ b/activerecord/test/cases/default_test_firebird.rb @@ -0,0 +1,16 @@ +require 'abstract_unit' +require 'fixtures/default' + +class DefaultTest < ActiveSupport::TestCase + def test_default_timestamp + default = Default.new + assert_instance_of(Time, default.default_timestamp) + assert_equal(:datetime, default.column_for_attribute(:default_timestamp).type) + + # Variance should be small; increase if required -- e.g., if test db is on + # remote host and clocks aren't synchronized. + t1 = Time.new + accepted_variance = 1.0 + assert_in_delta(t1.to_f, default.default_timestamp.to_f, accepted_variance) + end +end diff --git a/activerecord/test/cases/defaults_test.rb b/activerecord/test/cases/defaults_test.rb new file mode 100644 index 0000000000..8a5c8e0530 --- /dev/null +++ b/activerecord/test/cases/defaults_test.rb @@ -0,0 +1,67 @@ +require 'abstract_unit' +require 'fixtures/default' +require 'fixtures/entrant' + +class DefaultTest < ActiveSupport::TestCase + def test_nil_defaults_for_not_null_columns + column_defaults = + if current_adapter?(:MysqlAdapter) + { 'id' => nil, 'name' => '', 'course_id' => nil } + else + { 'id' => nil, 'name' => nil, 'course_id' => nil } + end + + column_defaults.each do |name, default| + column = Entrant.columns_hash[name] + assert !column.null, "#{name} column should be NOT NULL" + assert_equal default, column.default, "#{name} column should be DEFAULT #{default.inspect}" + end + end + + if current_adapter?(:MysqlAdapter) + # MySQL uses an implicit default 0 rather than NULL unless in strict mode. + # We use an implicit NULL so schema.rb is compatible with other databases. + def test_mysql_integer_not_null_defaults + klass = Class.new(ActiveRecord::Base) + klass.table_name = 'test_integer_not_null_default_zero' + klass.connection.create_table klass.table_name do |t| + t.column :zero, :integer, :null => false, :default => 0 + t.column :omit, :integer, :null => false + end + + assert_equal 0, klass.columns_hash['zero'].default + assert !klass.columns_hash['zero'].null + # 0 in MySQL 4, nil in 5. + assert [0, nil].include?(klass.columns_hash['omit'].default) + assert !klass.columns_hash['omit'].null + + assert_raise(ActiveRecord::StatementInvalid) { klass.create! } + + assert_nothing_raised do + instance = klass.create!(:omit => 1) + assert_equal 0, instance.zero + assert_equal 1, instance.omit + end + ensure + klass.connection.drop_table(klass.table_name) rescue nil + end + end + + if current_adapter?(:PostgreSQLAdapter, :SQLServerAdapter, :FirebirdAdapter, :OpenBaseAdapter, :OracleAdapter) + def test_default_integers + default = Default.new + assert_instance_of Fixnum, default.positive_integer + assert_equal 1, default.positive_integer + assert_instance_of Fixnum, default.negative_integer + assert_equal -1, default.negative_integer + assert_instance_of BigDecimal, default.decimal_number + assert_equal BigDecimal.new("2.78"), default.decimal_number + end + end + + if current_adapter?(:PostgreSQLAdapter) + def test_multiline_default_text + assert_equal "--- []\n\n", Default.columns_hash['multiline_default'].default + end + end +end diff --git a/activerecord/test/cases/deprecated_finder_test.rb b/activerecord/test/cases/deprecated_finder_test.rb new file mode 100755 index 0000000000..9c5a74cf02 --- /dev/null +++ b/activerecord/test/cases/deprecated_finder_test.rb @@ -0,0 +1,30 @@ +require 'abstract_unit' +require 'fixtures/entrant' + +class DeprecatedFinderTest < ActiveSupport::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/finder_test.rb b/activerecord/test/cases/finder_test.rb new file mode 100644 index 0000000000..14f494776c --- /dev/null +++ b/activerecord/test/cases/finder_test.rb @@ -0,0 +1,650 @@ +require 'abstract_unit' +require 'fixtures/author' +require 'fixtures/comment' +require 'fixtures/company' +require 'fixtures/topic' +require 'fixtures/reply' +require 'fixtures/entrant' +require 'fixtures/developer' +require 'fixtures/post' + +class FinderTest < ActiveSupport::TestCase + fixtures :companies, :topics, :entrants, :developers, :developers_projects, :posts, :comments, :accounts, :authors + + def test_find + assert_equal(topics(:first).title, Topic.find(1).title) + end + + # find should handle strings that come from URLs + # (example: Category.find(params[:id])) + def test_find_with_string + assert_equal(Topic.find(1).title,Topic.find("1").title) + end + + def test_exists + assert Topic.exists?(1) + assert Topic.exists?("1") + assert Topic.exists?(:author_name => "David") + assert Topic.exists?(:author_name => "Mary", :approved => true) + assert Topic.exists?(["parent_id = ?", 1]) + assert !Topic.exists?(45) + + begin + assert !Topic.exists?("foo") + rescue ActiveRecord::StatementInvalid + # PostgreSQL complains about string comparison with integer field + rescue Exception + flunk + end + + assert_raise(NoMethodError) { Topic.exists?([1,2]) } + end + + def test_find_by_array_of_one_id + assert_kind_of(Array, Topic.find([ 1 ])) + assert_equal(1, Topic.find([ 1 ]).length) + end + + def test_find_by_ids + assert_equal 2, Topic.find(1, 2).size + assert_equal topics(:second).title, Topic.find([2]).first.title + 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 + + # 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 + assert_equal 2, last_devs.size + end + + def test_find_an_empty_array + assert_equal [], Topic.find([]) + end + + def test_find_by_ids_missing_one + assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, 2, 45) } + end + + def test_find_all_with_limit + entrants = Entrant.find(:all, :order => "id ASC", :limit => 2) + + assert_equal(2, entrants.size) + assert_equal(entrants(:first).name, entrants.first.name) + 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) + + 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_orderings + developers = Developer.find(:all, :order => "salary ASC, id DESC", :limit => 3, :offset => 1) + assert_equal ["David", "fixture_10", "fixture_9"], developers.collect {|d| d.name} + end + + def test_find_with_limit_and_condition + developers = Developer.find(:all, :order => "id DESC", :conditions => "salary = 100000", :limit => 3, :offset =>7) + assert_equal(1, developers.size) + assert_equal("fixture_3", developers.first.name) + end + + def test_find_with_entire_select_statement + topics = Topic.find_by_sql "SELECT * FROM topics WHERE author_name = 'Mary'" + + assert_equal(1, topics.size) + assert_equal(topics(:second).title, topics.first.title) + end + + def test_find_with_prepared_select_statement + topics = Topic.find_by_sql ["SELECT * FROM topics WHERE author_name = ?", "Mary"] + + assert_equal(1, topics.size) + assert_equal(topics(:second).title, topics.first.title) + end + + def test_find_by_sql_with_sti_on_joined_table + accounts = Account.find_by_sql("SELECT * FROM accounts INNER JOIN companies ON companies.id = accounts.firm_id") + 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) + end + + def test_find_first_failing + first = Topic.find(:first, :conditions => "title = 'The First Topic!'") + assert_nil(first) + end + + def test_unexisting_record_exception_handling + assert_raises(ActiveRecord::RecordNotFound) { + Topic.find(1).parent + } + + Topic.find(2).topic + end + + def test_find_only_some_columns + topic = Topic.find(1, :select => "author_name") + assert_raises(ActiveRecord::MissingAttributeError) {topic.title} + assert_equal "David", topic.author_name + assert !topic.attribute_present?("title") + #assert !topic.respond_to?("title") + assert topic.attribute_present?("author_name") + assert topic.respond_to?("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_raises(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => ["approved = ?", true]) } + end + + def test_find_on_hash_conditions + assert Topic.find(1, :conditions => { :approved => false }) + assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { :approved => true }) } + end + + def test_find_on_hash_conditions_with_explicit_table_name + assert Topic.find(1, :conditions => { 'topics.approved' => false }) + assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { 'topics.approved' => true }) } + end + + def test_find_on_association_proxy_conditions + assert_equal [1, 2, 3, 5, 6, 7, 8, 9, 10], Comment.find_all_by_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_raises(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { :id => 2..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 + 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_raises(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { :author_name => "David", :title => "The First Topic", :replies_count => 1, :approved => true }) } + assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { :author_name => "David", :title => "HHC", :replies_count => 1, :approved => false }) } + assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { :author_name => "David", :title => "The First Topic", :replies_count => 1, :approved => true }) } + 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 + 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 + 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 + end + + def test_hash_condition_find_malformed + assert_raises(ActiveRecord::StatementInvalid) { + Company.find(:first, :conditions => { :id => 2, :dhh => true }) + } + 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" }) + 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') + end + + def test_hash_condition_find_with_nil + topic = Topic.find(:first, :conditions => { :last_read => nil } ) + assert_not_nil topic + assert_nil topic.last_read + 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_raises(ActiveRecord::PreparedStatementInvalid) { + Company.find(:first, :conditions => ["id=? AND name = ?", 2]) + } + assert_raises(ActiveRecord::PreparedStatementInvalid) { + Company.find(:first, :conditions => ["id=?", 2, 3, 4]) + } + end + + def test_bind_variables_with_quotes + Company.create("name" => "37signals' go'es agains") + assert Company.find(:first, :conditions => ["name = ?", "37signals' go'es agains"]) + 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"}]) + end + + def test_bind_arity + assert_nothing_raised { bind '' } + assert_raises(ActiveRecord::PreparedStatementInvalid) { bind '', 1 } + + assert_raises(ActiveRecord::PreparedStatementInvalid) { bind '?' } + assert_nothing_raised { bind '?', 1 } + assert_raises(ActiveRecord::PreparedStatementInvalid) { bind '?', 1, 1 } + end + + def test_named_bind_variables + assert_equal '1', bind(':a', :a => 1) # ' ruby-mode + assert_equal '1 1', bind(':a :a', :a => 1) # ' ruby-mode + + 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 + end + + def test_bind_enumerable + quoted_abc = %(#{ActiveRecord::Base.connection.quote('a')},#{ActiveRecord::Base.connection.quote('b')},#{ActiveRecord::Base.connection.quote('c')}) + + assert_equal '1,2,3', bind('?', [1, 2, 3]) + assert_equal quoted_abc, bind('?', %w(a b c)) + + assert_equal '1,2,3', bind(':a', :a => [1, 2, 3]) + assert_equal quoted_abc, bind(':a', :a => %w(a b c)) # ' + + require 'set' + assert_equal '1,2,3', bind('?', Set.new([1, 2, 3])) + assert_equal quoted_abc, bind('?', Set.new(%w(a b c))) + + assert_equal '1,2,3', bind(':a', :a => Set.new([1, 2, 3])) + assert_equal quoted_abc, bind(':a', :a => Set.new(%w(a b c))) # ' + end + + def test_bind_empty_enumerable + quoted_nil = ActiveRecord::Base.connection.quote(nil) + assert_equal quoted_nil, bind('?', []) + assert_equal " in (#{quoted_nil})", bind(' in (?)', []) + assert_equal "foo in (#{quoted_nil})", bind('foo in (?)', []) + end + + def test_bind_string + assert_equal ActiveRecord::Base.connection.quote(''), bind('?', '') + end + + def test_bind_record + o = Struct.new(:quoted_id).new(1) + assert_equal '1', bind('?', o) + + os = [o] * 3 + assert_equal '1,1,1', bind('?', os) + end + + def test_string_sanitation + assert_not_equal "#{ActiveRecord::Base.connection.quoted_string_prefix}'something ' 1=1'", ActiveRecord::Base.sanitize("something ' 1=1") + assert_equal "#{ActiveRecord::Base.connection.quoted_string_prefix}'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])) + assert_equal(2, Entrant.count_by_sql(["SELECT COUNT(*) FROM entrants WHERE id > ?", 1])) + end + + def test_find_by_one_attribute + assert_equal topics(:first), Topic.find_by_title("The First Topic") + assert_nil Topic.find_by_title("The First Topic!") + end + + def test_find_by_one_attribute_caches_dynamic_finder + # ensure this test can run independently of order + class << Topic; self; end.send(:remove_method, :find_by_title) if Topic.respond_to?(:find_by_title) + assert !Topic.respond_to?(:find_by_title) + t = Topic.find_by_title("The First Topic") + assert Topic.respond_to?(:find_by_title) + end + + def test_dynamic_finder_returns_same_results_after_caching + # ensure this test can run independently of order + class << Topic; self; end.send(:remove_method, :find_by_title) if Topic.respond_to?(:find_by_title) + t = Topic.find_by_title("The First Topic") + assert_equal t, Topic.find_by_title("The First Topic") # find_by_title has been cached + 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]) + end + + def test_dynamic_finder_on_one_attribute_with_conditions_caches_method + # ensure this test can run independently of order + class << Account; self; end.send(:remove_method, :find_by_credit_limit) if Account.respond_to?(:find_by_credit_limit) + assert !Account.respond_to?(:find_by_credit_limit) + a = Account.find_by_credit_limit(50, :conditions => ['firm_id = ?', 6]) + assert Account.respond_to?(:find_by_credit_limit) + end + + 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.respond_to?(: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 + 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]) + end + + def test_find_by_one_missing_attribute + assert_raises(NoMethodError) { Topic.find_by_undertitle("The First Topic!") } + end + + def test_find_by_invalid_method_syntax + assert_raises(NoMethodError) { Topic.fail_to_find_by_title("The First Topic") } + assert_raises(NoMethodError) { Topic.find_by_title?("The First Topic") } + assert_raises(NoMethodError) { Topic.fail_to_find_or_create_by_title("Nonexistent Title") } + assert_raises(NoMethodError) { Topic.find_or_create_by_title?("Nonexistent Title") } + end + + def test_find_by_two_attributes + assert_equal topics(:first), Topic.find_by_title_and_author_name("The First Topic", "David") + assert_nil Topic.find_by_title_and_author_name("The First Topic", "Mary") + 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_with_options + topics = Topic.find_all_by_content("Have a nice day", :order => "id DESC") + assert topics(:first), topics.last + + topics = Topic.find_all_by_content("Have a nice day", :order => "id") + assert 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's 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 1, 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 1, topics.size + assert_nil topics[0].last_read + 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.new_record? + 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.new_record? + 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.new_record? + assert_equal "38signals", sig38.name + assert_equal 17, sig38.firm_id + assert_equal 23, sig38.client_of + end + + def test_find_or_initialize_from_one_attribute + sig38 = Company.find_or_initialize_by_name("38signals") + assert_equal "38signals", sig38.name + assert sig38.new_record? + 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.new_record? + 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.new_record? + end + + def test_dynamic_find_or_initialize_from_one_attribute_caches_method + class << Company; self; end.send(:remove_method, :find_or_initialize_by_name) if Company.respond_to?(:find_or_initialize_by_name) + assert !Company.respond_to?(:find_or_initialize_by_name) + sig38 = Company.find_or_initialize_by_name("38signals") + assert Company.respond_to?(:find_or_initialize_by_name) + 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.new_record? + 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.new_record? + end + + def test_find_with_bad_sql + assert_raises(ActiveRecord::StatementInvalid) { Topic.find_by_sql "select 1 from badtable" } + end + + def test_find_with_invalid_params + assert_raises(ArgumentError) { Topic.find :first, :join => "It should be `joins'" } + assert_raises(ArgumentError) { Topic.find :first, :conditions => '1 = 1', :join => "It should be `joins'" } + end + + def test_dynamic_finder_with_invalid_params + assert_raises(ArgumentError) { Topic.find_by_title 'No Title', :join => "It should be `joins'" } + end + + def test_find_all_with_limit + first_five_developers = Developer.find :all, :order => 'id ASC', :limit => 5 + assert_equal 5, first_five_developers.length + assert_equal 'David', first_five_developers.first.name + assert_equal 'fixture_5', first_five_developers.last.name + + no_developers = Developer.find :all, :order => 'id ASC', :limit => 0 + assert_equal 0, no_developers.length + end + + def test_find_all_with_limit_and_offset + first_three_developers = Developer.find :all, :order => 'id ASC', :limit => 3, :offset => 0 + second_three_developers = Developer.find :all, :order => 'id ASC', :limit => 3, :offset => 3 + last_two_developers = Developer.find :all, :order => 'id ASC', :limit => 2, :offset => 8 + + assert_equal 3, first_three_developers.length + assert_equal 3, second_three_developers.length + assert_equal 2, last_two_developers.length + + assert_equal 'David', first_three_developers.first.name + assert_equal 'fixture_4', second_three_developers.first.name + assert_equal 'fixture_9', last_two_developers.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 + last_posts = Post.find :all, :order => ' author_id, id ', :limit => 3, :offset => 6 + + 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]], last_posts.map { |p| [p.author_id, p.id] } + end + + def test_find_all_with_join + developers_on_project_one = Developer.find( + :all, + :joins => 'LEFT JOIN developers_projects ON developers.id = developers_projects.developer_id', + :conditions => 'project_id=1' + ) + assert_equal 3, developers_on_project_one.length + developer_names = developers_on_project_one.map { |d| d.name } + assert developer_names.include?('David') + assert developer_names.include?('Jamis') + end + + def test_joins_dont_clobber_id + first = Firm.find( + :first, + :joins => 'INNER JOIN companies AS clients ON clients.firm_id = companies.id', + :conditions => 'companies.id = 1' + ) + assert_equal 1, first.id + end + + 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'") + end + end + + # http://dev.rubyonrails.org/ticket/6778 + def test_find_ignores_previously_inserted_record + post = Post.create!(:title => 'test', :body => 'it out') + assert_equal [], Post.find_all_by_id(nil) + end + + def test_find_by_empty_ids + assert_equal [], Post.find([]) + end + + def test_find_by_empty_in_condition + assert_equal [], Post.find(:all, :conditions => ['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') + end + + def test_select_value + assert_equal "37signals", Company.connection.select_value("SELECT name FROM companies WHERE id = 1") + assert_nil Company.connection.select_value("SELECT name FROM companies WHERE id = -1") + # make sure we didn't break count... + assert_equal 0, Company.count_by_sql("SELECT COUNT(*) FROM companies WHERE name = 'Halliburton'") + assert_equal 1, Company.count_by_sql("SELECT COUNT(*) FROM companies WHERE name = '37signals'") + end + + def test_select_values + assert_equal ["1","2","3","4","5","6","7","8","9"], Company.connection.select_values("SELECT id FROM companies ORDER BY id").map! { |i| i.to_s } + assert_equal ["37signals","Summit","Microsoft", "Flamboyant Software", "Ex Nihilo", "RailsCore", "Leetsoft", "Jadedpixel", "Odegy"], Company.connection.select_values("SELECT name FROM companies ORDER BY id") + end + + def test_select_rows + assert_equal( + [["1", nil, nil, "37signals"], + ["2", "1", "2", "Summit"], + ["3", "1", "1", "Microsoft"]], + Company.connection.select_rows("SELECT id, firm_id, client_of, name FROM companies WHERE id IN (1,2,3) ORDER BY id").map! {|i| i.map! {|j| j.to_s unless j.nil?}}) + assert_equal [["1", "37signals"], ["2", "Summit"], ["3", "Microsoft"]], + Company.connection.select_rows("SELECT id, name FROM companies WHERE id IN (1,2,3) ORDER BY id").map! {|i| i.map! {|j| j.to_s unless j.nil?}} + end + + protected + def bind(statement, *vars) + if vars.first.is_a?(Hash) + ActiveRecord::Base.send(:replace_named_bind_variables, statement, vars.first) + else + ActiveRecord::Base.send(:replace_bind_variables, statement, vars) + end + end +end diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb new file mode 100755 index 0000000000..51dbee6af3 --- /dev/null +++ b/activerecord/test/cases/fixtures_test.rb @@ -0,0 +1,594 @@ +require 'abstract_unit' +require 'fixtures/post' +require 'fixtures/binary' +require 'fixtures/topic' +require 'fixtures/computer' +require 'fixtures/developer' +require 'fixtures/company' +require 'fixtures/task' +require 'fixtures/reply' +require 'fixtures/joke' +require 'fixtures/course' +require 'fixtures/category' +require 'fixtures/parrot' +require 'fixtures/pirate' +require 'fixtures/treasure' +require 'fixtures/matey' +require 'fixtures/ship' + +class FixturesTest < ActiveSupport::TestCase + self.use_instantiated_fixtures = true + self.use_transactional_fixtures = false + + fixtures :topics, :developers, :accounts, :tasks, :categories, :funny_jokes, :binaries + + FIXTURES = %w( accounts binaries companies customers + developers developers_projects entrants + movies projects subscribers topics tasks ) + MATCH_ATTRIBUTE_NAME = /[a-zA-Z][-_\w]*/ + + BINARY_FIXTURE_PATH = File.dirname(__FILE__) + '/fixtures/flowers.jpg' + + def test_clean_fixtures + FIXTURES.each do |name| + fixtures = nil + assert_nothing_raised { fixtures = create_fixtures(name) } + assert_kind_of(Fixtures, fixtures) + fixtures.each { |name, fixture| + fixture.each { |key, value| + assert_match(MATCH_ATTRIBUTE_NAME, key) + } + } + end + end + + def test_multiple_clean_fixtures + fixtures_array = nil + assert_nothing_raised { fixtures_array = create_fixtures(*FIXTURES) } + assert_kind_of(Array, fixtures_array) + fixtures_array.each { |fixtures| assert_kind_of(Fixtures, fixtures) } + end + + def test_attributes + topics = create_fixtures("topics") + assert_equal("The First Topic", topics["first"]["title"]) + assert_nil(topics["second"]["author_email_address"]) + end + + def test_inserts + topics = create_fixtures("topics") + first_row = ActiveRecord::Base.connection.select_one("SELECT * FROM topics WHERE author_name = 'David'") + assert_equal("The First Topic", first_row["title"]) + + second_row = ActiveRecord::Base.connection.select_one("SELECT * FROM topics WHERE author_name = 'Mary'") + assert_nil(second_row["author_email_address"]) + end + + if ActiveRecord::Base.connection.supports_migrations? + def test_inserts_with_pre_and_suffix + # Reset cache to make finds on the new table work + Fixtures.reset_cache + + ActiveRecord::Base.connection.create_table :prefix_topics_suffix do |t| + t.column :title, :string + t.column :author_name, :string + t.column :author_email_address, :string + t.column :written_on, :datetime + t.column :bonus_time, :time + t.column :last_read, :date + t.column :content, :string + t.column :approved, :boolean, :default => true + t.column :replies_count, :integer, :default => 0 + t.column :parent_id, :integer + t.column :type, :string, :limit => 50 + end + + # Store existing prefix/suffix + old_prefix = ActiveRecord::Base.table_name_prefix + old_suffix = ActiveRecord::Base.table_name_suffix + + # Set a prefix/suffix we can test against + 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"]) + + second_row = ActiveRecord::Base.connection.select_one("SELECT * FROM prefix_topics_suffix WHERE author_name = 'Mary'") + assert_nil(second_row["author_email_address"]) + 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 + end + end + + def test_insert_with_datetime + topics = create_fixtures("tasks") + first = Task.find(1) + assert first + end + + def test_logger_level_invariant + level = ActiveRecord::Base.logger.level + create_fixtures('topics') + assert_equal level, ActiveRecord::Base.logger.level + end + + def test_instantiation + topics = create_fixtures("topics") + assert_kind_of Topic, topics["first"].find + end + + def test_complete_instantiation + assert_equal 2, @topics.size + assert_equal "The First Topic", @first.title + end + + def test_fixtures_from_root_yml_with_instantiation + # assert_equal 2, @accounts.size + assert_equal 50, @unknown.credit_limit + end + + def test_erb_in_fixtures + assert_equal 11, @developers.size + assert_equal "fixture_5", @dev_5.name + end + + def test_empty_yaml_fixture + assert_not_nil Fixtures.new( Account.connection, "accounts", 'Account', File.dirname(__FILE__) + "/fixtures/naked/yml/accounts") + end + + def test_empty_yaml_fixture_with_a_comment_in_it + assert_not_nil Fixtures.new( Account.connection, "companies", 'Company', File.dirname(__FILE__) + "/fixtures/naked/yml/companies") + end + + def test_dirty_dirty_yaml_file + assert_raises(Fixture::FormatError) do + Fixtures.new( Account.connection, "courses", 'Course', File.dirname(__FILE__) + "/fixtures/naked/yml/courses") + end + end + + def test_empty_csv_fixtures + assert_not_nil Fixtures.new( Account.connection, "accounts", 'Account', File.dirname(__FILE__) + "/fixtures/naked/csv/accounts") + end + + def test_omap_fixtures + assert_nothing_raised do + fixtures = Fixtures.new(Account.connection, 'categories', 'Category', File.dirname(__FILE__) + '/fixtures/categories_ordered') + + i = 0 + fixtures.each do |name, fixture| + assert_equal "fixture_no_#{i}", name + assert_equal "Category #{i}", fixture['name'] + i += 1 + end + end + end + + def test_yml_file_in_subdirectory + assert_equal(categories(:sub_special_1).name, "A special category in a subdir file") + assert_equal(categories(:sub_special_1).class, SpecialCategory) + end + + def test_subsubdir_file_with_arbitrary_name + assert_equal(categories(:sub_special_3).name, "A special category in an arbitrarily named subsubdir file") + assert_equal(categories(:sub_special_3).class, SpecialCategory) + end + + def test_binary_in_fixtures + assert_equal 1, @binaries.size + data = File.open(BINARY_FIXTURE_PATH, "rb").read.freeze + assert_equal data, @flowers.data + end +end + +if Account.connection.respond_to?(:reset_pk_sequence!) + class FixturesResetPkSequenceTest < ActiveSupport::TestCase + fixtures :accounts + fixtures :companies + + def setup + @instances = [Account.new(:credit_limit => 50), Company.new(:name => 'RoR Consulting')] + Fixtures.reset_cache # make sure tables get reinitialized + end + + def test_resets_to_min_pk_with_specified_pk_and_sequence + @instances.each do |instance| + model = instance.class + model.delete_all + model.connection.reset_pk_sequence!(model.table_name, model.primary_key, model.sequence_name) + + instance.save! + assert_equal 1, instance.id, "Sequence reset for #{model.table_name} failed." + end + end + + def test_resets_to_min_pk_with_default_pk_and_sequence + @instances.each do |instance| + model = instance.class + model.delete_all + model.connection.reset_pk_sequence!(model.table_name) + + instance.save! + assert_equal 1, instance.id, "Sequence reset for #{model.table_name} failed." + end + end + + def test_create_fixtures_resets_sequences_when_not_cached + @instances.each do |instance| + max_id = create_fixtures(instance.class.table_name).inject(0) do |max_id, (name, fixture)| + fixture_id = fixture['id'].to_i + fixture_id > max_id ? fixture_id : max_id + end + + # Clone the last fixture to check that it gets the next greatest id. + instance.save! + assert_equal max_id + 1, instance.id, "Sequence reset for #{instance.class.table_name} failed." + end + end + end +end + +class FixturesWithoutInstantiationTest < ActiveSupport::TestCase + self.use_instantiated_fixtures = false + fixtures :topics, :developers, :accounts + + def test_without_complete_instantiation + assert_nil @first + assert_nil @topics + assert_nil @developers + assert_nil @accounts + end + + def test_fixtures_from_root_yml_without_instantiation + assert_nil @unknown + end + + def test_accessor_methods + assert_equal "The First Topic", topics(:first).title + assert_equal "Jamis", developers(:jamis).name + assert_equal 50, accounts(:signals37).credit_limit + end + + def test_accessor_methods_with_multiple_args + assert_equal 2, topics(:first, :second).size + assert_raise(StandardError) { topics([:first, :second]) } + end + + uses_mocha 'reloading_fixtures_through_accessor_methods' do + def test_reloading_fixtures_through_accessor_methods + assert_equal "The First Topic", topics(:first).title + @loaded_fixtures['topics']['first'].expects(:find).returns(stub(:title => "Fresh Topic!")) + assert_equal "Fresh Topic!", topics(:first, true).title + end + end +end + +class FixturesWithoutInstanceInstantiationTest < ActiveSupport::TestCase + self.use_instantiated_fixtures = true + self.use_instantiated_fixtures = :no_instances + + fixtures :topics, :developers, :accounts + + def test_without_instance_instantiation + assert_nil @first + assert_not_nil @topics + assert_not_nil @developers + assert_not_nil @accounts + end +end + +class TransactionalFixturesTest < ActiveSupport::TestCase + self.use_instantiated_fixtures = true + self.use_transactional_fixtures = true + + fixtures :topics + + def test_destroy + assert_not_nil @first + @first.destroy + end + + def test_destroy_just_kidding + assert_not_nil @first + end +end + +class MultipleFixturesTest < ActiveSupport::TestCase + fixtures :topics + fixtures :developers, :accounts + + def test_fixture_table_names + assert_equal %w(topics developers accounts), fixture_table_names + end +end + +class SetupTest < ActiveSupport::TestCase + # fixtures :topics + + def setup + @first = true + end + + def test_nothing + end +end + +class SetupSubclassTest < SetupTest + def setup + super + @second = true + end + + def test_subclassing_should_preserve_setups + assert @first + assert @second + end +end + + +class OverlappingFixturesTest < ActiveSupport::TestCase + fixtures :topics, :developers + fixtures :developers, :accounts + + def test_fixture_table_names + assert_equal %w(topics developers accounts), fixture_table_names + end +end + +class ForeignKeyFixturesTest < ActiveSupport::TestCase + fixtures :fk_test_has_pk, :fk_test_has_fk + + # if foreign keys are implemented and fixtures + # are not deleted in reverse order then this test + # case will raise StatementInvalid + + def test_number1 + assert true + end + + def test_number2 + assert true + end +end + +class SetTableNameFixturesTest < ActiveSupport::TestCase + set_fixture_class :funny_jokes => 'Joke' + fixtures :funny_jokes + + def test_table_method + assert_kind_of Joke, funny_jokes(:a_joke) + end +end + +class CustomConnectionFixturesTest < ActiveSupport::TestCase + set_fixture_class :courses => Course + fixtures :courses + + def test_connection + assert_kind_of Course, courses(:ruby) + assert_equal Course.connection, courses(:ruby).connection + end +end + +class InvalidTableNameFixturesTest < ActiveSupport::TestCase + fixtures :funny_jokes + + def test_raises_error + assert_raises FixtureClassNotFound do + funny_jokes(:a_joke) + end + end +end + +class CheckEscapedYamlFixturesTest < ActiveSupport::TestCase + set_fixture_class :funny_jokes => 'Joke' + fixtures :funny_jokes + + def test_proper_escaped_fixture + assert_equal "The \\n Aristocrats\nAte the candy\n", funny_jokes(:another_joke).name + end +end + +class DevelopersProject; end +class ManyToManyFixturesWithClassDefined < ActiveSupport::TestCase + fixtures :developers_projects + + def test_this_should_run_cleanly + assert true + end +end + +class FixturesBrokenRollbackTest < ActiveSupport::TestCase + def blank_setup; end + alias_method :ar_setup_fixtures, :setup_fixtures + alias_method :setup_fixtures, :blank_setup + alias_method :setup, :blank_setup + + def blank_teardown; end + alias_method :ar_teardown_fixtures, :teardown_fixtures + alias_method :teardown_fixtures, :blank_teardown + alias_method :teardown, :blank_teardown + + def test_no_rollback_in_teardown_unless_transaction_active + assert_equal 0, Thread.current['open_transactions'] + assert_raise(RuntimeError) { ar_setup_fixtures } + assert_equal 0, Thread.current['open_transactions'] + assert_nothing_raised { ar_teardown_fixtures } + assert_equal 0, Thread.current['open_transactions'] + end + + private + def load_fixtures + raise 'argh' + end +end + +class LoadAllFixturesTest < ActiveSupport::TestCase + self.fixture_path= File.join(File.dirname(__FILE__), '/fixtures/all') + fixtures :all + + def test_all_there + assert_equal %w(developers people tasks), fixture_table_names.sort + end +end + +class FasterFixturesTest < ActiveSupport::TestCase + fixtures :categories, :authors + + def load_extra_fixture(name) + fixture = create_fixtures(name) + assert fixture.is_a?(Fixtures) + @loaded_fixtures[fixture.table_name] = fixture + end + + def test_cache + assert Fixtures.fixture_is_cached?(ActiveRecord::Base.connection, 'categories') + assert Fixtures.fixture_is_cached?(ActiveRecord::Base.connection, 'authors') + + assert_no_queries do + create_fixtures('categories') + create_fixtures('authors') + end + + load_extra_fixture('posts') + assert Fixtures.fixture_is_cached?(ActiveRecord::Base.connection, 'posts') + self.class.setup_fixture_accessors('posts') + assert_equal 'Welcome to the weblog', posts(:welcome).title + end +end + +class FoxyFixturesTest < ActiveSupport::TestCase + fixtures :parrots, :parrots_pirates, :pirates, :treasures, :mateys, :ships, :computers, :developers + + def test_identifies_strings + assert_equal(Fixtures.identify("foo"), Fixtures.identify("foo")) + assert_not_equal(Fixtures.identify("foo"), Fixtures.identify("FOO")) + end + + def test_identifies_symbols + assert_equal(Fixtures.identify(:foo), Fixtures.identify(:foo)) + end + + TIMESTAMP_COLUMNS = %w(created_at created_on updated_at updated_on) + + def test_populates_timestamp_columns + TIMESTAMP_COLUMNS.each do |property| + assert_not_nil(parrots(:george).send(property), "should set #{property}") + end + end + + def test_does_not_populate_timestamp_columns_if_model_has_set_record_timestamps_to_false + TIMESTAMP_COLUMNS.each do |property| + assert_nil(ships(:black_pearl).send(property), "should not set #{property}") + end + end + + def test_populates_all_columns_with_the_same_time + last = nil + + TIMESTAMP_COLUMNS.each do |property| + current = parrots(:george).send(property) + last ||= current + + assert_equal(last, current) + last = current + end + end + + def test_only_populates_columns_that_exist + assert_not_nil(pirates(:blackbeard).created_on) + assert_not_nil(pirates(:blackbeard).updated_on) + end + + def test_preserves_existing_fixture_data + assert_equal(2.weeks.ago.to_date, pirates(:redbeard).created_on.to_date) + assert_equal(2.weeks.ago.to_date, pirates(:redbeard).updated_on.to_date) + end + + def test_generates_unique_ids + assert_not_nil(parrots(:george).id) + assert_not_equal(parrots(:george).id, parrots(:louis).id) + end + + def test_automatically_sets_primary_key + assert_not_nil(ships(:black_pearl)) + end + + def test_preserves_existing_primary_key + assert_equal(2, ships(:interceptor).id) + end + + def test_resolves_belongs_to_symbols + assert_equal(parrots(:george), pirates(:blackbeard).parrot) + end + + def test_ignores_belongs_to_symbols_if_association_and_foreign_key_are_named_the_same + assert_equal(developers(:david), computers(:workstation).developer) + end + + def test_supports_join_tables + assert(pirates(:blackbeard).parrots.include?(parrots(:george))) + assert(pirates(:blackbeard).parrots.include?(parrots(:louis))) + assert(parrots(:george).pirates.include?(pirates(:blackbeard))) + end + + def test_supports_inline_habtm + assert(parrots(:george).treasures.include?(treasures(:diamond))) + assert(parrots(:george).treasures.include?(treasures(:sapphire))) + assert(!parrots(:george).treasures.include?(treasures(:ruby))) + end + + def test_supports_inline_habtm_with_specified_id + assert(parrots(:polly).treasures.include?(treasures(:ruby))) + assert(parrots(:polly).treasures.include?(treasures(:sapphire))) + assert(!parrots(:polly).treasures.include?(treasures(:diamond))) + end + + def test_supports_yaml_arrays + assert(parrots(:louis).treasures.include?(treasures(:diamond))) + assert(parrots(:louis).treasures.include?(treasures(:sapphire))) + end + + def test_strips_DEFAULTS_key + assert_raise(StandardError) { parrots(:DEFAULTS) } + + # this lets us do YAML defaults and not have an extra fixture entry + %w(sapphire ruby).each { |t| assert(parrots(:davey).treasures.include?(treasures(t))) } + end + + def test_supports_label_interpolation + assert_equal("frederick", parrots(:frederick).name) + end + + def test_supports_polymorphic_belongs_to + assert_equal(pirates(:redbeard), treasures(:sapphire).looter) + assert_equal(parrots(:louis), treasures(:ruby).looter) + end + + def test_only_generates_a_pk_if_necessary + m = Matey.find(:first) + m.pirate = pirates(:blackbeard) + m.target = pirates(:redbeard) + end + + def test_supports_sti + assert_kind_of DeadParrot, parrots(:polly) + assert_equal pirates(:blackbeard), parrots(:polly).killer + end +end + +class ActiveSupportSubclassWithFixturesTest < ActiveSupport::TestCase + fixtures :parrots + + # This seemingly useless assertion catches a bug that caused the fixtures + # setup code call nil[] + def test_foo + assert_equal parrots(:louis), Parrot.find_by_name("King Louis") + end +end diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb new file mode 100755 index 0000000000..092503f279 --- /dev/null +++ b/activerecord/test/cases/inheritance_test.rb @@ -0,0 +1,211 @@ +require 'abstract_unit' +require 'fixtures/company' +require 'fixtures/project' +require 'fixtures/subscriber' + +class InheritanceTest < ActiveSupport::TestCase + fixtures :companies, :projects, :subscribers, :accounts + + 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' + end + + def test_a_bad_type_column + #SQLServer need to turn Identity Insert On before manually inserting into the Identity column + if current_adapter?(:SQLServerAdapter, :SybaseAdapter) + Company.connection.execute "SET IDENTITY_INSERT companies ON" + end + Company.connection.insert "INSERT INTO companies (id, #{QUOTED_TYPE}, name) VALUES(100, 'bad_class!', 'Not happening')" + + #We then need to turn it back Off before continuing. + if current_adapter?(:SQLServerAdapter, :SybaseAdapter) + Company.connection.execute "SET IDENTITY_INSERT companies OFF" + end + assert_raises(ActiveRecord::SubclassNotFound) { Company.find(100) } + end + + def test_inheritance_find + assert Company.find(1).kind_of?(Firm), "37signals should be a firm" + assert Firm.find(1).kind_of?(Firm), "37signals should be a firm" + assert Company.find(2).kind_of?(Client), "Summit should be a client" + assert Client.find(2).kind_of?(Client), "Summit should be a client" + end + + def test_alt_inheritance_find + switch_to_alt_inheritance_column + test_inheritance_find + switch_to_default_inheritance_column + end + + def test_inheritance_find_all + companies = Company.find(:all, :order => 'id') + assert companies[0].kind_of?(Firm), "37signals should be a firm" + assert companies[1].kind_of?(Client), "Summit should be a client" + end + + def test_alt_inheritance_find_all + switch_to_alt_inheritance_column + test_inheritance_find_all + switch_to_default_inheritance_column + end + + def test_inheritance_save + firm = Firm.new + firm.name = "Next Angle" + firm.save + + next_angle = Company.find(firm.id) + assert next_angle.kind_of?(Firm), "Next Angle should be a firm" + end + + def test_alt_inheritance_save + switch_to_alt_inheritance_column + test_inheritance_save + switch_to_default_inheritance_column + end + + def test_inheritance_condition + assert_equal 9, Company.count + assert_equal 2, Firm.count + assert_equal 3, Client.count + end + + def test_alt_inheritance_condition + switch_to_alt_inheritance_column + test_inheritance_condition + switch_to_default_inheritance_column + end + + def test_finding_incorrect_type_data + assert_raises(ActiveRecord::RecordNotFound) { Firm.find(2) } + assert_nothing_raised { Firm.find(1) } + end + + def test_alt_finding_incorrect_type_data + switch_to_alt_inheritance_column + test_finding_incorrect_type_data + switch_to_default_inheritance_column + end + + 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 "37signals", Firm.find(:all).first.name + end + + def test_alt_update_all_within_inheritance + switch_to_alt_inheritance_column + test_update_all_within_inheritance + switch_to_default_inheritance_column + end + + def test_destroy_all_within_inheritance + Client.destroy_all + assert_equal 0, Client.count + assert_equal 2, Firm.count + end + + def test_alt_destroy_all_within_inheritance + switch_to_alt_inheritance_column + test_destroy_all_within_inheritance + switch_to_default_inheritance_column + 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'") + end + + def test_alt_find_first_within_inheritance + switch_to_alt_inheritance_column + test_find_first_within_inheritance + switch_to_default_inheritance_column + end + + def test_complex_inheritance + very_special_client = VerySpecialClient.create("name" => "veryspecial") + assert_equal very_special_client, VerySpecialClient.find(:first, :conditions => "name = 'veryspecial'") + 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, Client.find(very_special_client.id) + end + + def test_alt_complex_inheritance + switch_to_alt_inheritance_column + test_complex_inheritance + switch_to_default_inheritance_column + end + + def test_eager_load_belongs_to_something_inherited + account = Account.find(1, :include => :firm) + assert_not_nil account.instance_variable_get("@firm"), "nil proves eager load failed" + end + + def test_alt_eager_loading + switch_to_alt_inheritance_column + test_eager_load_belongs_to_something_inherited + switch_to_default_inheritance_column + end + + def test_inheritance_without_mapping + assert_kind_of SpecialSubscriber, SpecialSubscriber.find("webster132") + assert_nothing_raised { s = SpecialSubscriber.new("name" => "And breaaaaathe!"); s.id = 'roger'; s.save } + end + + 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| + c['type'] = nil + c.save + end + [ Company, Firm, Client].each { |klass| klass.reset_column_information } + Company.set_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') + end +end + + +class InheritanceComputeTypeTest < ActiveSupport::TestCase + fixtures :companies + + def setup + Dependencies.log_activity = true + end + + def teardown + Dependencies.log_activity = false + self.class.const_remove :FirmOnTheFly rescue nil + Firm.const_remove :FirmOnTheFly rescue nil + end + + def test_instantiation_doesnt_try_to_require_corresponding_file + foo = Firm.find(:first).clone + foo.ruby_type = foo.type = 'FirmOnTheFly' + foo.save! + + # Should fail without FirmOnTheFly in the type condition. + assert_raise(ActiveRecord::RecordNotFound) { Firm.find(foo.id) } + + # Nest FirmOnTheFly in the test case where Dependencies won't see it. + self.class.const_set :FirmOnTheFly, Class.new(Firm) + assert_raise(ActiveRecord::SubclassNotFound) { Firm.find(foo.id) } + + # Nest FirmOnTheFly in Firm where Dependencies will see it. + # This is analogous to nesting models in a migration. + Firm.const_set :FirmOnTheFly, Class.new(Firm) + + # And instantiate will find the existing constant rather than trying + # to require firm_on_the_fly. + assert_nothing_raised { assert_kind_of Firm::FirmOnTheFly, Firm.find(foo.id) } + end +end diff --git a/activerecord/test/cases/json_serialization_test.rb b/activerecord/test/cases/json_serialization_test.rb new file mode 100644 index 0000000000..49711f625b --- /dev/null +++ b/activerecord/test/cases/json_serialization_test.rb @@ -0,0 +1,180 @@ +require 'abstract_unit' +require 'fixtures/contact' +require 'fixtures/post' +require 'fixtures/author' +require 'fixtures/tagging' +require 'fixtures/tag' +require 'fixtures/comment' + +class JsonSerializationTest < ActiveSupport::TestCase + def setup + @contact = Contact.new( + :name => 'Konata Izumi', + :age => 16, + :avatar => 'binarydata', + :created_at => Time.utc(2006, 8, 1), + :awesome => true, + :preferences => { :shows => 'anime' } + ) + end + + def test_should_encode_all_encodable_attributes + json = @contact.to_json + + assert_match %r{"name": "Konata Izumi"}, json + assert_match %r{"age": 16}, json + assert json.include?(%("created_at": #{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))})) + assert_match %r{"awesome": true}, json + assert_match %r{"preferences": \{"shows": "anime"\}}, json + end + + def test_should_allow_attribute_filtering_with_only + json = @contact.to_json(:only => [:name, :age]) + + assert_match %r{"name": "Konata Izumi"}, json + assert_match %r{"age": 16}, json + assert_no_match %r{"awesome": true}, json + assert !json.include?(%("created_at": #{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))})) + assert_no_match %r{"preferences": \{"shows": "anime"\}}, json + end + + def test_should_allow_attribute_filtering_with_except + json = @contact.to_json(:except => [:name, :age]) + + assert_no_match %r{"name": "Konata Izumi"}, json + assert_no_match %r{"age": 16}, json + assert_match %r{"awesome": true}, json + assert json.include?(%("created_at": #{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))})) + assert_match %r{"preferences": \{"shows": "anime"\}}, json + end + + def test_methods_are_called_on_object + # Define methods on fixture. + def @contact.label; "Has cheezburger"; end + def @contact.favorite_quote; "Constraints are liberating"; end + + # Single method. + assert_match %r{"label": "Has cheezburger"}, @contact.to_json(:only => :name, :methods => :label) + + # Both methods. + methods_json = @contact.to_json(:only => :name, :methods => [:label, :favorite_quote]) + assert_match %r{"label": "Has cheezburger"}, methods_json + assert_match %r{"favorite_quote": "Constraints are liberating"}, methods_json + end +end + +class DatabaseConnectedJsonEncodingTest < ActiveSupport::TestCase + fixtures :authors, :posts, :comments, :tags, :taggings + + def setup + @david = authors(:david) + @mary = authors(:mary) + end + + def test_includes_uses_association_name + json = @david.to_json(:include => :posts) + + assert_match %r{"posts": \[}, json + + assert_match %r{"id": 1}, json + assert_match %r{"name": "David"}, json + + assert_match %r{"author_id": 1}, json + assert_match %r{"title": "Welcome to the weblog"}, json + assert_match %r{"body": "Such a lovely day"}, json + + assert_match %r{"title": "So I was thinking"}, json + assert_match %r{"body": "Like I hopefully always am"}, json + end + + def test_includes_uses_association_name_and_applies_attribute_filters + json = @david.to_json(:include => { :posts => { :only => :title } }) + + assert_match %r{"name": "David"}, json + assert_match %r{"posts": \[}, json + + assert_match %r{"title": "Welcome to the weblog"}, json + assert_no_match %r{"body": "Such a lovely day"}, json + + assert_match %r{"title": "So I was thinking"}, json + assert_no_match %r{"body": "Like I hopefully always am"}, json + end + + def test_includes_fetches_second_level_associations + json = @david.to_json(:include => { :posts => { :include => { :comments => { :only => :body } } } }) + + assert_match %r{"name": "David"}, json + assert_match %r{"posts": \[}, json + + assert_match %r{"comments": \[}, json + assert_match %r{\{"body": "Thank you again for the welcome"\}}, json + assert_match %r{\{"body": "Don't think too hard"\}}, json + assert_no_match %r{"post_id": }, json + end + + def test_includes_fetches_nth_level_associations + json = @david.to_json( + :include => { + :posts => { + :include => { + :taggings => { + :include => { + :tag => { :only => :name } + } + } + } + } + }) + + assert_match %r{"name": "David"}, json + assert_match %r{"posts": \[}, json + + assert_match %r{"taggings": \[}, json + assert_match %r{"tag": \{"name": "General"\}}, json + end + + def test_should_not_call_methods_on_associations_that_dont_respond + def @david.favorite_quote; "Constraints are liberating"; end + json = @david.to_json(:include => :posts, :methods => :favorite_quote) + + assert !@david.posts.first.respond_to?(:favorite_quote) + assert_match %r{"favorite_quote": "Constraints are liberating"}, json + assert_equal %r{"favorite_quote": }.match(json).size, 1 + end + + def test_should_allow_only_option_for_list_of_authors + authors = [@david, @mary] + + assert_equal %([{"name": "David"}, {"name": "Mary"}]), authors.to_json(:only => :name) + end + + def test_should_allow_except_option_for_list_of_authors + authors = [@david, @mary] + + assert_equal %([{"id": 1}, {"id": 2}]), authors.to_json(:except => [:name, :author_address_id]) + end + + def test_should_allow_includes_for_list_of_authors + authors = [@david, @mary] + json = authors.to_json( + :only => :name, + :include => { + :posts => { :only => :id } + } + ) + + ['"name": "David"', '"posts": [', '{"id": 1}', '{"id": 2}', '{"id": 4}', + '{"id": 5}', '{"id": 6}', '"name": "Mary"', '"posts": [{"id": 7}]'].each do |fragment| + assert json.include?(fragment), json + end + end + + def test_should_allow_options_for_hash_of_authors + authors_hash = { + 1 => @david, + 2 => @mary + } + + assert_equal %({1: {"name": "David"}}), authors_hash.to_json(:only => [1, :name]) + end +end diff --git a/activerecord/test/cases/lifecycle_test.rb b/activerecord/test/cases/lifecycle_test.rb new file mode 100755 index 0000000000..91c6b8cc43 --- /dev/null +++ b/activerecord/test/cases/lifecycle_test.rb @@ -0,0 +1,137 @@ +require 'abstract_unit' +require 'fixtures/topic' +require 'fixtures/developer' +require 'fixtures/reply' + +class Topic; def after_find() end end +class Developer; def after_find() end end +class SpecialDeveloper < Developer; end + +class TopicManualObserver + include Singleton + + attr_reader :action, :object, :callbacks + + def initialize + Topic.add_observer(self) + @callbacks = [] + end + + def update(callback_method, object) + @callbacks << { "callback_method" => callback_method, "object" => object } + end + + def has_been_notified? + !@callbacks.empty? + end +end + +class TopicaObserver < ActiveRecord::Observer + observe :topic + + attr_reader :topic + + def after_find(topic) + @topic = topic + end +end + +class TopicObserver < ActiveRecord::Observer + attr_reader :topic + + def after_find(topic) + @topic = topic + end +end + +class MultiObserver < ActiveRecord::Observer + attr_reader :record + + def self.observed_class() [ Topic, Developer ] end + + cattr_reader :last_inherited + @@last_inherited = nil + + def observed_class_inherited_with_testing(subclass) + observed_class_inherited_without_testing(subclass) + @@last_inherited = subclass + end + + alias_method_chain :observed_class_inherited, :testing + + def after_find(record) + @record = record + end +end + +class LifecycleTest < ActiveSupport::TestCase + fixtures :topics, :developers + + def test_before_destroy + assert_equal 2, Topic.count + Topic.find(1).destroy + assert_equal 0, Topic.count + end + + def test_after_save + ActiveRecord::Base.observers = :topic_manual_observer + ActiveRecord::Base.instantiate_observers + + topic = Topic.find(1) + topic.title = "hello" + topic.save + + assert TopicManualObserver.instance.has_been_notified? + assert_equal :after_save, TopicManualObserver.instance.callbacks.last["callback_method"] + end + + def test_observer_update_on_save + ActiveRecord::Base.observers = TopicManualObserver + ActiveRecord::Base.instantiate_observers + + topic = Topic.find(1) + assert TopicManualObserver.instance.has_been_notified? + assert_equal :after_find, TopicManualObserver.instance.callbacks.first["callback_method"] + end + + def test_auto_observer + topic_observer = TopicaObserver.instance + + topic = Topic.find(1) + assert_equal topic.title, topic_observer.topic.title + end + + def test_inferred_auto_observer + topic_observer = TopicObserver.instance + + topic = Topic.find(1) + assert_equal topic.title, topic_observer.topic.title + end + + def test_observing_two_classes + multi_observer = MultiObserver.instance + + topic = Topic.find(1) + assert_equal topic.title, multi_observer.record.title + + developer = Developer.find(1) + assert_equal developer.name, multi_observer.record.name + end + + def test_observing_subclasses + multi_observer = MultiObserver.instance + + developer = SpecialDeveloper.find(1) + assert_equal developer.name, multi_observer.record.name + + klass = Class.new(Developer) + assert_equal klass, multi_observer.last_inherited + + developer = klass.find(1) + assert_equal developer.name, multi_observer.record.name + end + + def test_invalid_observer + assert_raise(ArgumentError) { Topic.observers = Object.new; Topic.instantiate_observers } + end +end diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb new file mode 100644 index 0000000000..da2b594c5d --- /dev/null +++ b/activerecord/test/cases/locking_test.rb @@ -0,0 +1,282 @@ +require 'abstract_unit' +require 'fixtures/person' +require 'fixtures/reader' +require 'fixtures/legacy_thing' + +class LockWithoutDefault < ActiveRecord::Base; end + +class LockWithCustomColumnWithoutDefault < ActiveRecord::Base + set_table_name :lock_without_defaults_cust + set_locking_column :custom_lock_version +end + +class ReadonlyFirstNamePerson < Person + attr_readonly :first_name +end + +class OptimisticLockingTest < ActiveSupport::TestCase + fixtures :people, :legacy_things + + # need to disable transactional fixtures, because otherwise the sqlite3 + # adapter (at least) chokes when we try and change the schema in the middle + # of a test (see test_increment_counter_*). + self.use_transactional_fixtures = false + + def test_lock_existing + p1 = Person.find(1) + p2 = Person.find(1) + assert_equal 0, p1.lock_version + assert_equal 0, p2.lock_version + + p1.save! + assert_equal 1, p1.lock_version + assert_equal 0, p2.lock_version + + assert_raises(ActiveRecord::StaleObjectError) { p2.save! } + end + + def test_lock_repeating + p1 = Person.find(1) + p2 = Person.find(1) + assert_equal 0, p1.lock_version + assert_equal 0, p2.lock_version + + p1.save! + assert_equal 1, p1.lock_version + assert_equal 0, p2.lock_version + + assert_raises(ActiveRecord::StaleObjectError) { p2.save! } + assert_raises(ActiveRecord::StaleObjectError) { p2.save! } + end + + def test_lock_new + p1 = Person.new(:first_name => 'anika') + assert_equal 0, p1.lock_version + + p1.save! + p2 = Person.find(p1.id) + assert_equal 0, p1.lock_version + assert_equal 0, p2.lock_version + + p1.save! + assert_equal 1, p1.lock_version + assert_equal 0, p2.lock_version + + assert_raises(ActiveRecord::StaleObjectError) { p2.save! } + end + + def test_lock_new_with_nil + p1 = Person.new(:first_name => 'anika') + p1.save! + p1.lock_version = nil # simulate bad fixture or column with no default + p1.save! + assert_equal 1, p1.lock_version + end + + + def test_lock_column_name_existing + t1 = LegacyThing.find(1) + t2 = LegacyThing.find(1) + assert_equal 0, t1.version + assert_equal 0, t2.version + + t1.save! + assert_equal 1, t1.version + assert_equal 0, t2.version + + assert_raises(ActiveRecord::StaleObjectError) { t2.save! } + end + + def test_lock_column_is_mass_assignable + p1 = Person.create(:first_name => 'bianca') + assert_equal 0, p1.lock_version + assert_equal p1.lock_version, Person.new(p1.attributes).lock_version + + p1.save! + assert_equal 1, p1.lock_version + assert_equal p1.lock_version, Person.new(p1.attributes).lock_version + end + + def test_lock_without_default_sets_version_to_zero + t1 = LockWithoutDefault.new + assert_equal 0, t1.lock_version + end + + def test_lock_with_custom_column_without_default_sets_version_to_zero + t1 = LockWithCustomColumnWithoutDefault.new + assert_equal 0, t1.custom_lock_version + end + + def test_readonly_attributes + assert_equal Set.new([ 'first_name' ]), ReadonlyFirstNamePerson.readonly_attributes + + p = ReadonlyFirstNamePerson.create(:first_name => "unchangeable name") + p.reload + assert_equal "unchangeable name", p.first_name + + p.update_attributes(:first_name => "changed name") + p.reload + assert_equal "unchangeable name", p.first_name + end + + { :lock_version => Person, :custom_lock_version => LegacyThing }.each do |name, model| + define_method("test_increment_counter_updates_#{name}") do + counter_test model, 1 do |id| + model.increment_counter :test_count, id + end + end + + define_method("test_decrement_counter_updates_#{name}") do + counter_test model, -1 do |id| + model.decrement_counter :test_count, id + end + end + + define_method("test_update_counters_updates_#{name}") do + counter_test model, 1 do |id| + model.update_counters id, :test_count => 1 + end + end + end + + private + + def add_counter_column_to(model) + model.connection.add_column model.table_name, :test_count, :integer, :null => false, :default => 0 + model.reset_column_information + # OpenBase does not set a value to existing rows when adding a not null default column + model.update_all(:test_count => 0) if current_adapter?(:OpenBaseAdapter) + end + + def remove_counter_column_from(model) + model.connection.remove_column model.table_name, :test_count + model.reset_column_information + end + + def counter_test(model, expected_count) + add_counter_column_to(model) + object = model.find(:first) + assert_equal 0, object.test_count + assert_equal 0, object.send(model.locking_column) + yield object.id + object.reload + assert_equal expected_count, object.test_count + assert_equal 1, object.send(model.locking_column) + ensure + remove_counter_column_from(model) + end +end + + +# TODO: test against the generated SQL since testing locking behavior itself +# is so cumbersome. Will deadlock Ruby threads if the underlying db.execute +# blocks, so separate script called by Kernel#system is needed. +# (See exec vs. async_exec in the PostgreSQL adapter.) + +# TODO: The SQL Server, Sybase, and OpenBase adapters currently have no support for pessimistic locking + +unless current_adapter?(:SQLServerAdapter, :SybaseAdapter, :OpenBaseAdapter) + class PessimisticLockingTest < ActiveSupport::TestCase + self.use_transactional_fixtures = false + fixtures :people, :readers + + def setup + # Avoid introspection queries during tests. + Person.columns; Reader.columns + + @allow_concurrency = ActiveRecord::Base.allow_concurrency + ActiveRecord::Base.allow_concurrency = true + end + + def teardown + ActiveRecord::Base.allow_concurrency = @allow_concurrency + end + + # Test typical find. + 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.with_scope(:find => { :lock => true }) do + Person.find 1 + end + end + end + end + + # PostgreSQL protests SELECT ... FOR UPDATE on an outer join. + unless current_adapter?(:PostgreSQLAdapter) + # Test locked eager find. + def test_eager_find_with_lock + assert_nothing_raised do + Person.transaction do + Person.find 1, :include => :readers, :lock => true + end + end + end + end + + # Locking a record reloads it. + def test_sane_lock_method + assert_nothing_raised do + Person.transaction do + person = Person.find 1 + old, person.first_name = person.first_name, 'fooman' + person.lock! + assert_equal old, person.first_name + end + end + end + + if current_adapter?(:PostgreSQLAdapter, :OracleAdapter) + def test_no_locks_no_wait + first, second = duel { Person.find 1 } + assert first.end > second.end + end + + 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 + + protected + def duel(zzz = 5) + t0, t1, t2, t3 = nil, nil, nil, nil + + a = Thread.new do + t0 = Time.now + Person.transaction do + yield + sleep zzz # block thread 2 for zzz seconds + end + t1 = Time.now + end + + b = Thread.new do + sleep zzz / 2.0 # ensure thread 1 tx starts first + t2 = Time.now + Person.transaction { yield } + t3 = Time.now + end + + a.join + b.join + + assert t1 > t0 + zzz + assert t2 > t0 + assert t3 > t2 + [t0.to_f..t1.to_f, t2.to_f..t3.to_f] + end + end + end +end diff --git a/activerecord/test/cases/method_scoping_test.rb b/activerecord/test/cases/method_scoping_test.rb new file mode 100644 index 0000000000..00304ba5cf --- /dev/null +++ b/activerecord/test/cases/method_scoping_test.rb @@ -0,0 +1,416 @@ +require 'abstract_unit' +require 'fixtures/developer' +require 'fixtures/project' +require 'fixtures/comment' +require 'fixtures/post' +require 'fixtures/category' + +class MethodScopingTest < ActiveSupport::TestCase + fixtures :developers, :projects, :comments, :posts + + def test_set_conditions + Developer.with_scope(:find => { :conditions => 'just a test...' }) do + assert_equal 'just a test...', Developer.send(:current_scoped_methods)[:find][:conditions] + end + end + + def test_scoped_find + Developer.with_scope(:find => { :conditions => "name = 'David'" }) do + assert_nothing_raised { Developer.find(1) } + end + end + + def test_scoped_find_first + Developer.with_scope(:find => { :conditions => "salary = 100000" }) do + assert_equal Developer.find(10), Developer.find(:first, :order => 'name') + end + end + + def test_scoped_find_combines_conditions + Developer.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.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.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.with_scope(:find => { :conditions => "name = 'David'" }) do + assert_equal [developers(:david)], Developer.find(:all) + end + end + + def test_scoped_count + Developer.with_scope(:find => { :conditions => "name = 'David'" }) do + assert_equal 1, Developer.count + end + + Developer.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.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_count_include + # with the include, will retrieve only developers for the given project + Developer.with_scope(:find => { :include => :projects }) do + assert_equal 1, Developer.count(:conditions => 'projects.id = 2') + end + end + + def test_scoped_create + new_comment = nil + + VerySpecialComment.with_scope(:create => { :post_id => 1 }) do + assert_equal({ :post_id => 1 }, VerySpecialComment.send(:current_scoped_methods)[:create]) + new_comment = VerySpecialComment.create :body => "Wonderful world" + end + + assert Post.find(1).comments.include?(new_comment) + end + + def test_immutable_scope + options = { :conditions => "name = 'David'" } + Developer.with_scope(:find => options) do + assert_equal %w(David), Developer.find(:all).map { |d| d.name } + options[:conditions] = "name != 'David'" + assert_equal %w(David), Developer.find(:all).map { |d| d.name } + end + + scope = { :find => { :conditions => "name = 'David'" }} + Developer.with_scope(scope) do + assert_equal %w(David), Developer.find(:all).map { |d| d.name } + scope[:find][:conditions] = "name != 'David'" + assert_equal %w(David), Developer.find(:all).map { |d| d.name } + end + end + + def test_scoped_with_duck_typing + scoping = Struct.new(:method_scoping).new(:find => { :conditions => ["name = ?", 'David'] }) + Developer.with_scope(scoping) do + assert_equal %w(David), Developer.find(:all).map { |d| d.name } + end + end + + def test_ensure_that_method_scoping_is_correctly_restored + scoped_methods = Developer.instance_eval('current_scoped_methods') + + begin + Developer.with_scope(:find => { :conditions => "name = 'Jamis'" }) do + raise "an exception" + end + rescue + end + assert_equal scoped_methods, Developer.instance_eval('current_scoped_methods') + end +end + +class NestedScopingTest < ActiveSupport::TestCase + fixtures :developers, :projects, :comments, :posts + + def test_merge_options + Developer.with_scope(:find => { :conditions => 'salary = 80000' }) do + Developer.with_scope(:find => { :limit => 10 }) do + merged_option = Developer.instance_eval('current_scoped_methods')[:find] + assert_equal({ :conditions => 'salary = 80000', :limit => 10 }, merged_option) + end + end + end + + def test_replace_options + Developer.with_scope(:find => { :conditions => "name = 'David'" }) do + Developer.with_exclusive_scope(:find => { :conditions => "name = 'Jamis'" }) do + assert_equal({:find => { :conditions => "name = 'Jamis'" }}, Developer.instance_eval('current_scoped_methods')) + assert_equal({:find => { :conditions => "name = 'Jamis'" }}, Developer.send(:scoped_methods)[-1]) + end + end + end + + def test_append_conditions + Developer.with_scope(:find => { :conditions => "name = 'David'" }) do + Developer.with_scope(:find => { :conditions => 'salary = 80000' }) do + appended_condition = Developer.instance_eval('current_scoped_methods')[:find][:conditions] + assert_equal("( name = 'David' ) AND ( salary = 80000 )", appended_condition) + assert_equal(1, Developer.count) + end + Developer.with_scope(:find => { :conditions => "name = 'Maiha'" }) do + assert_equal(0, Developer.count) + end + end + end + + def test_merge_and_append_options + Developer.with_scope(:find => { :conditions => 'salary = 80000', :limit => 10 }) do + Developer.with_scope(:find => { :conditions => "name = 'David'" }) do + merged_option = Developer.instance_eval('current_scoped_methods')[:find] + assert_equal({ :conditions => "( salary = 80000 ) AND ( name = 'David' )", :limit => 10 }, merged_option) + end + end + end + + def test_nested_scoped_find + Developer.with_scope(:find => { :conditions => "name = 'Jamis'" }) do + Developer.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.with_scope(:find => { :include => :projects }) do + Developer.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.with_scope(:find => { :include => :projects, :conditions => "projects.id = 2" }) do + Developer.with_scope(:find => { :include => :projects }) do + assert_equal 1, Developer.instance_eval('current_scoped_methods')[:find][:include].length + assert_equal('David', Developer.find(:first).name) + end + end + + # the nested scope doesn't remove the first :include + Developer.with_scope(:find => { :include => :projects, :conditions => "projects.id = 2" }) do + Developer.with_scope(:find => { :include => [] }) do + assert_equal 1, Developer.instance_eval('current_scoped_methods')[:find][:include].length + assert_equal('David', Developer.find(:first).name) + end + end + + # mixing array and symbol include's will merge correctly + Developer.with_scope(:find => { :include => [:projects], :conditions => "projects.id = 2" }) do + Developer.with_scope(:find => { :include => :projects }) do + assert_equal 1, Developer.instance_eval('current_scoped_methods')[:find][:include].length + assert_equal('David', Developer.find(:first).name) + end + end + end + + def test_nested_scoped_find_replace_include + Developer.with_scope(:find => { :include => :projects }) do + Developer.with_exclusive_scope(:find => { :include => [] }) do + assert_equal 0, Developer.instance_eval('current_scoped_methods')[:find][:include].length + end + end + end + + def test_three_level_nested_exclusive_scoped_find + Developer.with_scope(:find => { :conditions => "name = 'Jamis'" }) do + assert_equal('Jamis', Developer.find(:first).name) + + Developer.with_exclusive_scope(:find => { :conditions => "name = 'David'" }) do + assert_equal('David', Developer.find(:first).name) + + Developer.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.with_scope(:find => { :conditions => "salary < 100000" }) do + Developer.with_scope(:find => { :offset => 1 }) do + assert_equal(poor_jamis, Developer.find(:first, :order => 'id asc')) + end + end + end + + def test_merged_scoped_find_sanitizes_conditions + Developer.with_scope(:find => { :conditions => ["name = ?", 'David'] }) do + Developer.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.with_scope(:find => { :conditions => ["name = ?", 'David'] }) do + Developer.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.with_scope(:find => { :conditions => ["name = ?", 'David'] }) do + Developer.with_scope(:find => { :conditions => ['salary > ?', 9000] }) do + assert_equal %w(David), Developer.find(:all).map { |d| d.name } + end + end + end + + def test_immutable_nested_scope + options1 = { :conditions => "name = 'Jamis'" } + options2 = { :conditions => "name = 'David'" } + Developer.with_scope(:find => options1) do + Developer.with_exclusive_scope(:find => options2) do + assert_equal %w(David), Developer.find(:all).map { |d| d.name } + options1[:conditions] = options2[:conditions] = nil + assert_equal %w(David), Developer.find(:all).map { |d| d.name } + end + end + end + + def test_immutable_merged_scope + options1 = { :conditions => "name = 'Jamis'" } + options2 = { :conditions => "salary > 10000" } + Developer.with_scope(:find => options1) do + Developer.with_scope(:find => options2) do + assert_equal %w(Jamis), Developer.find(:all).map { |d| d.name } + options1[:conditions] = options2[:conditions] = nil + assert_equal %w(Jamis), Developer.find(:all).map { |d| d.name } + end + end + end + + def test_ensure_that_method_scoping_is_correctly_restored + Developer.with_scope(:find => { :conditions => "name = 'David'" }) do + scoped_methods = Developer.instance_eval('current_scoped_methods') + begin + Developer.with_scope(:find => { :conditions => "name = 'Maiha'" }) do + raise "an exception" + end + rescue + end + assert_equal scoped_methods, Developer.instance_eval('current_scoped_methods') + end + end +end + +class HasManyScopingTest< ActiveSupport::TestCase + fixtures :comments, :posts + + def setup + @welcome = Post.find(1) + end + + def test_forwarding_of_static_methods + assert_equal 'a comment...', Comment.what_are_you + assert_equal 'a comment...', @welcome.comments.what_are_you + end + + def test_forwarding_to_scoped + assert_equal 4, Comment.search_by_type('Comment').size + 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 + Comment.with_scope(:find => { :conditions => '1=1' }) do + assert_equal 'a comment...', @welcome.comments.what_are_you + end + end +end + + +class HasAndBelongsToManyScopingTest< ActiveSupport::TestCase + fixtures :posts, :categories, :categories_posts + + def setup + @welcome = Post.find(1) + end + + def test_forwarding_of_static_methods + assert_equal 'a category...', Category.what_are_you + 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 + Category.with_scope(:find => { :conditions => '1=1' }) do + assert_equal 'a comment...', @welcome.comments.what_are_you + end + end +end + + +=begin +# We disabled the scoping for has_one and belongs_to as we can't think of a proper use case + + +class BelongsToScopingTest< ActiveSupport::TestCase + fixtures :comments, :posts + + def setup + @greetings = Comment.find(1) + end + + def test_forwarding_of_static_method + assert_equal 'a post...', Post.what_are_you + assert_equal 'a post...', @greetings.post.what_are_you + end + + def test_forwarding_to_dynamic_finders + assert_equal 4, Post.find_all_by_type('Post').size + assert_equal 1, @greetings.post.find_all_by_type('Post').size + end + +end + + +class HasOneScopingTest< ActiveSupport::TestCase + fixtures :comments, :posts + + def setup + @sti_comments = Post.find(4) + end + + def test_forwarding_of_static_methods + assert_equal 'a comment...', Comment.what_are_you + assert_equal 'a very special comment...', @sti_comments.very_special_comment.what_are_you + end + + def test_forwarding_to_dynamic_finders + assert_equal 1, Comment.find_all_by_type('VerySpecialComment').size + assert_equal 1, @sti_comments.very_special_comment.find_all_by_type('VerySpecialComment').size + assert_equal 0, @sti_comments.very_special_comment.find_all_by_type('Comment').size + end + +end + +=end diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb new file mode 100644 index 0000000000..c349f1cb1b --- /dev/null +++ b/activerecord/test/cases/migration_test.rb @@ -0,0 +1,987 @@ +require 'abstract_unit' +require 'bigdecimal/util' + +require 'fixtures/person' +require 'fixtures/topic' +require File.dirname(__FILE__) + '/fixtures/migrations/1_people_have_last_names' +require File.dirname(__FILE__) + '/fixtures/migrations/2_we_need_reminders' +require File.dirname(__FILE__) + '/fixtures/migrations_with_decimal/1_give_me_big_numbers' + +if ActiveRecord::Base.connection.supports_migrations? + class BigNumber < ActiveRecord::Base; end + + class Reminder < ActiveRecord::Base; end + + class ActiveRecord::Migration + class < 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?(:OracleAdapter, :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"]) } + 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") } + assert_nothing_raised { Person.connection.add_index("people", ["last_name", "first_name"]) } + 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) + 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_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_raises(ActiveRecord::StatementInvalid) do + Person.connection.execute "insert into testings (foo) values (NULL)" + end + ensure + Person.connection.drop_table :testings rescue nil + end + + def test_create_table_with_defaults + # MySQL doesn't allow defaults on TEXT or BLOB columns. + mysql = current_adapter?(:MysqlAdapter) + + Person.connection.create_table :testings do |t| + t.column :one, :string, :default => "hello" + t.column :two, :boolean, :default => true + t.column :three, :boolean, :default => false + t.column :four, :integer, :default => 1 + t.column :five, :text, :default => "hello" unless mysql + end + + columns = Person.connection.columns(:testings) + one = columns.detect { |c| c.name == "one" } + two = columns.detect { |c| c.name == "two" } + three = columns.detect { |c| c.name == "three" } + four = columns.detect { |c| c.name == "four" } + five = columns.detect { |c| c.name == "five" } unless mysql + + assert_equal "hello", one.default + assert_equal true, two.default + assert_equal false, three.default + assert_equal 1, four.default + assert_equal "hello", five.default unless mysql + + ensure + Person.connection.drop_table :testings rescue nil + 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 + end + end + + columns = Person.connection.columns(:testings) + foo = columns.detect { |c| c.name == "foo" } + assert_equal 255, foo.limit + + default = columns.detect { |c| c.name == "default_int" } + one = columns.detect { |c| c.name == "one_int" } + four = columns.detect { |c| c.name == "four_int" } + eight = columns.detect { |c| c.name == "eight_int" } + + 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?(: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 + end + + # SQL Server, Sybase, and SQLite3 will not allow you to add a NOT NULL + # column to a table without a default value. + unless current_adapter?(:SQLServerAdapter, :SybaseAdapter, :SQLiteAdapter) + def test_add_column_not_null_without_default + Person.connection.create_table :testings do |t| + t.column :foo, :string + end + Person.connection.add_column :testings, :bar, :string, :null => false + + assert_raises(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_add_column_not_null_with_default + Person.connection.create_table :testings do |t| + t.column :foo, :string + end + + con = Person.connection + Person.connection.enable_identity_insert("testings", true) if current_adapter?(:SybaseAdapter) + Person.connection.execute "insert into testings (#{con.quote_column_name('id')}, #{con.quote_column_name('foo')}) values (1, 'hello')" + Person.connection.enable_identity_insert("testings", false) if current_adapter?(:SybaseAdapter) + assert_nothing_raised {Person.connection.add_column :testings, :bar, :string, :null => false, :default => "default" } + + assert_raises(ActiveRecord::StatementInvalid) do + unless current_adapter?(:OpenBaseAdapter) + Person.connection.execute "insert into testings (#{con.quote_column_name('id')}, #{con.quote_column_name('foo')}, #{con.quote_column_name('bar')}) values (2, 'hello', NULL)" + else + Person.connection.insert("INSERT INTO testings (#{con.quote_column_name('id')}, #{con.quote_column_name('foo')}, #{con.quote_column_name('bar')}) VALUES (2, 'hello', NULL)", + "Testing Insert","id",2) + end + end + ensure + Person.connection.drop_table :testings rescue nil + end + + # We specifically do a manual INSERT here, and then test only the SELECT + # functionality. This allows us to more easily catch INSERT being broken, + # but SELECT actually working fine. + def test_native_decimal_insert_manual_vs_automatic + correct_value = '0012345678901234567890.0123456789'.to_d + + Person.delete_all + Person.connection.add_column "people", "wealth", :decimal, :precision => '30', :scale => '10' + Person.reset_column_information + + # Do a manual insertion + if current_adapter?(:OracleAdapter) + Person.connection.execute "insert into people (id, wealth) values (people_seq.nextval, 12345678901234567890.0123456789)" + elsif current_adapter?(:OpenBaseAdapter) + 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") } + + # SELECT + row = Person.find(: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 + + # Reset to old state + Person.connection.del_column "people", "wealth" rescue nil + Person.reset_column_information + end + + 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 + + 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 + + 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 + + # Test for 30 significent 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?(:SQLServerAdapter, :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 + + # 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) + assert_equal DateTime.now.offset, bob.moment_of_truth.offset + assert_not_equal 0, bob.moment_of_truth.offset + assert_not_equal "Z", bob.moment_of_truth.zone + assert_equal DateTime::ITALY, bob.moment_of_truth.start + end + + assert_equal TrueClass, bob.male?.class + assert_kind_of BigDecimal, bob.wealth + end + + if current_adapter?(:MysqlAdapter) + def test_unabstracted_database_dependent_types + Person.delete_all + + ActiveRecord::Migration.add_column :people, :intelligence_quotient, :tinyint + Person.reset_column_information + Person.create :intelligence_quotient => 300 + jonnyg = Person.find(:first) + assert_equal 127, jonnyg.intelligence_quotient + jonnyg.destroy + 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 + + 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_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 :people, :first_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_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 + end + end + + def test_rename_table + begin + ActiveRecord::Base.connection.create_table :octopuses do |t| + t.column :url, :string + 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 + 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 + begin + ActiveRecord::Base.connection.create_table :octopuses do |t| + t.column :url, :string + 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) + + assert_equal 'http://www.foreverflying.com/octopus-black7.jpg', ActiveRecord::Base.connection.select_value("SELECT url FROM octopi WHERE id=1") + assert ActiveRecord::Base.connection.indexes(:octopi).first.columns.include?("url") + ensure + ActiveRecord::Base.connection.drop_table :octopuses rescue nil + ActiveRecord::Base.connection.drop_table :octopi rescue nil + end + end + + def test_change_column + Person.connection.add_column 'people', 'age', :integer + old_columns = Person.connection.columns(Person.table_name, "#{name} Columns") + 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, "#{name} Columns") + 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, "#{name} Columns") + 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, "#{name} Columns") + 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 } + + assert_nothing_raised { Person.connection.execute "insert into testings (#{Person.connection.quote_column_name('select')}) values ('7 chars')" } + ensure + Person.connection.drop_table :testings rescue nil + end + + def test_change_column_default_to_null + Person.connection.change_column_default "people", "first_name", nil + Person.reset_column_information + assert_nil Person.new.first_name + end + + def test_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_raises(ActiveRecord::StatementInvalid) { Reminder.find(:first) } + end + + def test_add_table_with_decimals + Person.connection.drop_table :big_numbers rescue nil + + assert !BigNumber.table_exists? + GiveMeBigNumbers.up + + assert BigNumber.create( + :bank_balance => 1586.43, + :big_bank_balance => BigDecimal("1000234000567.95"), + :world_population => 6000000000, + :my_house_population => 3, + :value_of_e => BigDecimal("2.7182818284590452353602875") + ) + + b = BigNumber.find(:first) + assert_not_nil b + + assert_not_nil b.bank_balance + assert_not_nil b.big_bank_balance + assert_not_nil b.world_population + assert_not_nil b.my_house_population + assert_not_nil b.value_of_e + + # TODO: set world_population >= 2**62 to cover 64-bit platforms and test + # is_a?(Bignum) + assert_kind_of Integer, b.world_population + assert_equal 6000000000, b.world_population + assert_kind_of Fixnum, b.my_house_population + assert_equal 3, b.my_house_population + assert_kind_of BigDecimal, b.bank_balance + assert_equal BigDecimal("1586.43"), b.bank_balance + assert_kind_of BigDecimal, b.big_bank_balance + assert_equal BigDecimal("1000234000567.95"), b.big_bank_balance + + # This one is fun. The 'value_of_e' field is defined as 'DECIMAL' with + # precision/scale explicitly left out. By the SQL standard, numbers + # assigned to this field should be truncated but that's seldom respected. + if current_adapter?(:PostgreSQLAdapter, :SQLite2Adapter) + # - 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?(:SQLiteAdapter) + # - SQLite3 stores a float, in violation of SQL + assert_kind_of BigDecimal, b.value_of_e + assert_equal BigDecimal("2.71828182845905"), b.value_of_e + elsif current_adapter?(:SQLServer) + # - SQL Server rounds instead of truncating + assert_kind_of Fixnum, b.value_of_e + assert_equal 3, b.value_of_e + else + # - SQL standard is an integer + assert_kind_of Fixnum, b.value_of_e + assert_equal 2, b.value_of_e + end + + GiveMeBigNumbers.down + assert_raises(ActiveRecord::StatementInvalid) { BigNumber.find(:first) } + end + + def test_migrator + assert !Person.column_methods_hash.include?(:last_name) + assert !Reminder.table_exists? + + ActiveRecord::Migrator.up(File.dirname(__FILE__) + '/fixtures/migrations/') + + 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(File.dirname(__FILE__) + '/fixtures/migrations/') + + assert_equal 0, ActiveRecord::Migrator.current_version + Person.reset_column_information + assert !Person.column_methods_hash.include?(:last_name) + assert_raises(ActiveRecord::StatementInvalid) { Reminder.find(:first) } + end + + def test_migrator_one_up + assert !Person.column_methods_hash.include?(:last_name) + assert !Reminder.table_exists? + + ActiveRecord::Migrator.up(File.dirname(__FILE__) + '/fixtures/migrations/', 1) + + Person.reset_column_information + assert Person.column_methods_hash.include?(:last_name) + assert !Reminder.table_exists? + + ActiveRecord::Migrator.up(File.dirname(__FILE__) + '/fixtures/migrations/', 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(File.dirname(__FILE__) + '/fixtures/migrations/') + + ActiveRecord::Migrator.down(File.dirname(__FILE__) + '/fixtures/migrations/', 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(File.dirname(__FILE__) + '/fixtures/migrations/', 1) + ActiveRecord::Migrator.down(File.dirname(__FILE__) + '/fixtures/migrations/', 0) + + assert !Person.column_methods_hash.include?(:last_name) + assert !Reminder.table_exists? + end + + def test_migrator_verbosity + ActiveRecord::Migrator.up(File.dirname(__FILE__) + '/fixtures/migrations/', 1) + assert PeopleHaveLastNames.message_count > 0 + PeopleHaveLastNames.message_count = 0 + + ActiveRecord::Migrator.down(File.dirname(__FILE__) + '/fixtures/migrations/', 0) + assert PeopleHaveLastNames.message_count > 0 + PeopleHaveLastNames.message_count = 0 + end + + def test_migrator_verbosity_off + PeopleHaveLastNames.verbose = false + ActiveRecord::Migrator.up(File.dirname(__FILE__) + '/fixtures/migrations/', 1) + assert PeopleHaveLastNames.message_count.zero? + ActiveRecord::Migrator.down(File.dirname(__FILE__) + '/fixtures/migrations/', 0) + assert PeopleHaveLastNames.message_count.zero? + end + + def test_migrator_going_down_due_to_version_target + ActiveRecord::Migrator.up(File.dirname(__FILE__) + '/fixtures/migrations/', 1) + ActiveRecord::Migrator.migrate(File.dirname(__FILE__) + '/fixtures/migrations/', 0) + + assert !Person.column_methods_hash.include?(:last_name) + assert !Reminder.table_exists? + + ActiveRecord::Migrator.migrate(File.dirname(__FILE__) + '/fixtures/migrations/') + + 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_schema_info_table_name + ActiveRecord::Base.table_name_prefix = "prefix_" + ActiveRecord::Base.table_name_suffix = "_suffix" + Reminder.reset_table_name + assert_equal "prefix_schema_info_suffix", ActiveRecord::Migrator.schema_info_table_name + ActiveRecord::Base.table_name_prefix = "" + ActiveRecord::Base.table_name_suffix = "" + Reminder.reset_table_name + assert_equal "schema_info", ActiveRecord::Migrator.schema_info_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_raises(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 + end + } + + columns = Person.connection.columns(:binary_testings) + data_column = columns.detect { |c| c.name == "data" } + + if current_adapter?(:MysqlAdapter) + assert_equal '', data_column.default + else + assert_nil data_column.default + end + + Person.connection.drop_table :binary_testings rescue nil + end + + def test_migrator_with_duplicates + assert_raises(ActiveRecord::DuplicateMigrationVersionError) do + ActiveRecord::Migrator.migrate(File.dirname(__FILE__) + '/fixtures/migrations_with_duplicate/', nil) + end + end + + def test_migrator_with_missing_version_numbers + ActiveRecord::Migrator.migrate(File.dirname(__FILE__) + '/fixtures/migrations_with_missing_versions/', 500) + assert !Person.column_methods_hash.include?(:middle_name) + assert_equal 4, ActiveRecord::Migrator.current_version + + ActiveRecord::Migrator.migrate(File.dirname(__FILE__) + '/fixtures/migrations_with_missing_versions/', 2) + Person.reset_column_information + assert !Reminder.table_exists? + assert Person.column_methods_hash.include?(:last_name) + assert_equal 2, ActiveRecord::Migrator.current_version + 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 fail + assert_raises(ActiveRecord::StatementInvalid) do + begin + Person.connection.create_table :table_with_name_thats_just_ok do |t| + t.column :foo, :string, :null => false + end + ensure + Person.connection.drop_table :table_with_name_thats_just_ok rescue nil + end + end + + # should be all good w/ a custom sequence name + assert_nothing_raised do + begin + Person.connection.create_table :table_with_name_thats_just_ok, + :sequence_name => 'suitably_short_seq' do |t| + t.column :foo, :string, :null => false + end + + Person.connection.execute("select suitably_short_seq.nextval from dual") + + ensure + Person.connection.drop_table :table_with_name_thats_just_ok, + :sequence_name => 'suitably_short_seq' rescue nil + end + end + + # confirm the custom sequence got dropped + assert_raises(ActiveRecord::StatementInvalid) do + Person.connection.execute("select suitably_short_seq.nextval from dual") + end + end + end + + uses_mocha 'Sexy migration tests' do + class SexyMigrationsTest < ActiveSupport::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 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 + 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 + end + + def test_timestamps_creates_updated_at_and_created_at + with_new_table do |t| + t.expects(:column).with(:created_at, :datetime) + t.expects(:column).with(:updated_at, :datetime) + t.timestamps + end + 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 + 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 + end + + protected + def with_new_table + Person.connection.create_table :delete_me do |t| + yield t + end + ensure + Person.connection.drop_table :delete_me rescue nil + end + + end # SexyMigrationsTest + end # uses_mocha +end diff --git a/activerecord/test/cases/migration_test_firebird.rb b/activerecord/test/cases/migration_test_firebird.rb new file mode 100644 index 0000000000..5c7dbd7a35 --- /dev/null +++ b/activerecord/test/cases/migration_test_firebird.rb @@ -0,0 +1,124 @@ +require 'abstract_unit' +require 'fixtures/course' + +class FirebirdMigrationTest < ActiveSupport::TestCase + self.use_transactional_fixtures = false + + def setup + # using Course connection for tests -- need a db that doesn't already have a BOOLEAN domain + @connection = Course.connection + @fireruby_connection = @connection.instance_variable_get(:@connection) + end + + def teardown + @connection.drop_table :foo rescue nil + @connection.execute("DROP DOMAIN D_BOOLEAN") rescue nil + end + + def test_create_table_with_custom_sequence_name + assert_nothing_raised do + @connection.create_table(:foo, :sequence => 'foo_custom_seq') do |f| + f.column :bar, :string + end + end + assert !sequence_exists?('foo_seq') + assert sequence_exists?('foo_custom_seq') + + assert_nothing_raised { @connection.drop_table(:foo, :sequence => 'foo_custom_seq') } + assert !sequence_exists?('foo_custom_seq') + ensure + FireRuby::Generator.new('foo_custom_seq', @fireruby_connection).drop rescue nil + end + + def test_create_table_without_sequence + assert_nothing_raised do + @connection.create_table(:foo, :sequence => false) do |f| + f.column :bar, :string + end + end + assert !sequence_exists?('foo_seq') + assert_nothing_raised { @connection.drop_table :foo } + + assert_nothing_raised do + @connection.create_table(:foo, :id => false) do |f| + f.column :bar, :string + end + end + assert !sequence_exists?('foo_seq') + assert_nothing_raised { @connection.drop_table :foo } + end + + def test_create_table_with_boolean_column + assert !boolean_domain_exists? + assert_nothing_raised do + @connection.create_table :foo do |f| + f.column :bar, :string + f.column :baz, :boolean + end + end + assert boolean_domain_exists? + end + + def test_add_boolean_column + assert !boolean_domain_exists? + @connection.create_table :foo do |f| + f.column :bar, :string + end + + assert_nothing_raised { @connection.add_column :foo, :baz, :boolean } + assert boolean_domain_exists? + assert_equal :boolean, @connection.columns(:foo).find { |c| c.name == "baz" }.type + end + + def test_change_column_to_boolean + assert !boolean_domain_exists? + # Manually create table with a SMALLINT column, which can be changed to a BOOLEAN + @connection.execute "CREATE TABLE foo (bar SMALLINT)" + assert_equal :integer, @connection.columns(:foo).find { |c| c.name == "bar" }.type + + assert_nothing_raised { @connection.change_column :foo, :bar, :boolean } + assert boolean_domain_exists? + assert_equal :boolean, @connection.columns(:foo).find { |c| c.name == "bar" }.type + end + + def test_rename_table_with_data_and_index + @connection.create_table :foo do |f| + f.column :baz, :string, :limit => 50 + end + 100.times { |i| @connection.execute "INSERT INTO foo VALUES (GEN_ID(foo_seq, 1), 'record #{i+1}')" } + @connection.add_index :foo, :baz + + assert_nothing_raised { @connection.rename_table :foo, :bar } + assert !@connection.tables.include?("foo") + assert @connection.tables.include?("bar") + assert_equal "index_bar_on_baz", @connection.indexes("bar").first.name + assert_equal 100, FireRuby::Generator.new("bar_seq", @fireruby_connection).last + assert_equal 100, @connection.select_one("SELECT COUNT(*) FROM bar")["count"] + ensure + @connection.drop_table :bar rescue nil + end + + def test_renaming_table_with_fk_constraint_raises_error + @connection.create_table :parent do |p| + p.column :name, :string + end + @connection.create_table :child do |c| + c.column :parent_id, :integer + end + @connection.execute "ALTER TABLE child ADD CONSTRAINT fk_child_parent FOREIGN KEY(parent_id) REFERENCES parent(id)" + assert_raise(ActiveRecord::ActiveRecordError) { @connection.rename_table :child, :descendant } + ensure + @connection.drop_table :child rescue nil + @connection.drop_table :descendant rescue nil + @connection.drop_table :parent rescue nil + end + + private + def boolean_domain_exists? + !@connection.select_one("SELECT 1 FROM rdb$fields WHERE rdb$field_name = 'D_BOOLEAN'").nil? + end + + def sequence_exists?(sequence_name) + FireRuby::Generator.exists?(sequence_name, @fireruby_connection) + end +end diff --git a/activerecord/test/cases/mixin_test.rb b/activerecord/test/cases/mixin_test.rb new file mode 100644 index 0000000000..0823ffddd6 --- /dev/null +++ b/activerecord/test/cases/mixin_test.rb @@ -0,0 +1,95 @@ +require 'abstract_unit' + +class Mixin < ActiveRecord::Base +end + +# Let us control what Time.now returns for the TouchTest suite +class Time + @@forced_now_time = nil + cattr_accessor :forced_now_time + + class << self + def now_with_forcing + if @@forced_now_time + @@forced_now_time + else + now_without_forcing + end + end + alias_method_chain :now, :forcing + end +end + + +class TouchTest < ActiveSupport::TestCase + fixtures :mixins + + def setup + Time.forced_now_time = Time.now + end + + def teardown + Time.forced_now_time = nil + end + + def test_time_mocking + five_minutes_ago = 5.minutes.ago + Time.forced_now_time = five_minutes_ago + assert_equal five_minutes_ago, Time.now + + Time.forced_now_time = nil + assert_not_equal five_minutes_ago, Time.now + end + + def test_update + stamped = Mixin.new + + assert_nil stamped.updated_at + assert_nil stamped.created_at + stamped.save + assert_equal Time.now, stamped.updated_at + assert_equal Time.now, stamped.created_at + end + + def test_create + obj = Mixin.create + assert_equal Time.now, obj.updated_at + assert_equal Time.now, obj.created_at + end + + def test_many_updates + stamped = Mixin.new + + assert_nil stamped.updated_at + assert_nil stamped.created_at + stamped.save + assert_equal Time.now, stamped.created_at + assert_equal Time.now, stamped.updated_at + + old_updated_at = stamped.updated_at + + Time.forced_now_time = 5.minutes.from_now + stamped.save + + assert_equal Time.now, stamped.updated_at + assert_equal old_updated_at, stamped.created_at + end + + def test_create_turned_off + Mixin.record_timestamps = false + + mixin = Mixin.new + + assert_nil mixin.updated_at + mixin.save + assert_nil mixin.updated_at + + # Make sure Mixin.record_timestamps gets reset, even if this test fails, + # so that other tests do not fail because Mixin.record_timestamps == false + rescue Exception => e + raise e + ensure + Mixin.record_timestamps = true + end + +end diff --git a/activerecord/test/cases/modules_test.rb b/activerecord/test/cases/modules_test.rb new file mode 100644 index 0000000000..cced0bb895 --- /dev/null +++ b/activerecord/test/cases/modules_test.rb @@ -0,0 +1,34 @@ +require 'abstract_unit' +require 'fixtures/company_in_module' + +class ModulesTest < ActiveSupport::TestCase + fixtures :accounts, :companies, :projects, :developers + + def test_module_spanning_associations + firm = MyApplication::Business::Firm.find(: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.developers << MyApplication::Business::Developer.create("name" => "John") + assert "John", project.developers.last.name + end + + def test_associations_spanning_cross_modules + account = MyApplication::Billing::Account.find(:first, :order => 'id') + 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 + assert_kind_of MyApplication::Billing::Nested::Firm, account.nested_qualified_billing_firm + assert_kind_of MyApplication::Billing::Nested::Firm, account.nested_unqualified_billing_firm + end + + def test_find_account_and_include_company + account = MyApplication::Billing::Account.find(1, :include => :firm) + assert_kind_of MyApplication::Business::Firm, account.instance_variable_get('@firm') + assert_kind_of MyApplication::Business::Firm, account.firm + end + +end diff --git a/activerecord/test/cases/multiple_db_test.rb b/activerecord/test/cases/multiple_db_test.rb new file mode 100644 index 0000000000..5d64a3efaa --- /dev/null +++ b/activerecord/test/cases/multiple_db_test.rb @@ -0,0 +1,60 @@ +require 'abstract_unit' +require 'fixtures/entrant' + +# So we can test whether Course.connection survives a reload. +require_dependency 'fixtures/course' + +class MultipleDbTest < ActiveSupport::TestCase + self.use_transactional_fixtures = false + + def setup + @courses = create_fixtures("courses") { Course.retrieve_connection } + @entrants = create_fixtures("entrants") + end + + def test_connected + assert_not_nil Entrant.connection + assert_not_nil Course.connection + end + + def test_proper_connection + assert_not_equal(Entrant.connection, Course.connection) + assert_equal(Entrant.connection, Entrant.retrieve_connection) + assert_equal(Course.connection, Course.retrieve_connection) + assert_equal(ActiveRecord::Base.connection, Entrant.connection) + end + + def test_find + c1 = Course.find(1) + assert_equal "Ruby Development", c1.name + c2 = Course.find(2) + assert_equal "Java Development", c2.name + e1 = Entrant.find(1) + assert_equal "Ruby Developer", e1.name + e2 = Entrant.find(2) + assert_equal "Ruby Guru", e2.name + e3 = Entrant.find(3) + assert_equal "Java Lover", e3.name + end + + def test_associations + c1 = Course.find(1) + assert_equal 2, c1.entrants.count + e1 = Entrant.find(1) + assert_equal e1.course.id, c1.id + c2 = Course.find(2) + assert_equal 1, c2.entrants.count + e3 = Entrant.find(3) + assert_equal e3.course.id, c2.id + end + + def test_course_connection_should_survive_dependency_reload + assert Course.connection + + Dependencies.clear + Object.send(:remove_const, :Course) + require_dependency 'fixtures/course' + + assert Course.connection + end +end diff --git a/activerecord/test/cases/pk_test.rb b/activerecord/test/cases/pk_test.rb new file mode 100644 index 0000000000..a9e9150f23 --- /dev/null +++ b/activerecord/test/cases/pk_test.rb @@ -0,0 +1,101 @@ +require "#{File.dirname(__FILE__)}/abstract_unit" +require 'fixtures/topic' +require 'fixtures/reply' +require 'fixtures/subscriber' +require 'fixtures/movie' +require 'fixtures/keyboard' +require 'fixtures/mixed_case_monkey' + +class PrimaryKeysTest < ActiveSupport::TestCase + fixtures :topics, :subscribers, :movies, :mixed_case_monkeys + + def test_integer_key + topic = Topic.find(1) + assert_equal(topics(:first).author_name, topic.author_name) + topic = Topic.find(2) + assert_equal(topics(:second).author_name, topic.author_name) + + topic = Topic.new + topic.title = "New Topic" + assert_equal(nil, topic.id) + assert_nothing_raised { topic.save! } + id = topic.id + + topicReloaded = Topic.find(id) + assert_equal("New Topic", topicReloaded.title) + end + + def test_customized_primary_key_auto_assigns_on_save + Keyboard.delete_all + keyboard = Keyboard.new(:name => 'HHKB') + assert_nothing_raised { keyboard.save! } + assert_equal keyboard.id, Keyboard.find_by_name('HHKB').id + end + + def test_customized_primary_key_can_be_get_before_saving + keyboard = Keyboard.new + assert_nil keyboard.id + assert_nothing_raised { assert_nil keyboard.key_number } + end + + def test_customized_string_primary_key_settable_before_save + subscriber = Subscriber.new + assert_nothing_raised { subscriber.id = 'webster123' } + assert_equal 'webster123', subscriber.id + assert_equal 'webster123', subscriber.nick + end + + def test_string_key + subscriber = Subscriber.find(subscribers(:first).nick) + assert_equal(subscribers(:first).name, subscriber.name) + subscriber = Subscriber.find(subscribers(:second).nick) + assert_equal(subscribers(:second).name, subscriber.name) + + subscriber = Subscriber.new + subscriber.id = "jdoe" + assert_equal("jdoe", subscriber.id) + subscriber.name = "John Doe" + assert_nothing_raised { subscriber.save! } + assert_equal("jdoe", subscriber.id) + + subscriberReloaded = Subscriber.find("jdoe") + assert_equal("John Doe", subscriberReloaded.name) + end + + def test_find_with_more_than_one_string_key + assert_equal 2, Subscriber.find(subscribers(:first).nick, subscribers(:second).nick).length + end + + def test_primary_key_prefix + ActiveRecord::Base.primary_key_prefix_type = :table_name + Topic.reset_primary_key + assert_equal "topicid", Topic.primary_key + + ActiveRecord::Base.primary_key_prefix_type = :table_name_with_underscore + Topic.reset_primary_key + assert_equal "topic_id", Topic.primary_key + + ActiveRecord::Base.primary_key_prefix_type = nil + Topic.reset_primary_key + assert_equal "id", Topic.primary_key + end + + def test_delete_should_quote_pkey + assert_nothing_raised { MixedCaseMonkey.delete(1) } + end + def test_update_counters_should_quote_pkey_and_quote_counter_columns + assert_nothing_raised { MixedCaseMonkey.update_counters(1, :fleaCount => 99) } + end + def test_find_with_one_id_should_quote_pkey + assert_nothing_raised { MixedCaseMonkey.find(1) } + end + def test_find_with_multiple_ids_should_quote_pkey + assert_nothing_raised { MixedCaseMonkey.find([1,2]) } + end + def test_instance_update_should_quote_pkey + assert_nothing_raised { MixedCaseMonkey.find(1).save } + end + def test_instance_destroy_should_quote_pkey + assert_nothing_raised { MixedCaseMonkey.find(1).destroy } + end +end diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb new file mode 100644 index 0000000000..242223161c --- /dev/null +++ b/activerecord/test/cases/query_cache_test.rb @@ -0,0 +1,124 @@ +require 'abstract_unit' +require 'fixtures/topic' +require 'fixtures/reply' +require 'fixtures/task' +require 'fixtures/course' +require 'fixtures/category' +require 'fixtures/post' + + +class QueryCacheTest < ActiveSupport::TestCase + fixtures :tasks, :topics, :categories, :posts, :categories_posts + + def test_find_queries + assert_queries(2) { Task.find(1); Task.find(1) } + end + + def test_find_queries_with_cache + Task.cache do + assert_queries(1) { Task.find(1); Task.find(1) } + end + end + + def test_count_queries_with_cache + Task.cache do + assert_queries(1) { Task.count; Task.count } + end + end + + def test_query_cache_dups_results_correctly + Task.cache do + now = Time.now.utc + task = Task.find 1 + assert_not_equal now, task.starting + task.starting = now + task.reload + assert_not_equal now, task.starting + end + end + + def test_cache_is_flat + Task.cache do + Topic.columns # don't count this query + assert_queries(1) { Topic.find(1); Topic.find(1); } + end + + ActiveRecord::Base.cache do + assert_queries(1) { Task.find(1); Task.find(1) } + end + end + + def test_cache_does_not_wrap_string_results_in_arrays + Task.cache do + assert_instance_of String, Task.connection.select_value("SELECT count(*) AS count_all FROM tasks") + end + end +end + +uses_mocha 'QueryCacheExpiryTest' do + +class QueryCacheExpiryTest < ActiveSupport::TestCase + fixtures :tasks + + def test_find + Task.connection.expects(:clear_query_cache).times(1) + + assert !Task.connection.query_cache_enabled + Task.cache do + assert Task.connection.query_cache_enabled + Task.find(1) + + Task.uncached do + assert !Task.connection.query_cache_enabled + Task.find(1) + end + + assert Task.connection.query_cache_enabled + end + assert !Task.connection.query_cache_enabled + end + + def test_update + Task.connection.expects(:clear_query_cache).times(2) + + Task.cache do + Task.find(1).save! + end + end + + def test_destroy + Task.connection.expects(:clear_query_cache).times(2) + + Task.cache do + Task.find(1).destroy + end + end + + def test_insert + ActiveRecord::Base.connection.expects(:clear_query_cache).times(2) + + Task.cache do + Task.create! + end + end + + def test_cache_is_expired_by_habtm_update + ActiveRecord::Base.connection.expects(:clear_query_cache).times(2) + ActiveRecord::Base.cache do + c = Category.find(:first) + p = Post.find(:first) + p.categories << c + end + end + + def test_cache_is_expired_by_habtm_delete + ActiveRecord::Base.connection.expects(:clear_query_cache).times(2) + ActiveRecord::Base.cache do + c = Category.find(:first) + p = Post.find(:first) + p.categories.delete_all + end + end +end + +end diff --git a/activerecord/test/cases/readonly_test.rb b/activerecord/test/cases/readonly_test.rb new file mode 100755 index 0000000000..5674a4b442 --- /dev/null +++ b/activerecord/test/cases/readonly_test.rb @@ -0,0 +1,107 @@ +require 'abstract_unit' +require 'fixtures/post' +require 'fixtures/comment' +require 'fixtures/developer' +require 'fixtures/project' +require 'fixtures/reader' +require 'fixtures/person' + +# Dummy class methods to test implicit association scoping. +def Comment.foo() find :first end +def Project.foo() find :first end + + +class ReadOnlyTest < ActiveSupport::TestCase + fixtures :posts, :comments, :developers, :projects, :developers_projects + + def test_cant_save_readonly_record + dev = Developer.find(1) + assert !dev.readonly? + + dev.readonly! + assert dev.readonly? + + assert_nothing_raised do + dev.name = 'Luscious forbidden fruit.' + assert !dev.save + dev.name = 'Forbidden.' + end + assert_raise(ActiveRecord::ReadOnlyRecord) { dev.save } + assert_raise(ActiveRecord::ReadOnlyRecord) { dev.save! } + end + + + def test_find_with_readonly_option + Developer.find(:all).each { |d| assert !d.readonly? } + Developer.find(:all, :readonly => false).each { |d| assert !d.readonly? } + Developer.find(:all, :readonly => true).each { |d| assert d.readonly? } + end + + + def test_find_with_joins_option_implies_readonly + # Blank joins don't count. + Developer.find(:all, :joins => ' ').each { |d| assert !d.readonly? } + Developer.find(:all, :joins => ' ', :readonly => false).each { |d| assert !d.readonly? } + + # Others do. + Developer.find(:all, :joins => ', projects').each { |d| assert d.readonly? } + Developer.find(:all, :joins => ', projects', :readonly => false).each { |d| assert !d.readonly? } + end + + + def test_habtm_find_readonly + dev = Developer.find(1) + assert !dev.projects.empty? + assert dev.projects.all?(&:readonly?) + assert dev.projects.find(:all).all?(&:readonly?) + assert dev.projects.find(:all, :readonly => true).all?(&:readonly?) + end + + def test_has_many_find_readonly + post = Post.find(1) + assert !post.comments.empty? + assert !post.comments.any?(&:readonly?) + assert !post.comments.find(:all).any?(&:readonly?) + assert post.comments.find(:all, :readonly => true).all?(&:readonly?) + end + + def test_has_many_with_through_is_not_implicitly_marked_readonly + assert people = Post.find(1).people + assert !people.any?(&:readonly?) + end + + def test_readonly_scoping + Post.with_scope(:find => { :conditions => '1=1' }) do + assert !Post.find(1).readonly? + assert Post.find(1, :readonly => true).readonly? + assert !Post.find(1, :readonly => false).readonly? + end + + Post.with_scope(:find => { :joins => ' ' }) do + assert !Post.find(1).readonly? + assert Post.find(1, :readonly => true).readonly? + assert !Post.find(1, :readonly => false).readonly? + end + + # Oracle barfs on this because the join includes unqualified and + # conflicting column names + unless current_adapter?(:OracleAdapter) + Post.with_scope(:find => { :joins => ', developers' }) do + assert Post.find(1).readonly? + assert Post.find(1, :readonly => true).readonly? + assert !Post.find(1, :readonly => false).readonly? + end + end + + Post.with_scope(:find => { :readonly => true }) do + assert Post.find(1).readonly? + assert Post.find(1, :readonly => true).readonly? + assert !Post.find(1, :readonly => false).readonly? + end + end + + def test_association_collection_method_missing_scoping_not_readonly + assert !Developer.find(1).projects.foo.readonly? + assert !Post.find(1).comments.foo.readonly? + end +end diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb new file mode 100644 index 0000000000..7790747cea --- /dev/null +++ b/activerecord/test/cases/reflection_test.rb @@ -0,0 +1,175 @@ +require 'abstract_unit' +require 'fixtures/topic' +require 'fixtures/customer' +require 'fixtures/company' +require 'fixtures/company_in_module' +require 'fixtures/subscriber' + +class ReflectionTest < ActiveSupport::TestCase + fixtures :topics, :customers, :companies, :subscribers + + def setup + @first = Topic.find(1) + end + + def test_column_null_not_null + subscriber = Subscriber.find(:first) + assert subscriber.column_for_attribute("name").null + assert !subscriber.column_for_attribute("nick").null + end + + def test_read_attribute_names + assert_equal( + %w( id title author_name author_email_address bonus_time written_on last_read content approved replies_count parent_id type ).sort, + @first.attribute_names + ) + end + + def test_columns + assert_equal 12, 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 type), column_names + end + + def test_content_columns + content_columns = Topic.content_columns + content_column_names = content_columns.map {|column| column.name} + assert_equal 8, content_columns.length + assert_equal %w(title author_name author_email_address written_on bonus_time last_read content approved).sort, content_column_names.sort + end + + def test_column_string_type_and_limit + assert_equal :string, @first.column_for_attribute("title").type + assert_equal 255, @first.column_for_attribute("title").limit + end + + def test_column_null_not_null + subscriber = Subscriber.find(:first) + assert subscriber.column_for_attribute("name").null + assert !subscriber.column_for_attribute("nick").null + end + + def test_human_name_for_column + assert_equal "Author name", @first.column_for_attribute("author_name").human_name + end + + def test_integer_columns + assert_equal :integer, @first.column_for_attribute("id").type + end + + def test_reflection_klass_for_nested_class_name + reflection = ActiveRecord::Reflection::MacroReflection.new(nil, nil, { :class_name => 'MyApplication::Business::Company' }, nil) + assert_nothing_raised do + assert_equal MyApplication::Business::Company, reflection.klass + end + end + + def test_aggregation_reflection + reflection_for_address = ActiveRecord::Reflection::AggregateReflection.new( + :composed_of, :address, { :mapping => [ %w(address_street street), %w(address_city city), %w(address_country country) ] }, Customer + ) + + reflection_for_balance = ActiveRecord::Reflection::AggregateReflection.new( + :composed_of, :balance, { :class_name => "Money", :mapping => %w(balance amount) }, Customer + ) + + reflection_for_gps_location = ActiveRecord::Reflection::AggregateReflection.new( + :composed_of, :gps_location, { }, Customer + ) + + assert Customer.reflect_on_all_aggregations.include?(reflection_for_gps_location) + assert Customer.reflect_on_all_aggregations.include?(reflection_for_balance) + assert Customer.reflect_on_all_aggregations.include?(reflection_for_address) + + assert_equal reflection_for_address, Customer.reflect_on_aggregation(:address) + + assert_equal Address, Customer.reflect_on_aggregation(:address).klass + + assert_equal Money, Customer.reflect_on_aggregation(:balance).klass + end + + def test_has_many_reflection + reflection_for_clients = ActiveRecord::Reflection::AssociationReflection.new(:has_many, :clients, { :order => "id", :dependent => :destroy }, Firm) + + assert_equal reflection_for_clients, Firm.reflect_on_association(:clients) + + assert_equal Client, Firm.reflect_on_association(:clients).klass + assert_equal 'companies', Firm.reflect_on_association(:clients).table_name + + assert_equal Client, Firm.reflect_on_association(:clients_of_firm).klass + assert_equal 'companies', Firm.reflect_on_association(:clients_of_firm).table_name + end + + def test_has_one_reflection + reflection_for_account = ActiveRecord::Reflection::AssociationReflection.new(:has_one, :account, { :foreign_key => "firm_id", :dependent => :destroy }, Firm) + assert_equal reflection_for_account, Firm.reflect_on_association(:account) + + assert_equal Account, Firm.reflect_on_association(:account).klass + assert_equal 'accounts', Firm.reflect_on_association(:account).table_name + end + + def test_belongs_to_inferred_foreign_key_from_assoc_name + Company.belongs_to :foo + assert_equal "foo_id", Company.reflect_on_association(:foo).primary_key_name + Company.belongs_to :bar, :class_name => "Xyzzy" + assert_equal "bar_id", Company.reflect_on_association(:bar).primary_key_name + Company.belongs_to :baz, :class_name => "Xyzzy", :foreign_key => "xyzzy_id" + assert_equal "xyzzy_id", Company.reflect_on_association(:baz).primary_key_name + end + + def test_association_reflection_in_modules + assert_reflection MyApplication::Business::Firm, + :clients_of_firm, + :klass => MyApplication::Business::Client, + :class_name => 'Client', + :table_name => 'companies' + + assert_reflection MyApplication::Billing::Account, + :firm, + :klass => MyApplication::Business::Firm, + :class_name => 'MyApplication::Business::Firm', + :table_name => 'companies' + + assert_reflection MyApplication::Billing::Account, + :qualified_billing_firm, + :klass => MyApplication::Billing::Firm, + :class_name => 'MyApplication::Billing::Firm', + :table_name => 'companies' + + assert_reflection MyApplication::Billing::Account, + :unqualified_billing_firm, + :klass => MyApplication::Billing::Firm, + :class_name => 'Firm', + :table_name => 'companies' + + assert_reflection MyApplication::Billing::Account, + :nested_qualified_billing_firm, + :klass => MyApplication::Billing::Nested::Firm, + :class_name => 'MyApplication::Billing::Nested::Firm', + :table_name => 'companies' + + assert_reflection MyApplication::Billing::Account, + :nested_unqualified_billing_firm, + :klass => MyApplication::Billing::Nested::Firm, + :class_name => 'Nested::Firm', + :table_name => 'companies' + end + + def test_reflection_of_all_associations + assert_equal 17, Firm.reflect_on_all_associations.size + assert_equal 15, Firm.reflect_on_all_associations(:has_many).size + assert_equal 2, Firm.reflect_on_all_associations(:has_one).size + assert_equal 0, Firm.reflect_on_all_associations(:belongs_to).size + end + + private + def assert_reflection(klass, association, options) + assert reflection = klass.reflect_on_association(association) + options.each do |method, value| + assert_equal(value, reflection.send(method)) + end + end +end diff --git a/activerecord/test/cases/reserved_word_test_mysql.rb b/activerecord/test/cases/reserved_word_test_mysql.rb new file mode 100644 index 0000000000..0600c9460e --- /dev/null +++ b/activerecord/test/cases/reserved_word_test_mysql.rb @@ -0,0 +1,177 @@ +require "#{File.dirname(__FILE__)}/abstract_unit" + +class Group < ActiveRecord::Base + Group.table_name = 'group' + belongs_to :select, :class_name => 'Select' + has_one :values +end + +class Select < ActiveRecord::Base + Select.table_name = 'select' + has_many :groups +end + +class Values < ActiveRecord::Base + Values.table_name = 'values' +end + +class Distinct < ActiveRecord::Base + Distinct.table_name = 'distinct' + has_and_belongs_to_many :selects + has_many :values, :through => :groups +end + +# a suite of tests to ensure the ConnectionAdapters#MysqlAdapter can handle tables with +# reserved word names (ie: group, order, values, etc...) +class MysqlReservedWordTest < ActiveSupport::TestCase + def setup + @connection = ActiveRecord::Base.connection + + # we call execute directly here (and do similar below) because ActiveRecord::Base#create_table() + # will fail with these table names if these test cases fail + + create_tables_directly 'group'=>'id int auto_increment primary key, `order` varchar(255), select_id int', + 'select'=>'id int auto_increment primary key', + 'values'=>'id int auto_increment primary key, group_id int', + 'distinct'=>'id int auto_increment primary key', + 'distincts_selects'=>'distinct_id int, select_id int' + end + + def teardown + drop_tables_directly ['group', 'select', 'values', 'distinct', 'distincts_selects', 'order'] + end + + # create tables with reserved-word names and columns + def test_create_tables + assert_nothing_raised { + @connection.create_table :order do |t| + t.column :group, :string + end + } + end + + # rename tables with reserved-word names + def test_rename_tables + assert_nothing_raised { @connection.rename_table(:group, :order) } + end + + # alter column with a reserved-word name in a table with a reserved-word name + def test_change_columns + assert_nothing_raised { @connection.change_column_default(:group, :order, 'whatever') } + #the quoting here will reveal any double quoting issues in change_column's interaction with the column method in the adapter + assert_nothing_raised { @connection.change_column('group', 'order', :Int, :default => 0) } + assert_nothing_raised { @connection.rename_column(:group, :order, :values) } + end + + # dump structure of table with reserved word name + def test_structure_dump + assert_nothing_raised { @connection.structure_dump } + end + + # introspect table with reserved word name + def test_introspect + assert_nothing_raised { @connection.columns(:group) } + assert_nothing_raised { @connection.indexes(:group) } + end + + #fixtures + self.use_instantiated_fixtures = true + self.use_transactional_fixtures = false + + #fixtures :group + + def test_fixtures + f = create_test_fixtures :select, :distinct, :group, :values, :distincts_selects + + assert_nothing_raised { + f.each do |x| + x.delete_existing_fixtures + end + } + + assert_nothing_raised { + f.each do |x| + x.insert_fixtures + end + } + end + + #activerecord model class with reserved-word table name + def test_activerecord_model + create_test_fixtures :select, :distinct, :group, :values, :distincts_selects + x = nil + assert_nothing_raised { x = Group.new } + x.order = 'x' + assert_nothing_raised { x.save } + x.order = 'y' + assert_nothing_raised { x.save } + assert_nothing_raised { y = Group.find_by_order('y') } + assert_nothing_raised { y = Group.find(1) } + x = Group.find(1) + end + + # has_one association with reserved-word table name + def test_has_one_associations + create_test_fixtures :select, :distinct, :group, :values, :distincts_selects + v = nil + assert_nothing_raised { v = Group.find(1).values } + assert_equal v.id, 2 + end + + # belongs_to association with reserved-word table name + def test_belongs_to_associations + create_test_fixtures :select, :distinct, :group, :values, :distincts_selects + gs = nil + assert_nothing_raised { gs = Select.find(2).groups } + assert_equal gs.length, 2 + assert(gs.collect{|x| x.id}.sort == [2, 3]) + end + + # has_and_belongs_to_many with reserved-word table name + def test_has_and_belongs_to_many + create_test_fixtures :select, :distinct, :group, :values, :distincts_selects + s = nil + assert_nothing_raised { s = Distinct.find(1).selects } + assert_equal s.length, 2 + assert(s.collect{|x|x.id}.sort == [1, 2]) + end + + # activerecord model introspection with reserved-word table and column names + def test_activerecord_introspection + assert_nothing_raised { Group.table_exists? } + assert_nothing_raised { Group.columns } + end + + # Calculations + def test_calculations_work_with_reserved_words + assert_nothing_raised { Group.count } + end + + def test_associations_work_with_reserved_words + assert_nothing_raised { Select.find(:all, :include => [:groups]) } + end + + #the following functions were added to DRY test cases + + private + # custom fixture loader, uses Fixtures#create_fixtures and appends base_path to the current file's path + def create_test_fixtures(*fixture_names) + fixture_path = "./test/fixtures/reserved_words" + Fixtures.create_fixtures(fixture_path, fixture_names) + end + + # custom drop table, uses execute on connection to drop a table if it exists. note: escapes table_name + def drop_tables_directly(table_names, connection = @connection) + table_names.each do |name| + connection.execute("DROP TABLE IF EXISTS `#{name}`") + end + end + + # custom create table, uses execute on connection to create a table, note: escapes table_name, does NOT escape columns + def create_tables_directly (tables, connection = @connection) + tables.each do |table_name, column_properties| + connection.execute("CREATE TABLE `#{table_name}` ( #{column_properties} )") + end + end + +end diff --git a/activerecord/test/cases/schema_authorization_test_postgresql.rb b/activerecord/test/cases/schema_authorization_test_postgresql.rb new file mode 100644 index 0000000000..d42611b515 --- /dev/null +++ b/activerecord/test/cases/schema_authorization_test_postgresql.rb @@ -0,0 +1,75 @@ +require 'abstract_unit' + +class SchemaThing < ActiveRecord::Base +end + +class SchemaAuthorizationTest < ActiveSupport::TestCase + self.use_transactional_fixtures = false + + TABLE_NAME = 'schema_things' + COLUMNS = [ + 'id serial primary key', + 'name character varying(50)' + ] + USERS = ['rails_pg_schema_user1', 'rails_pg_schema_user2'] + + def setup + @connection = ActiveRecord::Base.connection + @connection.execute "SET search_path TO '$user',public" + set_session_auth + USERS.each do |u| + @connection.execute "CREATE ROLE #{u}" + @connection.execute "CREATE SCHEMA AUTHORIZATION #{u}" + set_session_auth u + @connection.execute "CREATE TABLE #{TABLE_NAME} (#{COLUMNS.join(',')})" + @connection.execute "INSERT INTO #{TABLE_NAME} (name) VALUES ('#{u}')" + set_session_auth + end + end + + def teardown + set_session_auth + @connection.execute "RESET search_path" + USERS.each do |u| + @connection.execute "DROP SCHEMA #{u} CASCADE" + @connection.execute "DROP ROLE #{u}" + end + end + + def test_schema_invisible + assert_raise(ActiveRecord::StatementInvalid) do + set_session_auth + @connection.execute "SELECT * FROM #{TABLE_NAME}" + end + end + + def test_schema_uniqueness + assert_nothing_raised do + set_session_auth + USERS.each do |u| + set_session_auth u + assert_equal u, @connection.select_value("SELECT name FROM #{TABLE_NAME} WHERE id = 1") + set_session_auth + end + end + end + + def test_sequence_schema_caching + assert_nothing_raised do + USERS.each do |u| + set_session_auth u + st = SchemaThing.new :name => 'TEST1' + st.save! + st = SchemaThing.new :id => 5, :name => 'TEST2' + st.save! + set_session_auth + end + end + end + + private + def set_session_auth auth = nil + @connection.execute "SET SESSION AUTHORIZATION #{auth || 'default'}" + end + +end diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb new file mode 100644 index 0000000000..2b7226ec3b --- /dev/null +++ b/activerecord/test/cases/schema_dumper_test.rb @@ -0,0 +1,131 @@ +require 'abstract_unit' +require "#{File.dirname(__FILE__)}/../lib/active_record/schema_dumper" +require 'stringio' + +if ActiveRecord::Base.connection.respond_to?(:tables) + + class SchemaDumperTest < ActiveSupport::TestCase + def standard_dump + stream = StringIO.new + ActiveRecord::SchemaDumper.ignore_tables = [] + ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream) + stream.string + end + + def test_schema_dump + output = standard_dump + assert_match %r{create_table "accounts"}, output + assert_match %r{create_table "authors"}, output + assert_no_match %r{create_table "schema_info"}, output + end + + def test_schema_dump_excludes_sqlite_sequence + output = standard_dump + assert_no_match %r{create_table "sqlite_sequence"}, output + end + + def assert_line_up(lines, pattern, required = false) + return assert(true) if lines.empty? + matches = lines.map { |line| line.match(pattern) } + assert matches.all? if required + matches.compact! + return assert(true) if matches.empty? + assert_equal 1, matches.map{ |match| match.offset(0).first }.uniq.length + end + + def column_definition_lines(output = standard_dump) + output.scan(/^( *)create_table.*?\n(.*?)^\1end/m).map{ |m| m.last.split(/\n/) } + end + + def test_types_line_up + column_definition_lines.each do |column_set| + next if column_set.empty? + + lengths = column_set.map do |column| + if match = column.match(/t\.(?:integer|decimal|float|datetime|timestamp|time|date|text|binary|string|boolean)\s+"/) + match[0].length + end + end + + assert_equal 1, lengths.uniq.length + end + end + + def test_arguments_line_up + column_definition_lines.each do |column_set| + assert_line_up(column_set, /:default => /) + assert_line_up(column_set, /:limit => /) + assert_line_up(column_set, /:null => /) + end + end + + def test_no_dump_errors + output = standard_dump + assert_no_match %r{\# Could not dump table}, output + end + + def test_schema_dump_includes_not_null_columns + stream = StringIO.new + + ActiveRecord::SchemaDumper.ignore_tables = [/^[^r]/] + ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream) + output = stream.string + assert_match %r{:null => false}, output + end + + def test_schema_dump_with_string_ignored_table + stream = StringIO.new + + ActiveRecord::SchemaDumper.ignore_tables = ['accounts'] + ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream) + output = stream.string + assert_no_match %r{create_table "accounts"}, output + assert_match %r{create_table "authors"}, output + assert_no_match %r{create_table "schema_info"}, output + end + + + def test_schema_dump_with_regexp_ignored_table + stream = StringIO.new + + ActiveRecord::SchemaDumper.ignore_tables = [/^account/] + ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream) + output = stream.string + assert_no_match %r{create_table "accounts"}, output + assert_match %r{create_table "authors"}, output + assert_no_match %r{create_table "schema_info"}, output + end + + + def test_schema_dump_illegal_ignored_table_value + stream = StringIO.new + ActiveRecord::SchemaDumper.ignore_tables = [5] + assert_raise(StandardError) do + ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream) + end + end + + if current_adapter?(:MysqlAdapter) + def test_schema_dump_should_not_add_default_value_for_mysql_text_field + output = standard_dump + assert_match %r{t.text\s+"body",\s+:default => "",\s+:null => false$}, output + end + + def test_mysql_schema_dump_should_honor_nonstandard_primary_keys + output = standard_dump + match = output.match(%r{create_table "movies"(.*)do}) + assert_not_nil(match, "nonstandardpk table not found") + assert_match %r(:primary_key => "movieid"), match[1], "non-standard primary key not preserved" + end + end + + def test_schema_dump_includes_decimal_options + stream = StringIO.new + ActiveRecord::SchemaDumper.ignore_tables = [/^[^n]/] + ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream) + output = stream.string + assert_match %r{:precision => 3,[[:space:]]+:scale => 2,[[:space:]]+:default => 2.78}, output + end + end + +end diff --git a/activerecord/test/cases/schema_test_postgresql.rb b/activerecord/test/cases/schema_test_postgresql.rb new file mode 100644 index 0000000000..c38d6c9253 --- /dev/null +++ b/activerecord/test/cases/schema_test_postgresql.rb @@ -0,0 +1,64 @@ +require 'abstract_unit' + +class SchemaTest < ActiveSupport::TestCase + self.use_transactional_fixtures = false + + SCHEMA_NAME = 'test_schema' + TABLE_NAME = 'things' + COLUMNS = [ + 'id integer', + 'name character varying(50)', + 'moment timestamp without time zone default now()' + ] + + def setup + @connection = ActiveRecord::Base.connection + @connection.execute "CREATE SCHEMA #{SCHEMA_NAME} CREATE TABLE #{TABLE_NAME} (#{COLUMNS.join(',')})" + end + + def teardown + @connection.execute "DROP SCHEMA #{SCHEMA_NAME} CASCADE" + end + + def test_with_schema_prefixed_table_name + assert_nothing_raised do + assert_equal COLUMNS, columns("#{SCHEMA_NAME}.#{TABLE_NAME}") + end + end + + def test_with_schema_search_path + assert_nothing_raised do + with_schema_search_path(SCHEMA_NAME) do + assert_equal COLUMNS, columns(TABLE_NAME) + end + end + end + + def test_raise_on_unquoted_schema_name + assert_raise(ActiveRecord::StatementInvalid) do + with_schema_search_path '$user,public' + end + end + + def test_without_schema_search_path + assert_raise(ActiveRecord::StatementInvalid) { columns(TABLE_NAME) } + end + + def test_ignore_nil_schema_search_path + assert_nothing_raised { with_schema_search_path nil } + end + + private + def columns(table_name) + @connection.send(:column_definitions, table_name).map do |name, type, default| + "#{name} #{type}" + (default ? " default #{default}" : '') + end + end + + def with_schema_search_path(schema_search_path) + @connection.schema_search_path = schema_search_path + yield if block_given? + ensure + @connection.schema_search_path = "'$user', public" + end +end diff --git a/activerecord/test/cases/serialization_test.rb b/activerecord/test/cases/serialization_test.rb new file mode 100644 index 0000000000..e14fa02780 --- /dev/null +++ b/activerecord/test/cases/serialization_test.rb @@ -0,0 +1,47 @@ +require 'abstract_unit' +require 'fixtures/contact' + +class SerializationTest < ActiveSupport::TestCase + FORMATS = [ :xml, :json ] + + def setup + @contact_attributes = { + :name => 'aaron stack', + :age => 25, + :avatar => 'binarydata', + :created_at => Time.utc(2006, 8, 1), + :awesome => false, + :preferences => { :gem => 'ruby' } + } + + @contact = Contact.new(@contact_attributes) + end + + def test_serialize_should_be_reversible + for format in FORMATS + @serialized = Contact.new.send("to_#{format}") + contact = Contact.new.send("from_#{format}", @serialized) + + assert_equal @contact_attributes.keys.collect(&:to_s).sort, contact.attributes.keys.collect(&:to_s).sort, "For #{format}" + end + end + + def test_serialize_should_allow_attribute_only_filtering + for format in FORMATS + @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}" + assert_nil contact.avatar, "For #{format}" + end + end + + def test_serialize_should_allow_attribute_except_filtering + for format in FORMATS + @serialized = Contact.new(@contact_attributes).send("to_#{format}", :except => [ :age, :name ]) + contact = Contact.new.send("from_#{format}", @serialized) + assert_nil contact.name, "For #{format}" + assert_nil contact.age, "For #{format}" + assert_equal @contact_attributes[:awesome], contact.awesome, "For #{format}" + end + end +end \ No newline at end of file diff --git a/activerecord/test/cases/synonym_test_oracle.rb b/activerecord/test/cases/synonym_test_oracle.rb new file mode 100644 index 0000000000..c053bacb69 --- /dev/null +++ b/activerecord/test/cases/synonym_test_oracle.rb @@ -0,0 +1,17 @@ +require 'abstract_unit' +require 'fixtures/topic' +require 'fixtures/subject' + +# confirm that synonyms work just like tables; in this case +# the "subjects" table in Oracle (defined in oci.sql) is just +# a synonym to the "topics" table + +class TestOracleSynonym < ActiveSupport::TestCase + + def test_oracle_synonym + topic = Topic.new + subject = Subject.new + assert_equal(topic.attributes, subject.attributes) + end + +end diff --git a/activerecord/test/cases/table_name_test_sqlserver.rb b/activerecord/test/cases/table_name_test_sqlserver.rb new file mode 100644 index 0000000000..f52a3da5fb --- /dev/null +++ b/activerecord/test/cases/table_name_test_sqlserver.rb @@ -0,0 +1,23 @@ +require 'abstract_unit' +require "#{File.dirname(__FILE__)}/../lib/active_record/schema" + +if ActiveRecord::Base.connection.supports_migrations? + class Order < ActiveRecord::Base + self.table_name = '[order]' + end + + class TableNameTest < ActiveSupport::TestCase + self.use_transactional_fixtures = false + + # Ensures Model.columns works when using SQLServer escape characters. + # Enables legacy schemas using SQL reserved words as table names. + # Should work with table names with spaces as well ('table name'). + def test_escaped_table_name + assert_nothing_raised do + ActiveRecord::Base.connection.select_all 'SELECT * FROM [order]' + end + assert_equal '[order]', Order.table_name + assert_equal 5, Order.columns.length + end + end +end diff --git a/activerecord/test/cases/threaded_connections_test.rb b/activerecord/test/cases/threaded_connections_test.rb new file mode 100644 index 0000000000..ec90df5294 --- /dev/null +++ b/activerecord/test/cases/threaded_connections_test.rb @@ -0,0 +1,48 @@ +require 'abstract_unit' +require 'fixtures/topic' +require 'fixtures/reply' + +unless %w(FrontBase).include? ActiveRecord::Base.connection.adapter_name + class ThreadedConnectionsTest < ActiveSupport::TestCase + self.use_transactional_fixtures = false + + fixtures :topics + + def setup + @connection = ActiveRecord::Base.remove_connection + @connections = [] + @allow_concurrency = ActiveRecord::Base.allow_concurrency + end + + def teardown + # clear the connection cache + ActiveRecord::Base.send(:clear_all_cached_connections!) + # set allow_concurrency to saved value + ActiveRecord::Base.allow_concurrency = @allow_concurrency + # reestablish old connection + ActiveRecord::Base.establish_connection(@connection) + end + + def gather_connections(use_threaded_connections) + ActiveRecord::Base.allow_concurrency = use_threaded_connections + ActiveRecord::Base.establish_connection(@connection) + + 5.times do + Thread.new do + Topic.find :first + @connections << ActiveRecord::Base.active_connections.values.first + end.join + end + end + + def test_threaded_connections + gather_connections(true) + assert_equal @connections.uniq.length, 5 + end + + def test_unthreaded_connections + gather_connections(false) + assert_equal @connections.uniq.length, 1 + end + end +end diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb new file mode 100644 index 0000000000..f36ee3f4bb --- /dev/null +++ b/activerecord/test/cases/transactions_test.rb @@ -0,0 +1,281 @@ +require 'abstract_unit' +require 'fixtures/topic' +require 'fixtures/reply' +require 'fixtures/developer' + +class TransactionTest < ActiveSupport::TestCase + self.use_transactional_fixtures = false + fixtures :topics, :developers + + def setup + @first, @second = Topic.find(1, 2).sort_by { |t| t.id } + end + + def test_successful + Topic.transaction do + @first.approved = true + @second.approved = false + @first.save + @second.save + end + + assert Topic.find(1).approved?, "First should have been approved" + assert !Topic.find(2).approved?, "Second should have been unapproved" + end + + def transaction_with_return + Topic.transaction do + @first.approved = true + @second.approved = false + @first.save + @second.save + return + end + end + + def test_successful_with_return + class << Topic.connection + alias :real_commit_db_transaction :commit_db_transaction + def commit_db_transaction + $committed = true + real_commit_db_transaction + end + end + + $committed = false + transaction_with_return + assert $committed + + assert Topic.find(1).approved?, "First should have been approved" + assert !Topic.find(2).approved?, "Second should have been unapproved" + ensure + class << Topic.connection + alias :commit_db_transaction :real_commit_db_transaction rescue nil + end + end + + def test_successful_with_instance_method + @first.transaction do + @first.approved = true + @second.approved = false + @first.save + @second.save + end + + assert Topic.find(1).approved?, "First should have been approved" + assert !Topic.find(2).approved?, "Second should have been unapproved" + end + + def test_failing_on_exception + begin + Topic.transaction do + @first.approved = true + @second.approved = false + @first.save + @second.save + raise "Bad things!" + end + rescue + # caught it + end + + assert @first.approved?, "First should still be changed in the objects" + assert !@second.approved?, "Second should still be changed in the objects" + + assert !Topic.find(1).approved?, "First shouldn't have been approved" + assert Topic.find(2).approved?, "Second should still be approved" + end + + + def test_callback_rollback_in_save + add_exception_raising_after_save_callback_to_topic + + begin + @first.approved = true + @first.save + flunk + rescue => e + assert_equal "Make the transaction rollback", e.message + assert !Topic.find(1).approved? + ensure + remove_exception_raising_after_save_callback_to_topic + end + end + + def test_callback_rollback_in_create + new_topic = Topic.new( + :title => "A new topic", + :author_name => "Ben", + :author_email_address => "ben@example.com", + :written_on => "2003-07-16t15:28:11.2233+01:00", + :last_read => "2004-04-15", + :bonus_time => "2005-01-30t15:28:00.00+01:00", + :content => "Have a nice day", + :approved => false) + new_record_snapshot = new_topic.new_record? + id_present = new_topic.has_attribute?(Topic.primary_key) + id_snapshot = new_topic.id + + # Make sure the second save gets the after_create callback called. + 2.times do + begin + add_exception_raising_after_create_callback_to_topic + new_topic.approved = true + new_topic.save + flunk + rescue => e + assert_equal "Make the transaction rollback", e.message + assert_equal new_record_snapshot, new_topic.new_record?, "The topic should have its old new_record value" + assert_equal id_snapshot, new_topic.id, "The topic should have its old id" + assert_equal id_present, new_topic.has_attribute?(Topic.primary_key) + ensure + remove_exception_raising_after_create_callback_to_topic + end + end + end + + def test_nested_explicit_transactions + Topic.transaction do + Topic.transaction do + @first.approved = true + @second.approved = false + @first.save + @second.save + end + end + + assert Topic.find(1).approved?, "First should have been approved" + assert !Topic.find(2).approved?, "Second should have been unapproved" + end + + def test_manually_rolling_back_a_transaction + Topic.transaction do + @first.approved = true + @second.approved = false + @first.save + @second.save + + raise ActiveRecord::Rollback + end + + assert @first.approved?, "First should still be changed in the objects" + assert !@second.approved?, "Second should still be changed in the objects" + + assert !Topic.find(1).approved?, "First shouldn't have been approved" + assert Topic.find(2).approved?, "Second should still be approved" + end + + uses_mocha 'mocking connection.commit_db_transaction' do + def test_rollback_when_commit_raises + Topic.connection.expects(:begin_db_transaction) + Topic.connection.expects(:commit_db_transaction).raises('OH NOES') + Topic.connection.expects(:rollback_db_transaction) + + assert_raise RuntimeError do + Topic.transaction do + # do nothing + end + end + end + end + + private + def add_exception_raising_after_save_callback_to_topic + Topic.class_eval { def after_save() raise "Make the transaction rollback" end } + end + + def remove_exception_raising_after_save_callback_to_topic + Topic.class_eval { remove_method :after_save } + end + + def add_exception_raising_after_create_callback_to_topic + Topic.class_eval { def after_create() raise "Make the transaction rollback" end } + end + + def remove_exception_raising_after_create_callback_to_topic + Topic.class_eval { remove_method :after_create } + end +end + +if current_adapter?(:PostgreSQLAdapter) + class ConcurrentTransactionTest < TransactionTest + def setup + @allow_concurrency = ActiveRecord::Base.allow_concurrency + ActiveRecord::Base.allow_concurrency = true + super + end + + def teardown + super + ActiveRecord::Base.allow_concurrency = @allow_concurrency + end + + # This will cause transactions to overlap and fail unless they are performed on + # separate database connections. + def test_transaction_per_thread + assert_nothing_raised do + threads = (1..3).map do + Thread.new do + Topic.transaction do + topic = Topic.find(1) + topic.approved = !topic.approved? + topic.save! + topic.approved = !topic.approved? + topic.save! + end + end + end + + threads.each { |t| t.join } + end + end + + # Test for dirty reads among simultaneous transactions. + def test_transaction_isolation__read_committed + # Should be invariant. + original_salary = Developer.find(1).salary + temporary_salary = 200000 + + assert_nothing_raised do + threads = (1..3).map do + Thread.new do + Developer.transaction do + # Expect original salary. + dev = Developer.find(1) + assert_equal original_salary, dev.salary + + dev.salary = temporary_salary + dev.save! + + # Expect temporary salary. + dev = Developer.find(1) + assert_equal temporary_salary, dev.salary + + dev.salary = original_salary + dev.save! + + # Expect original salary. + dev = Developer.find(1) + assert_equal original_salary, dev.salary + end + end + end + + # Keep our eyes peeled. + threads << Thread.new do + 10.times do + sleep 0.05 + Developer.transaction do + # Always expect original salary. + assert_equal original_salary, Developer.find(1).salary + end + end + end + + threads.each { |t| t.join } + end + + assert_equal original_salary, Developer.find(1).salary + end + end +end diff --git a/activerecord/test/cases/unconnected_test.rb b/activerecord/test/cases/unconnected_test.rb new file mode 100755 index 0000000000..6f475f8364 --- /dev/null +++ b/activerecord/test/cases/unconnected_test.rb @@ -0,0 +1,32 @@ +require 'abstract_unit' + +class TestRecord < ActiveRecord::Base +end + +class TestUnconnectedAdapter < ActiveSupport::TestCase + self.use_transactional_fixtures = false + + def setup + @underlying = ActiveRecord::Base.connection + @specification = ActiveRecord::Base.remove_connection + end + + def teardown + @underlying = nil + ActiveRecord::Base.establish_connection(@specification) + end + + def test_connection_no_longer_established + assert_raise(ActiveRecord::ConnectionNotEstablished) do + TestRecord.find(1) + end + + assert_raise(ActiveRecord::ConnectionNotEstablished) do + TestRecord.new.save + end + end + + def test_underlying_adapter_no_longer_active + assert !@underlying.active?, "Removed adapter should no longer be active" + end +end diff --git a/activerecord/test/cases/validations_test.rb b/activerecord/test/cases/validations_test.rb new file mode 100755 index 0000000000..daec199be1 --- /dev/null +++ b/activerecord/test/cases/validations_test.rb @@ -0,0 +1,1407 @@ +require 'abstract_unit' +require 'fixtures/topic' +require 'fixtures/reply' +require 'fixtures/person' +require 'fixtures/developer' +require 'fixtures/warehouse_thing' + +# The following methods in Topic are used in test_conditional_validation_* +class Topic + def condition_is_true + return true + end + + def condition_is_true_but_its_not + return false + end +end + +class ProtectedPerson < ActiveRecord::Base + set_table_name 'people' + attr_accessor :addon + attr_protected :first_name +end + +class UniqueReply < Reply + validates_uniqueness_of :content, :scope => 'parent_id' +end + +class PlagiarizedReply < Reply + validates_acceptance_of :author_name +end + +class SillyUniqueReply < UniqueReply +end + +class Topic < ActiveRecord::Base + has_many :unique_replies, :dependent => :destroy, :foreign_key => "parent_id" + has_many :silly_unique_replies, :dependent => :destroy, :foreign_key => "parent_id" +end + +class Wizard < ActiveRecord::Base + self.abstract_class = true + + validates_uniqueness_of :name +end + +class IneptWizard < Wizard + validates_uniqueness_of :city +end + +class Conjurer < IneptWizard +end + +class Thaumaturgist < IneptWizard +end + +class ValidationsTest < ActiveSupport::TestCase + fixtures :topics, :developers, 'warehouse-things' + + def setup + Topic.write_inheritable_attribute(:validate, nil) + Topic.write_inheritable_attribute(:validate_on_create, nil) + Topic.write_inheritable_attribute(:validate_on_update, nil) + end + + def test_single_field_validation + r = Reply.new + r.title = "There's no content!" + assert !r.valid?, "A reply without content shouldn't be saveable" + + r.content = "Messa content!" + assert r.valid?, "A reply with content should be saveable" + end + + def test_single_attr_validation_and_error_msg + r = Reply.new + r.title = "There's no content!" + assert !r.valid? + assert r.errors.invalid?("content"), "A reply without content should mark that attribute as invalid" + assert_equal "Empty", r.errors.on("content"), "A reply without content should contain an error" + assert_equal 1, r.errors.count + end + + def test_double_attr_validation_and_error_msg + r = Reply.new + assert !r.valid? + + assert r.errors.invalid?("title"), "A reply without title should mark that attribute as invalid" + assert_equal "Empty", r.errors.on("title"), "A reply without title should contain an error" + + assert r.errors.invalid?("content"), "A reply without content should mark that attribute as invalid" + assert_equal "Empty", r.errors.on("content"), "A reply without content should contain an error" + + assert_equal 2, r.errors.count + end + + def test_error_on_create + r = Reply.new + r.title = "Wrong Create" + assert !r.valid? + assert r.errors.invalid?("title"), "A reply with a bad title should mark that attribute as invalid" + assert_equal "is Wrong Create", r.errors.on("title"), "A reply with a bad content should contain an error" + end + + def test_error_on_update + r = Reply.new + r.title = "Bad" + r.content = "Good" + assert r.save, "First save should be successful" + + r.title = "Wrong Update" + assert !r.save, "Second save should fail" + + assert r.errors.invalid?("title"), "A reply with a bad title should mark that attribute as invalid" + assert_equal "is Wrong Update", r.errors.on("title"), "A reply with a bad content should contain an error" + end + + def test_invalid_record_exception + assert_raises(ActiveRecord::RecordInvalid) { Reply.create! } + assert_raises(ActiveRecord::RecordInvalid) { Reply.new.save! } + + begin + r = Reply.new + r.save! + flunk + rescue ActiveRecord::RecordInvalid => invalid + assert_equal r, invalid.record + end + end + + def test_exception_on_create_bang_many + assert_raises(ActiveRecord::RecordInvalid) do + Reply.create!([ { "title" => "OK" }, { "title" => "Wrong Create" }]) + end + end + + def test_scoped_create_without_attributes + Reply.with_scope(:create => {}) do + assert_raises(ActiveRecord::RecordInvalid) { Reply.create! } + end + end + + def test_create_with_exceptions_using_scope_for_protected_attributes + assert_nothing_raised do + ProtectedPerson.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.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_single_error_per_attr_iteration + r = Reply.new + r.save + + errors = [] + r.errors.each { |attr, msg| errors << [attr, msg] } + + assert errors.include?(["title", "Empty"]) + assert errors.include?(["content", "Empty"]) + end + + def test_multiple_errors_per_attr_iteration_with_full_error_composition + r = Reply.new + r.title = "Wrong Create" + r.content = "Mismatch" + r.save + + errors = [] + r.errors.each_full { |error| errors << error } + + assert_equal "Title is Wrong Create", errors[0] + assert_equal "Title is Content Mismatch", errors[1] + assert_equal 2, r.errors.count + end + + def test_errors_on_base + r = Reply.new + r.content = "Mismatch" + r.save + r.errors.add_to_base "Reply is not dignifying" + + errors = [] + r.errors.each_full { |error| errors << error } + + assert_equal "Reply is not dignifying", r.errors.on_base + + assert errors.include?("Title Empty") + assert errors.include?("Reply is not dignifying") + assert_equal 2, r.errors.count + end + + def test_create_without_validation + reply = Reply.new + assert !reply.save + assert reply.save(false) + end + + def test_create_without_validation_bang + count = Reply.count + assert_nothing_raised { Reply.new.save_without_validation! } + assert count+1, Reply.count + end + + def test_validates_each + perform = true + hits = 0 + Topic.validates_each(:title, :content, [:title, :content]) do |record, attr| + if perform + record.errors.add attr, 'gotcha' + hits += 1 + end + end + t = Topic.new("title" => "valid", "content" => "whatever") + assert !t.save + assert_equal 4, hits + assert_equal %w(gotcha gotcha), t.errors.on(:title) + assert_equal %w(gotcha gotcha), t.errors.on(:content) + ensure + perform = false + end + + def test_no_title_confirmation + Topic.validates_confirmation_of(:title) + + t = Topic.new(:author_name => "Plutarch") + assert t.valid? + + t.title_confirmation = "Parallel Lives" + assert !t.valid? + + t.title_confirmation = nil + t.title = "Parallel Lives" + assert t.valid? + + t.title_confirmation = "Parallel Lives" + assert t.valid? + end + + def test_title_confirmation + Topic.validates_confirmation_of(:title) + + t = Topic.create("title" => "We should be confirmed","title_confirmation" => "") + assert !t.save + + t.title_confirmation = "We should be confirmed" + assert t.save + end + + def test_terms_of_service_agreement_no_acceptance + Topic.validates_acceptance_of(:terms_of_service, :on => :create) + + t = Topic.create("title" => "We should not be confirmed") + assert t.save + end + + def test_terms_of_service_agreement + Topic.validates_acceptance_of(:terms_of_service, :on => :create) + + t = Topic.create("title" => "We should be confirmed","terms_of_service" => "") + assert !t.save + assert_equal "must be accepted", t.errors.on(:terms_of_service) + + t.terms_of_service = "1" + assert t.save + end + + + def test_eula + Topic.validates_acceptance_of(:eula, :message => "must be abided", :on => :create) + + t = Topic.create("title" => "We should be confirmed","eula" => "") + assert !t.save + assert_equal "must be abided", t.errors.on(:eula) + + t.eula = "1" + assert t.save + end + + def test_terms_of_service_agreement_with_accept_value + Topic.validates_acceptance_of(:terms_of_service, :on => :create, :accept => "I agree.") + + t = Topic.create("title" => "We should be confirmed", "terms_of_service" => "") + assert !t.save + assert_equal "must be accepted", t.errors.on(:terms_of_service) + + t.terms_of_service = "I agree." + assert t.save + end + + def test_validates_acceptance_of_as_database_column + reply = PlagiarizedReply.create("author_name" => "Dan Brown") + assert_equal "Dan Brown", reply["author_name"] + end + + def test_validates_acceptance_of_with_non_existant_table + Object.const_set :IncorporealModel, Class.new(ActiveRecord::Base) + + assert_nothing_raised ActiveRecord::StatementInvalid do + IncorporealModel.validates_acceptance_of(:incorporeal_column) + end + end + + def test_validate_presences + Topic.validates_presence_of(:title, :content) + + t = Topic.create + assert !t.save + assert_equal "can't be blank", t.errors.on(:title) + assert_equal "can't be blank", t.errors.on(:content) + + t.title = "something" + t.content = " " + + assert !t.save + assert_equal "can't be blank", t.errors.on(:content) + + t.content = "like stuff" + + assert t.save + end + + def test_validate_uniqueness + Topic.validates_uniqueness_of(:title) + + t = Topic.new("title" => "I'm unique!") + assert t.save, "Should save t as unique" + + t.content = "Remaining unique" + assert t.save, "Should still save t as unique" + + t2 = Topic.new("title" => "I'm unique!") + assert !t2.valid?, "Shouldn't be valid" + assert !t2.save, "Shouldn't save t2 as unique" + assert_equal "has already been taken", t2.errors.on(:title) + + t2.title = "Now Im really also unique" + assert t2.save, "Should now save t2 as unique" + end + + def test_validate_uniqueness_with_scope + Reply.validates_uniqueness_of(:content, :scope => "parent_id") + + 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" + + r2.content = "something else" + assert r2.save, "Saving r2 second time" + + t2 = Topic.create("title" => "I'm unique too!") + r3 = t2.replies.create "title" => "r3", "content" => "hello world" + assert r3.valid?, "Saving r3" + end + + def test_validate_uniqueness_scoped_to_defining_class + t = Topic.create("title" => "What, me worry?") + + r1 = t.unique_replies.create "title" => "r1", "content" => "a barrel of fun" + assert r1.valid?, "Saving r1" + + r2 = t.silly_unique_replies.create "title" => "r2", "content" => "a barrel of fun" + assert !r2.valid?, "Saving r2" + + # Should succeed as validates_uniqueness_of only applies to + # UniqueReply and its subclasses + r3 = t.replies.create "title" => "r2", "content" => "a barrel of fun" + assert r3.valid?, "Saving r3" + end + + def test_validate_uniqueness_with_scope_array + Reply.validates_uniqueness_of(:author_name, :scope => [:author_email_address, :parent_id]) + + t = Topic.create("title" => "The earth is actually flat!") + + r1 = t.replies.create "author_name" => "jeremy", "author_email_address" => "jeremy@rubyonrails.com", "title" => "You're crazy!", "content" => "Crazy reply" + assert r1.valid?, "Saving r1" + + r2 = t.replies.create "author_name" => "jeremy", "author_email_address" => "jeremy@rubyonrails.com", "title" => "You're crazy!", "content" => "Crazy reply again..." + assert !r2.valid?, "Saving r2. Double reply by same author." + + r2.author_email_address = "jeremy_alt_email@rubyonrails.com" + assert r2.save, "Saving r2 the second time." + + r3 = t.replies.create "author_name" => "jeremy", "author_email_address" => "jeremy_alt_email@rubyonrails.com", "title" => "You're wrong", "content" => "It's cubic" + assert !r3.valid?, "Saving r3" + + r3.author_name = "jj" + assert r3.save, "Saving r3 the second time." + + r3.author_name = "jeremy" + assert !r3.save, "Saving r3 the third time." + end + + def test_validate_case_insensitive_uniqueness + Topic.validates_uniqueness_of(:title, :parent_id, :case_sensitive => false, :allow_nil => true) + + t = Topic.new("title" => "I'm unique!", :parent_id => 2) + assert t.save, "Should save t as unique" + + t.content = "Remaining unique" + assert t.save, "Should still save t as unique" + + t2 = Topic.new("title" => "I'm UNIQUE!", :parent_id => 1) + assert !t2.valid?, "Shouldn't be valid" + assert !t2.save, "Shouldn't save t2 as unique" + assert t2.errors.on(:title) + assert t2.errors.on(:parent_id) + assert_equal "has already been taken", t2.errors.on(:title) + + t2.title = "I'm truly UNIQUE!" + assert !t2.valid?, "Shouldn't be valid" + assert !t2.save, "Shouldn't save t2 as unique" + assert_nil t2.errors.on(:title) + assert t2.errors.on(:parent_id) + + t2.parent_id = 3 + assert t2.save, "Should now save t2 as unique" + + t2.parent_id = nil + t2.title = nil + assert t2.valid?, "should validate with nil" + assert t2.save, "should save with nil" + end + + def test_validate_uniqueness_with_non_standard_table_names + i1 = WarehouseThing.create(:value => 1000) + assert !i1.valid?, "i1 should not be valid" + assert i1.errors.on(:value), "Should not be empty" + end + + + def test_validate_straight_inheritance_uniqueness + w1 = IneptWizard.create(:name => "Rincewind", :city => "Ankh-Morpork") + assert w1.valid?, "Saving w1" + + # Should use validation from base class (which is abstract) + w2 = IneptWizard.new(:name => "Rincewind", :city => "Quirm") + assert !w2.valid?, "w2 shouldn't be valid" + assert w2.errors.on(:name), "Should have errors for name" + assert_equal "has already been taken", w2.errors.on(:name), "Should have uniqueness message for name" + + w3 = Conjurer.new(:name => "Rincewind", :city => "Quirm") + assert !w3.valid?, "w3 shouldn't be valid" + assert w3.errors.on(:name), "Should have errors for name" + assert_equal "has already been taken", w3.errors.on(:name), "Should have uniqueness message for name" + + w4 = Conjurer.create(:name => "The Amazing Bonko", :city => "Quirm") + assert w4.valid?, "Saving w4" + + w5 = Thaumaturgist.new(:name => "The Amazing Bonko", :city => "Lancre") + assert !w5.valid?, "w5 shouldn't be valid" + assert w5.errors.on(:name), "Should have errors for name" + assert_equal "has already been taken", w5.errors.on(:name), "Should have uniqueness message for name" + + w6 = Thaumaturgist.new(:name => "Mustrum Ridcully", :city => "Quirm") + assert !w6.valid?, "w6 shouldn't be valid" + assert w6.errors.on(:city), "Should have errors for city" + assert_equal "has already been taken", w6.errors.on(:city), "Should have uniqueness message for city" + end + + def test_validate_format + Topic.validates_format_of(:title, :content, :with => /^Validation\smacros \w+!$/, :message => "is bad data") + + t = Topic.create("title" => "i'm incorrect", "content" => "Validation macros rule!") + assert !t.valid?, "Shouldn't be valid" + assert !t.save, "Shouldn't save because it's invalid" + assert_equal "is bad data", t.errors.on(:title) + assert_nil t.errors.on(:content) + + t.title = "Validation macros rule!" + + assert t.save + assert_nil t.errors.on(:title) + + assert_raise(ArgumentError) { Topic.validates_format_of(:title, :content) } + end + + # testing ticket #3142 + def test_validate_format_numeric + Topic.validates_format_of(:title, :content, :with => /^[1-9][0-9]*$/, :message => "is bad data") + + t = Topic.create("title" => "72x", "content" => "6789") + assert !t.valid?, "Shouldn't be valid" + assert !t.save, "Shouldn't save because it's invalid" + assert_equal "is bad data", t.errors.on(:title) + assert_nil t.errors.on(:content) + + t.title = "-11" + assert !t.valid?, "Shouldn't be valid" + + t.title = "03" + assert !t.valid?, "Shouldn't be valid" + + t.title = "z44" + assert !t.valid?, "Shouldn't be valid" + + t.title = "5v7" + assert !t.valid?, "Shouldn't be valid" + + t.title = "1" + + assert t.save + assert_nil t.errors.on(:title) + end + + def test_validates_inclusion_of + Topic.validates_inclusion_of( :title, :in => %w( a b c d e f g ) ) + + assert !Topic.create("title" => "a!", "content" => "abc").valid? + assert !Topic.create("title" => "a b", "content" => "abc").valid? + assert !Topic.create("title" => nil, "content" => "def").valid? + + t = Topic.create("title" => "a", "content" => "I know you are but what am I?") + assert t.valid? + t.title = "uhoh" + assert !t.valid? + assert t.errors.on(:title) + assert_equal "is not included in the list", t.errors["title"] + + assert_raise(ArgumentError) { Topic.validates_inclusion_of( :title, :in => nil ) } + assert_raise(ArgumentError) { Topic.validates_inclusion_of( :title, :in => 0) } + + assert_nothing_raised(ArgumentError) { Topic.validates_inclusion_of( :title, :in => "hi!" ) } + assert_nothing_raised(ArgumentError) { Topic.validates_inclusion_of( :title, :in => {} ) } + assert_nothing_raised(ArgumentError) { Topic.validates_inclusion_of( :title, :in => [] ) } + end + + def test_validates_inclusion_of_with_allow_nil + Topic.validates_inclusion_of( :title, :in => %w( a b c d e f g ), :allow_nil=>true ) + + assert !Topic.create("title" => "a!", "content" => "abc").valid? + assert !Topic.create("title" => "", "content" => "abc").valid? + assert Topic.create("title" => nil, "content" => "abc").valid? + end + + def test_numericality_with_getter_method + Developer.validates_numericality_of( :salary ) + developer = Developer.new("name" => "michael", "salary" => nil) + developer.instance_eval("def salary; read_attribute('salary') ? read_attribute('salary') : 100000; end") + assert developer.valid? + end + + def test_validates_length_of_with_allow_nil + Topic.validates_length_of( :title, :is => 5, :allow_nil=>true ) + + assert !Topic.create("title" => "ab").valid? + assert !Topic.create("title" => "").valid? + assert Topic.create("title" => nil).valid? + assert Topic.create("title" => "abcde").valid? + end + + def test_validates_length_of_with_allow_blank + Topic.validates_length_of( :title, :is => 5, :allow_blank=>true ) + + assert !Topic.create("title" => "ab").valid? + assert Topic.create("title" => "").valid? + assert Topic.create("title" => nil).valid? + assert Topic.create("title" => "abcde").valid? + end + + def test_validates_inclusion_of_with_formatted_message + Topic.validates_inclusion_of( :title, :in => %w( a b c d e f g ), :message => "option %s is not in the list" ) + + assert Topic.create("title" => "a", "content" => "abc").valid? + + t = Topic.create("title" => "uhoh", "content" => "abc") + assert !t.valid? + assert t.errors.on(:title) + assert_equal "option uhoh is not in the list", t.errors["title"] + end + + def test_numericality_with_allow_nil_and_getter_method + Developer.validates_numericality_of( :salary, :allow_nil => true) + developer = Developer.new("name" => "michael", "salary" => nil) + developer.instance_eval("def salary; read_attribute('salary') ? read_attribute('salary') : 100000; end") + assert developer.valid? + end + + def test_validates_exclusion_of + Topic.validates_exclusion_of( :title, :in => %w( abe monkey ) ) + + assert Topic.create("title" => "something", "content" => "abc").valid? + assert !Topic.create("title" => "monkey", "content" => "abc").valid? + end + + def test_validates_exclusion_of_with_formatted_message + Topic.validates_exclusion_of( :title, :in => %w( abe monkey ), :message => "option %s is restricted" ) + + assert Topic.create("title" => "something", "content" => "abc") + + t = Topic.create("title" => "monkey") + assert !t.valid? + assert t.errors.on(:title) + assert_equal "option monkey is restricted", t.errors["title"] + end + + def test_validates_length_of_using_minimum + Topic.validates_length_of :title, :minimum => 5 + + t = Topic.create("title" => "valid", "content" => "whatever") + assert t.valid? + + t.title = "not" + assert !t.valid? + assert t.errors.on(:title) + assert_equal "is too short (minimum is 5 characters)", t.errors["title"] + + t.title = "" + assert !t.valid? + assert t.errors.on(:title) + assert_equal "is too short (minimum is 5 characters)", t.errors["title"] + + t.title = nil + assert !t.valid? + assert t.errors.on(:title) + assert_equal "is too short (minimum is 5 characters)", t.errors["title"] + end + + def test_optionally_validates_length_of_using_minimum + Topic.validates_length_of :title, :minimum => 5, :allow_nil => true + + t = Topic.create("title" => "valid", "content" => "whatever") + assert t.valid? + + t.title = nil + assert t.valid? + end + + def test_validates_length_of_using_maximum + Topic.validates_length_of :title, :maximum => 5 + + t = Topic.create("title" => "valid", "content" => "whatever") + assert t.valid? + + t.title = "notvalid" + assert !t.valid? + assert t.errors.on(:title) + assert_equal "is too long (maximum is 5 characters)", t.errors["title"] + + t.title = "" + assert t.valid? + + t.title = nil + assert !t.valid? + end + + def test_optionally_validates_length_of_using_maximum + Topic.validates_length_of :title, :maximum => 5, :allow_nil => true + + t = Topic.create("title" => "valid", "content" => "whatever") + assert t.valid? + + t.title = nil + assert t.valid? + end + + def test_validates_length_of_using_within + Topic.validates_length_of(:title, :content, :within => 3..5) + + t = Topic.new("title" => "a!", "content" => "I'm ooooooooh so very long") + assert !t.valid? + assert_equal "is too short (minimum is 3 characters)", t.errors.on(:title) + assert_equal "is too long (maximum is 5 characters)", t.errors.on(:content) + + t.title = nil + t.content = nil + assert !t.valid? + assert_equal "is too short (minimum is 3 characters)", t.errors.on(:title) + assert_equal "is too short (minimum is 3 characters)", t.errors.on(:content) + + t.title = "abe" + t.content = "mad" + assert t.valid? + end + + def test_optionally_validates_length_of_using_within + Topic.validates_length_of :title, :content, :within => 3..5, :allow_nil => true + + t = Topic.create('title' => 'abc', 'content' => 'abcd') + assert t.valid? + + t.title = nil + assert t.valid? + end + + def test_optionally_validates_length_of_using_within_on_create + Topic.validates_length_of :title, :content, :within => 5..10, :on => :create, :too_long => "my string is too long: %d" + + t = Topic.create("title" => "thisisnotvalid", "content" => "whatever") + assert !t.save + assert t.errors.on(:title) + assert_equal "my string is too long: 10", t.errors[:title] + + t.title = "butthisis" + assert t.save + + t.title = "few" + assert t.save + + t.content = "andthisislong" + assert t.save + + t.content = t.title = "iamfine" + assert t.save + end + + def test_optionally_validates_length_of_using_within_on_update + Topic.validates_length_of :title, :content, :within => 5..10, :on => :update, :too_short => "my string is too short: %d" + + t = Topic.create("title" => "vali", "content" => "whatever") + assert !t.save + assert t.errors.on(:title) + + t.title = "not" + assert !t.save + assert t.errors.on(:title) + assert_equal "my string is too short: 5", t.errors[:title] + + t.title = "valid" + t.content = "andthisistoolong" + assert !t.save + assert t.errors.on(:content) + + t.content = "iamfine" + assert t.save + end + + def test_validates_length_of_using_is + Topic.validates_length_of :title, :is => 5 + + t = Topic.create("title" => "valid", "content" => "whatever") + assert t.valid? + + t.title = "notvalid" + assert !t.valid? + assert t.errors.on(:title) + assert_equal "is the wrong length (should be 5 characters)", t.errors["title"] + + t.title = "" + assert !t.valid? + + t.title = nil + assert !t.valid? + end + + def test_optionally_validates_length_of_using_is + Topic.validates_length_of :title, :is => 5, :allow_nil => true + + t = Topic.create("title" => "valid", "content" => "whatever") + assert t.valid? + + t.title = nil + assert t.valid? + end + + def test_validates_length_of_using_bignum + bigmin = 2 ** 30 + bigmax = 2 ** 32 + bigrange = bigmin...bigmax + assert_nothing_raised do + Topic.validates_length_of :title, :is => bigmin + 5 + Topic.validates_length_of :title, :within => bigrange + Topic.validates_length_of :title, :in => bigrange + Topic.validates_length_of :title, :minimum => bigmin + Topic.validates_length_of :title, :maximum => bigmax + end + end + + def test_validates_length_with_globally_modified_error_message + ActiveRecord::Errors.default_error_messages[:too_short] = 'tu est trops petit hombre %d' + Topic.validates_length_of :title, :minimum => 10 + t = Topic.create(:title => 'too short') + assert !t.valid? + + assert_equal 'tu est trops petit hombre 10', t.errors['title'] + end + + def test_validates_size_of_association + assert_nothing_raised { Topic.validates_size_of :replies, :minimum => 1 } + t = Topic.new('title' => 'noreplies', 'content' => 'whatever') + assert !t.save + assert t.errors.on(:replies) + reply = t.replies.build('title' => 'areply', 'content' => 'whateveragain') + assert t.valid? + end + + def test_validates_length_of_nasty_params + assert_raise(ArgumentError) { Topic.validates_length_of(:title, :minimum=>6, :maximum=>9) } + assert_raise(ArgumentError) { Topic.validates_length_of(:title, :within=>6, :maximum=>9) } + assert_raise(ArgumentError) { Topic.validates_length_of(:title, :within=>6, :minimum=>9) } + assert_raise(ArgumentError) { Topic.validates_length_of(:title, :within=>6, :is=>9) } + assert_raise(ArgumentError) { Topic.validates_length_of(:title, :minimum=>"a") } + assert_raise(ArgumentError) { Topic.validates_length_of(:title, :maximum=>"a") } + assert_raise(ArgumentError) { Topic.validates_length_of(:title, :within=>"a") } + assert_raise(ArgumentError) { Topic.validates_length_of(:title, :is=>"a") } + end + + def test_validates_length_of_custom_errors_for_minimum_with_message + Topic.validates_length_of( :title, :minimum=>5, :message=>"boo %d" ) + t = Topic.create("title" => "uhoh", "content" => "whatever") + assert !t.valid? + assert t.errors.on(:title) + assert_equal "boo 5", t.errors["title"] + end + + def test_validates_length_of_custom_errors_for_minimum_with_too_short + Topic.validates_length_of( :title, :minimum=>5, :too_short=>"hoo %d" ) + t = Topic.create("title" => "uhoh", "content" => "whatever") + assert !t.valid? + assert t.errors.on(:title) + assert_equal "hoo 5", t.errors["title"] + end + + def test_validates_length_of_custom_errors_for_maximum_with_message + Topic.validates_length_of( :title, :maximum=>5, :message=>"boo %d" ) + t = Topic.create("title" => "uhohuhoh", "content" => "whatever") + assert !t.valid? + assert t.errors.on(:title) + assert_equal "boo 5", t.errors["title"] + end + + def test_validates_length_of_custom_errors_for_maximum_with_too_long + Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d" ) + t = Topic.create("title" => "uhohuhoh", "content" => "whatever") + assert !t.valid? + assert t.errors.on(:title) + assert_equal "hoo 5", t.errors["title"] + end + + def test_validates_length_of_custom_errors_for_is_with_message + Topic.validates_length_of( :title, :is=>5, :message=>"boo %d" ) + t = Topic.create("title" => "uhohuhoh", "content" => "whatever") + assert !t.valid? + assert t.errors.on(:title) + assert_equal "boo 5", t.errors["title"] + end + + def test_validates_length_of_custom_errors_for_is_with_wrong_length + Topic.validates_length_of( :title, :is=>5, :wrong_length=>"hoo %d" ) + t = Topic.create("title" => "uhohuhoh", "content" => "whatever") + assert !t.valid? + assert t.errors.on(:title) + assert_equal "hoo 5", t.errors["title"] + end + + def test_validates_length_of_using_minimum_utf8 + with_kcode('UTF8') do + Topic.validates_length_of :title, :minimum => 5 + + t = Topic.create("title" => "一二三四五", "content" => "whatever") + assert t.valid? + + t.title = "一二三四" + assert !t.valid? + assert t.errors.on(:title) + assert_equal "is too short (minimum is 5 characters)", t.errors["title"] + end + end + + def test_validates_length_of_using_maximum_utf8 + with_kcode('UTF8') do + Topic.validates_length_of :title, :maximum => 5 + + t = Topic.create("title" => "一二三四五", "content" => "whatever") + assert t.valid? + + t.title = "一二34五六" + assert !t.valid? + assert t.errors.on(:title) + assert_equal "is too long (maximum is 5 characters)", t.errors["title"] + end + end + + def test_validates_length_of_using_within_utf8 + with_kcode('UTF8') do + Topic.validates_length_of(:title, :content, :within => 3..5) + + t = Topic.new("title" => "一二", "content" => "12三四五六七") + assert !t.valid? + assert_equal "is too short (minimum is 3 characters)", t.errors.on(:title) + assert_equal "is too long (maximum is 5 characters)", t.errors.on(:content) + t.title = "一二三" + t.content = "12三" + assert t.valid? + end + end + + def test_optionally_validates_length_of_using_within_utf8 + with_kcode('UTF8') do + Topic.validates_length_of :title, :content, :within => 3..5, :allow_nil => true + + t = Topic.create('title' => '一二三', 'content' => '一二三四五') + assert t.valid? + + t.title = nil + assert t.valid? + end + end + + def test_optionally_validates_length_of_using_within_on_create_utf8 + with_kcode('UTF8') do + Topic.validates_length_of :title, :content, :within => 5..10, :on => :create, :too_long => "長すぎます: %d" + + t = Topic.create("title" => "一二三四五六七八九十A", "content" => "whatever") + assert !t.save + assert t.errors.on(:title) + assert_equal "長すぎます: 10", t.errors[:title] + + t.title = "一二三四五六七八九" + assert t.save + + t.title = "一二3" + assert t.save + + t.content = "一二三四五六七八九十" + assert t.save + + t.content = t.title = "一二三四五六" + assert t.save + end + end + + def test_optionally_validates_length_of_using_within_on_update_utf8 + with_kcode('UTF8') do + Topic.validates_length_of :title, :content, :within => 5..10, :on => :update, :too_short => "短すぎます: %d" + + t = Topic.create("title" => "一二三4", "content" => "whatever") + assert !t.save + assert t.errors.on(:title) + + t.title = "1二三4" + assert !t.save + assert t.errors.on(:title) + assert_equal "短すぎます: 5", t.errors[:title] + + t.title = "valid" + t.content = "一二三四五六七八九十A" + assert !t.save + assert t.errors.on(:content) + + t.content = "一二345" + assert t.save + end + end + + def test_validates_length_of_using_is_utf8 + with_kcode('UTF8') do + Topic.validates_length_of :title, :is => 5 + + t = Topic.create("title" => "一二345", "content" => "whatever") + assert t.valid? + + t.title = "一二345六" + assert !t.valid? + assert t.errors.on(:title) + assert_equal "is the wrong length (should be 5 characters)", t.errors["title"] + end + end + + def test_validates_size_of_association_utf8 + with_kcode('UTF8') do + assert_nothing_raised { Topic.validates_size_of :replies, :minimum => 1 } + t = Topic.new('title' => 'あいうえお', 'content' => 'かきくけこ') + assert !t.save + assert t.errors.on(:replies) + t.replies.build('title' => 'あいうえお', 'content' => 'かきくけこ') + assert t.valid? + end + end + + def test_validates_associated_many + Topic.validates_associated( :replies ) + t = Topic.create("title" => "uhohuhoh", "content" => "whatever") + t.replies << [r = Reply.new("title" => "A reply"), r2 = Reply.new("title" => "Another reply", "content" => "non-empty"), r3 = Reply.new("title" => "Yet another reply"), r4 = Reply.new("title" => "The last reply", "content" => "non-empty")] + assert !t.valid? + assert t.errors.on(:replies) + assert_equal 1, r.errors.count # make sure all associated objects have been validated + assert_equal 0, r2.errors.count + assert_equal 1, r3.errors.count + assert_equal 0, r4.errors.count + r.content = r3.content = "non-empty" + assert t.valid? + end + + def test_validates_associated_one + Reply.validates_associated( :topic ) + Topic.validates_presence_of( :content ) + r = Reply.new("title" => "A reply", "content" => "with content!") + r.topic = Topic.create("title" => "uhohuhoh") + assert !r.valid? + assert r.errors.on(:topic) + r.topic.content = "non-empty" + assert r.valid? + end + + def test_validate_block + Topic.validate { |topic| topic.errors.add("title", "will never be valid") } + t = Topic.create("title" => "Title", "content" => "whatever") + assert !t.valid? + assert t.errors.on(:title) + assert_equal "will never be valid", t.errors["title"] + end + + def test_invalid_validator + Topic.validate 3 + assert_raise(ActiveRecord::ActiveRecordError) { t = Topic.create } + end + + def test_throw_away_typing + d = Developer.new("name" => "David", "salary" => "100,000") + assert !d.valid? + assert_equal 100, d.salary + assert_equal "100,000", d.salary_before_type_cast + end + + def test_validates_acceptance_of_with_custom_error_using_quotes + Developer.validates_acceptance_of :salary, :message=> "This string contains 'single' and \"double\" quotes" + d = Developer.new + d.salary = "0" + assert !d.valid? + assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:salary).last + end + + def test_validates_confirmation_of_with_custom_error_using_quotes + Developer.validates_confirmation_of :name, :message=> "confirm 'single' and \"double\" quotes" + d = Developer.new + d.name = "John" + d.name_confirmation = "Johnny" + assert !d.valid? + assert_equal "confirm 'single' and \"double\" quotes", d.errors.on(:name) + end + + def test_validates_format_of_with_custom_error_using_quotes + Developer.validates_format_of :name, :with => /^(A-Z*)$/, :message=> "format 'single' and \"double\" quotes" + d = Developer.new + d.name = d.name_confirmation = "John 32" + assert !d.valid? + assert_equal "format 'single' and \"double\" quotes", d.errors.on(:name) + end + + def test_validates_inclusion_of_with_custom_error_using_quotes + Developer.validates_inclusion_of :salary, :in => 1000..80000, :message=> "This string contains 'single' and \"double\" quotes" + d = Developer.new + d.salary = "90,000" + assert !d.valid? + assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:salary).last + end + + def test_validates_length_of_with_custom_too_long_using_quotes + Developer.validates_length_of :name, :maximum => 4, :too_long=> "This string contains 'single' and \"double\" quotes" + d = Developer.new + d.name = "Jeffrey" + assert !d.valid? + assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:name).last + end + + def test_validates_length_of_with_custom_too_short_using_quotes + Developer.validates_length_of :name, :minimum => 4, :too_short=> "This string contains 'single' and \"double\" quotes" + d = Developer.new + d.name = "Joe" + assert !d.valid? + assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:name).last + end + + def test_validates_length_of_with_custom_message_using_quotes + Developer.validates_length_of :name, :minimum => 4, :message=> "This string contains 'single' and \"double\" quotes" + d = Developer.new + d.name = "Joe" + assert !d.valid? + assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:name).last + end + + def test_validates_presence_of_with_custom_message_using_quotes + Developer.validates_presence_of :non_existent, :message=> "This string contains 'single' and \"double\" quotes" + d = Developer.new + d.name = "Joe" + assert !d.valid? + assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:non_existent) + end + + def test_validates_uniqueness_of_with_custom_message_using_quotes + Developer.validates_uniqueness_of :name, :message=> "This string contains 'single' and \"double\" quotes" + d = Developer.new + d.name = "David" + assert !d.valid? + assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:name).last + 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 + r = Reply.create("title" => "A reply", "content" => "with content!") + r.topic = Topic.create("title" => "uhohuhoh") + assert !r.valid? + assert_equal "This string contains 'single' and \"double\" quotes", r.errors.on(:topic).last + end + + def test_if_validation_using_method_true + # When the method returns true + Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d", :if => :condition_is_true ) + t = Topic.create("title" => "uhohuhoh", "content" => "whatever") + assert !t.valid? + assert t.errors.on(:title) + assert_equal "hoo 5", t.errors["title"] + end + + def test_unless_validation_using_method_true + # When the method returns true + Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d", :unless => :condition_is_true ) + t = Topic.create("title" => "uhohuhoh", "content" => "whatever") + assert t.valid? + assert !t.errors.on(:title) + end + + def test_if_validation_using_method_false + # When the method returns false + Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d", :if => :condition_is_true_but_its_not ) + t = Topic.create("title" => "uhohuhoh", "content" => "whatever") + assert t.valid? + assert !t.errors.on(:title) + end + + def test_unless_validation_using_method_false + # When the method returns false + Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d", :unless => :condition_is_true_but_its_not ) + t = Topic.create("title" => "uhohuhoh", "content" => "whatever") + assert !t.valid? + assert t.errors.on(:title) + assert_equal "hoo 5", t.errors["title"] + end + + def test_if_validation_using_string_true + # When the evaluated string returns true + Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d", :if => "a = 1; a == 1" ) + t = Topic.create("title" => "uhohuhoh", "content" => "whatever") + assert !t.valid? + assert t.errors.on(:title) + assert_equal "hoo 5", t.errors["title"] + end + + def test_unless_validation_using_string_true + # When the evaluated string returns true + Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d", :unless => "a = 1; a == 1" ) + t = Topic.create("title" => "uhohuhoh", "content" => "whatever") + assert t.valid? + assert !t.errors.on(:title) + end + + def test_if_validation_using_string_false + # When the evaluated string returns false + Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d", :if => "false") + t = Topic.create("title" => "uhohuhoh", "content" => "whatever") + assert t.valid? + assert !t.errors.on(:title) + end + + def test_unless_validation_using_string_false + # When the evaluated string returns false + Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d", :unless => "false") + t = Topic.create("title" => "uhohuhoh", "content" => "whatever") + assert !t.valid? + assert t.errors.on(:title) + assert_equal "hoo 5", t.errors["title"] + end + + def test_if_validation_using_block_true + # When the block returns true + Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d", + :if => Proc.new { |r| r.content.size > 4 } ) + t = Topic.create("title" => "uhohuhoh", "content" => "whatever") + assert !t.valid? + assert t.errors.on(:title) + assert_equal "hoo 5", t.errors["title"] + end + + def test_unless_validation_using_block_true + # When the block returns true + Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d", + :unless => Proc.new { |r| r.content.size > 4 } ) + t = Topic.create("title" => "uhohuhoh", "content" => "whatever") + assert t.valid? + assert !t.errors.on(:title) + end + + def test_if_validation_using_block_false + # When the block returns false + Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d", + :if => Proc.new { |r| r.title != "uhohuhoh"} ) + t = Topic.create("title" => "uhohuhoh", "content" => "whatever") + assert t.valid? + assert !t.errors.on(:title) + end + + def test_unless_validation_using_block_false + # When the block returns false + Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d", + :unless => Proc.new { |r| r.title != "uhohuhoh"} ) + t = Topic.create("title" => "uhohuhoh", "content" => "whatever") + assert !t.valid? + assert t.errors.on(:title) + assert_equal "hoo 5", t.errors["title"] + end + + def test_validates_associated_missing + Reply.validates_presence_of(:topic) + r = Reply.create("title" => "A reply", "content" => "with content!") + assert !r.valid? + assert r.errors.on(:topic) + + r.topic = Topic.find :first + assert r.valid? + end + + def test_errors_to_xml + r = Reply.new :title => "Wrong Create" + assert !r.valid? + xml = r.errors.to_xml(:skip_instruct => true) + assert_equal "", xml.first(8) + assert xml.include?("Title is Wrong Create") + assert xml.include?("Content Empty") + end + + def test_validation_order + Topic.validates_presence_of :title + Topic.validates_length_of :title, :minimum => 2 + + t = Topic.new("title" => "") + assert !t.valid? + assert_equal "can't be blank", t.errors.on("title").first + end + + # previous implementation of validates_presence_of eval'd the + # string with the wrong binding, this regression test is to + # ensure that it works correctly + def test_validation_with_if_as_string + Topic.validates_presence_of(:title) + Topic.validates_presence_of(:author_name, :if => "title.to_s.match('important')") + + t = Topic.new + assert !t.valid?, "A topic without a title should not be valid" + assert !t.errors.invalid?("author_name"), "A topic without an 'important' title should not require an author" + + t.title = "Just a title" + assert t.valid?, "A topic with a basic title should be valid" + + t.title = "A very important title" + assert !t.valid?, "A topic with an important title, but without an author, should not be valid" + assert t.errors.invalid?("author_name"), "A topic with an 'important' title should require an author" + + t.author_name = "Hubert J. Farnsworth" + assert t.valid?, "A topic with an important title and author should be valid" + end + + private + def with_kcode(kcode) + if RUBY_VERSION < '1.9' + orig_kcode, $KCODE = $KCODE, kcode + begin + yield + ensure + $KCODE = orig_kcode + end + else + yield + end + end +end + + +class ValidatesNumericalityTest < ActiveSupport::TestCase + NIL = [nil] + BLANK = ["", " ", " \t \r \n"] + BIGDECIMAL_STRINGS = %w(12345678901234567890.1234567890) # 30 significent digits + FLOAT_STRINGS = %w(0.0 +0.0 -0.0 10.0 10.5 -10.5 -0.0001 -090.1 90.1e1 -90.1e5 -90.1e-5 90e-5) + INTEGER_STRINGS = %w(0 +0 -0 10 +10 -10 0090 -090) + FLOATS = [0.0, 10.0, 10.5, -10.5, -0.0001] + FLOAT_STRINGS + INTEGERS = [0, 10, -10] + INTEGER_STRINGS + BIGDECIMAL = BIGDECIMAL_STRINGS.collect! { |bd| BigDecimal.new(bd) } + JUNK = ["not a number", "42 not a number", "0xdeadbeef", "00-1", "--3", "+-3", "+3-1", "-+019.0", "12.12.13.12", "123\nnot a number"] + + def setup + Topic.write_inheritable_attribute(:validate, nil) + Topic.write_inheritable_attribute(:validate_on_create, nil) + Topic.write_inheritable_attribute(:validate_on_update, nil) + end + + def test_default_validates_numericality_of + Topic.validates_numericality_of :approved + + invalid!(NIL + BLANK + JUNK) + valid!(FLOATS + INTEGERS + BIGDECIMAL) + end + + def test_validates_numericality_of_with_nil_allowed + Topic.validates_numericality_of :approved, :allow_nil => true + + invalid!(BLANK + JUNK) + valid!(NIL + FLOATS + INTEGERS + BIGDECIMAL) + end + + def test_validates_numericality_of_with_integer_only + Topic.validates_numericality_of :approved, :only_integer => true + + invalid!(NIL + BLANK + JUNK + FLOATS + BIGDECIMAL) + valid!(INTEGERS) + end + + def test_validates_numericality_of_with_integer_only_and_nil_allowed + Topic.validates_numericality_of :approved, :only_integer => true, :allow_nil => true + + invalid!(BLANK + JUNK + FLOATS + BIGDECIMAL) + valid!(NIL + INTEGERS) + end + + def test_validates_numericality_with_greater_than + Topic.validates_numericality_of :approved, :greater_than => 10 + + invalid!([-10, 10], 'must be greater than 10') + valid!([11]) + end + + def test_validates_numericality_with_greater_than_or_equal + Topic.validates_numericality_of :approved, :greater_than_or_equal_to => 10 + + invalid!([-9, 9], 'must be greater than or equal to 10') + valid!([10]) + end + + def test_validates_numericality_with_equal_to + Topic.validates_numericality_of :approved, :equal_to => 10 + + invalid!([-10, 11], 'must be equal to 10') + valid!([10]) + end + + def test_validates_numericality_with_less_than + Topic.validates_numericality_of :approved, :less_than => 10 + + invalid!([10], 'must be less than 10') + valid!([-9, 9]) + end + + def test_validates_numericality_with_less_than_or_equal_to + Topic.validates_numericality_of :approved, :less_than_or_equal_to => 10 + + invalid!([11], 'must be less than or equal to 10') + valid!([-10, 10]) + end + + def test_validates_numericality_with_odd + Topic.validates_numericality_of :approved, :odd => true + + invalid!([-2, 2], 'must be odd') + valid!([-1, 1]) + end + + def test_validates_numericality_with_even + Topic.validates_numericality_of :approved, :even => true + + invalid!([-1, 1], 'must be even') + valid!([-2, 2]) + end + + def test_validates_numericality_with_greater_than_less_than_and_even + Topic.validates_numericality_of :approved, :greater_than => 1, :less_than => 4, :even => true + + invalid!([1, 3, 4]) + valid!([2]) + end + + private + def invalid!(values, error=nil) + with_each_topic_approved_value(values) do |topic, value| + assert !topic.valid?, "#{value.inspect} not rejected as a number" + assert topic.errors.on(:approved) + assert_equal error, topic.errors.on(:approved) if error + end + end + + def valid!(values) + with_each_topic_approved_value(values) do |topic, value| + assert topic.valid?, "#{value.inspect} not accepted as a number" + end + end + + def with_each_topic_approved_value(values) + topic = Topic.new("title" => "numeric test", "content" => "whatever") + values.each do |value| + topic.approved = value + yield topic, value + end + end +end diff --git a/activerecord/test/cases/xml_serialization_test.rb b/activerecord/test/cases/xml_serialization_test.rb new file mode 100644 index 0000000000..2db75fad46 --- /dev/null +++ b/activerecord/test/cases/xml_serialization_test.rb @@ -0,0 +1,202 @@ +require 'abstract_unit' +require 'fixtures/contact' +require 'fixtures/post' +require 'fixtures/author' +require 'fixtures/tagging' +require 'fixtures/comment' + +class XmlSerializationTest < ActiveSupport::TestCase + def test_should_serialize_default_root + @xml = Contact.new.to_xml + assert_match %r{^}, @xml + assert_match %r{$}, @xml + end + + def test_should_serialize_default_root_with_namespace + @xml = Contact.new.to_xml :namespace=>"http://xml.rubyonrails.org/contact" + assert_match %r{^}, @xml + assert_match %r{$}, @xml + end + + def test_should_serialize_custom_root + @xml = Contact.new.to_xml :root => 'xml_contact' + assert_match %r{^}, @xml + assert_match %r{$}, @xml + end + + def test_should_allow_undasherized_tags + @xml = Contact.new.to_xml :root => 'xml_contact', :dasherize => false + assert_match %r{^}, @xml + assert_match %r{$}, @xml + assert_match %r{David}, @xml + end +end + +class DefaultXmlSerializationTest < ActiveSupport::TestCase + def setup + @xml = Contact.new(:name => 'aaron stack', :age => 25, :avatar => 'binarydata', :created_at => Time.utc(2006, 8, 1), :awesome => false, :preferences => { :gem => 'ruby' }).to_xml + end + + def test_should_serialize_string + assert_match %r{aaron stack}, @xml + end + + def test_should_serialize_integer + assert_match %r{25}, @xml + end + + def test_should_serialize_binary + assert_match %r{YmluYXJ5ZGF0YQ==\n}, @xml + assert_match %r{2006-08-01T00:00:00Z}, @xml + end + + def test_should_serialize_boolean + assert_match %r{false}, @xml + end + + def test_should_serialize_yaml + assert_match %r{--- \n:gem: ruby\n}, @xml + end +end + +class NilXmlSerializationTest < ActiveSupport::TestCase + def setup + @xml = Contact.new.to_xml(:root => 'xml_contact') + end + + def test_should_serialize_string + assert_match %r{}, @xml + end + + def test_should_serialize_integer + assert %r{}.match(@xml) + attributes = $1 + assert_match %r{nil="true"}, attributes + assert_match %r{type="integer"}, attributes + end + + def test_should_serialize_binary + assert %r{}.match(@xml) + attributes = $1 + assert_match %r{type="binary"}, attributes + assert_match %r{encoding="base64"}, attributes + assert_match %r{nil="true"}, attributes + end + + def test_should_serialize_datetime + assert %r{}.match(@xml) + attributes = $1 + assert_match %r{nil="true"}, attributes + assert_match %r{type="datetime"}, attributes + end + + def test_should_serialize_boolean + assert %r{}.match(@xml) + attributes = $1 + assert_match %r{type="boolean"}, attributes + assert_match %r{nil="true"}, attributes + end + + def test_should_serialize_yaml + assert %r{}.match(@xml) + attributes = $1 + assert_match %r{type="yaml"}, attributes + assert_match %r{nil="true"}, attributes + end +end + +class DatabaseConnectedXmlSerializationTest < ActiveSupport::TestCase + fixtures :authors, :posts + # to_xml used to mess with the hash the user provided which + # caused the builder to be reused. This meant the document kept + # getting appended to. + def test_passing_hash_shouldnt_reuse_builder + options = {:include=>:posts} + david = authors(:david) + first_xml_size = david.to_xml(options).size + second_xml_size = david.to_xml(options).size + assert_equal first_xml_size, second_xml_size + end + + def test_include_uses_association_name + xml = authors(:david).to_xml :include=>:hello_posts, :indent => 0 + assert_match %r{}, xml + assert_match %r{}, xml + assert_match %r{}, xml + end + + def test_methods_are_called_on_object + xml = authors(:david).to_xml :methods => :label, :indent => 0 + assert_match %r{}, xml + end + + def test_should_not_call_methods_on_associations_that_dont_respond + xml = authors(:david).to_xml :include=>:hello_posts, :methods => :label, :indent => 2 + assert !authors(:david).hello_posts.first.respond_to?(:label) + assert_match %r{^ }, xml + assert_no_match %r{^