aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/test/cases
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/test/cases')
-rw-r--r--activerecord/test/cases/adapter_test.rb214
-rw-r--r--activerecord/test/cases/adapters/mysql2/active_schema_test.rb34
-rw-r--r--activerecord/test/cases/adapters/mysql2/auto_increment_test.rb34
-rw-r--r--activerecord/test/cases/adapters/mysql2/bind_parameter_test.rb2
-rw-r--r--activerecord/test/cases/adapters/mysql2/boolean_test.rb6
-rw-r--r--activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb6
-rw-r--r--activerecord/test/cases/adapters/mysql2/charset_collation_test.rb6
-rw-r--r--activerecord/test/cases/adapters/mysql2/connection_test.rb36
-rw-r--r--activerecord/test/cases/adapters/mysql2/datetime_precision_quoting_test.rb9
-rw-r--r--activerecord/test/cases/adapters/mysql2/enum_test.rb6
-rw-r--r--activerecord/test/cases/adapters/mysql2/explain_test.rb24
-rw-r--r--activerecord/test/cases/adapters/mysql2/json_test.rb193
-rw-r--r--activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb23
-rw-r--r--activerecord/test/cases/adapters/mysql2/reserved_word_test.rb151
-rw-r--r--activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb10
-rw-r--r--activerecord/test/cases/adapters/mysql2/schema_test.rb4
-rw-r--r--activerecord/test/cases/adapters/mysql2/sp_test.rb4
-rw-r--r--activerecord/test/cases/adapters/mysql2/sql_types_test.rb2
-rw-r--r--activerecord/test/cases/adapters/mysql2/table_options_test.rb85
-rw-r--r--activerecord/test/cases/adapters/mysql2/transaction_test.rb92
-rw-r--r--activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb12
-rw-r--r--activerecord/test/cases/adapters/mysql2/virtual_column_test.rb8
-rw-r--r--activerecord/test/cases/adapters/postgresql/active_schema_test.rb13
-rw-r--r--activerecord/test/cases/adapters/postgresql/array_test.rb59
-rw-r--r--activerecord/test/cases/adapters/postgresql/bit_string_test.rb10
-rw-r--r--activerecord/test/cases/adapters/postgresql/bytea_test.rb13
-rw-r--r--activerecord/test/cases/adapters/postgresql/case_insensitive_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/change_schema_test.rb4
-rw-r--r--activerecord/test/cases/adapters/postgresql/cidr_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/citext_test.rb112
-rw-r--r--activerecord/test/cases/adapters/postgresql/collation_test.rb6
-rw-r--r--activerecord/test/cases/adapters/postgresql/composite_test.rb12
-rw-r--r--activerecord/test/cases/adapters/postgresql/connection_test.rb32
-rw-r--r--activerecord/test/cases/adapters/postgresql/datatype_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/date_test.rb42
-rw-r--r--activerecord/test/cases/adapters/postgresql/domain_test.rb8
-rw-r--r--activerecord/test/cases/adapters/postgresql/enum_test.rb8
-rw-r--r--activerecord/test/cases/adapters/postgresql/explain_test.rb18
-rw-r--r--activerecord/test/cases/adapters/postgresql/extension_migration_test.rb6
-rw-r--r--activerecord/test/cases/adapters/postgresql/foreign_table_test.rb109
-rw-r--r--activerecord/test/cases/adapters/postgresql/full_text_test.rb6
-rw-r--r--activerecord/test/cases/adapters/postgresql/geometric_test.rb16
-rw-r--r--activerecord/test/cases/adapters/postgresql/hstore_test.rb596
-rw-r--r--activerecord/test/cases/adapters/postgresql/infinity_test.rb41
-rw-r--r--activerecord/test/cases/adapters/postgresql/integer_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/json_test.rb215
-rw-r--r--activerecord/test/cases/adapters/postgresql/ltree_test.rb6
-rw-r--r--activerecord/test/cases/adapters/postgresql/money_test.rb27
-rw-r--r--activerecord/test/cases/adapters/postgresql/network_test.rb14
-rw-r--r--activerecord/test/cases/adapters/postgresql/numbers_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/partitions_test.rb22
-rw-r--r--activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb67
-rw-r--r--activerecord/test/cases/adapters/postgresql/prepared_statements_disabled_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/quoting_test.rb6
-rw-r--r--activerecord/test/cases/adapters/postgresql/range_test.rb85
-rw-r--r--activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/rename_table_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb13
-rw-r--r--activerecord/test/cases/adapters/postgresql/schema_test.rb73
-rw-r--r--activerecord/test/cases/adapters/postgresql/serial_test.rb78
-rw-r--r--activerecord/test/cases/adapters/postgresql/statement_pool_test.rb12
-rw-r--r--activerecord/test/cases/adapters/postgresql/timestamp_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/transaction_test.rb92
-rw-r--r--activerecord/test/cases/adapters/postgresql/type_lookup_test.rb16
-rw-r--r--activerecord/test/cases/adapters/postgresql/utils_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/uuid_test.rb238
-rw-r--r--activerecord/test/cases/adapters/postgresql/xml_test.rb2
-rw-r--r--activerecord/test/cases/adapters/sqlite3/collation_test.rb6
-rw-r--r--activerecord/test/cases/adapters/sqlite3/copy_table_test.rb2
-rw-r--r--activerecord/test/cases/adapters/sqlite3/explain_test.rb24
-rw-r--r--activerecord/test/cases/adapters/sqlite3/json_test.rb29
-rw-r--r--activerecord/test/cases/adapters/sqlite3/quoting_test.rb50
-rw-r--r--activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb189
-rw-r--r--activerecord/test/cases/adapters/sqlite3/sqlite3_create_folder_test.rb2
-rw-r--r--activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb4
-rw-r--r--activerecord/test/cases/aggregations_test.rb4
-rw-r--r--activerecord/test/cases/ar_schema_test.rb18
-rw-r--r--activerecord/test/cases/arel/attributes/attribute_test.rb1015
-rw-r--r--activerecord/test/cases/arel/attributes/math_test.rb83
-rw-r--r--activerecord/test/cases/arel/attributes_test.rb68
-rw-r--r--activerecord/test/cases/arel/collectors/bind_test.rb40
-rw-r--r--activerecord/test/cases/arel/collectors/composite_test.rb47
-rw-r--r--activerecord/test/cases/arel/collectors/sql_string_test.rb47
-rw-r--r--activerecord/test/cases/arel/collectors/substitute_bind_collector_test.rb48
-rw-r--r--activerecord/test/cases/arel/crud_test.rb65
-rw-r--r--activerecord/test/cases/arel/delete_manager_test.rb52
-rw-r--r--activerecord/test/cases/arel/factory_methods_test.rb46
-rw-r--r--activerecord/test/cases/arel/helper.rb45
-rw-r--r--activerecord/test/cases/arel/insert_manager_test.rb242
-rw-r--r--activerecord/test/cases/arel/nodes/and_test.rb21
-rw-r--r--activerecord/test/cases/arel/nodes/as_test.rb36
-rw-r--r--activerecord/test/cases/arel/nodes/ascending_test.rb46
-rw-r--r--activerecord/test/cases/arel/nodes/bin_test.rb35
-rw-r--r--activerecord/test/cases/arel/nodes/binary_test.rb29
-rw-r--r--activerecord/test/cases/arel/nodes/bind_param_test.rb22
-rw-r--r--activerecord/test/cases/arel/nodes/case_test.rb86
-rw-r--r--activerecord/test/cases/arel/nodes/casted_test.rb18
-rw-r--r--activerecord/test/cases/arel/nodes/count_test.rb35
-rw-r--r--activerecord/test/cases/arel/nodes/delete_statement_test.rb36
-rw-r--r--activerecord/test/cases/arel/nodes/descending_test.rb46
-rw-r--r--activerecord/test/cases/arel/nodes/distinct_test.rb21
-rw-r--r--activerecord/test/cases/arel/nodes/equality_test.rb86
-rw-r--r--activerecord/test/cases/arel/nodes/extract_test.rb43
-rw-r--r--activerecord/test/cases/arel/nodes/false_test.rb21
-rw-r--r--activerecord/test/cases/arel/nodes/grouping_test.rb26
-rw-r--r--activerecord/test/cases/arel/nodes/infix_operation_test.rb42
-rw-r--r--activerecord/test/cases/arel/nodes/insert_statement_test.rb44
-rw-r--r--activerecord/test/cases/arel/nodes/named_function_test.rb48
-rw-r--r--activerecord/test/cases/arel/nodes/node_test.rb41
-rw-r--r--activerecord/test/cases/arel/nodes/not_test.rb31
-rw-r--r--activerecord/test/cases/arel/nodes/or_test.rb36
-rw-r--r--activerecord/test/cases/arel/nodes/over_test.rb69
-rw-r--r--activerecord/test/cases/arel/nodes/select_core_test.rb71
-rw-r--r--activerecord/test/cases/arel/nodes/select_statement_test.rb51
-rw-r--r--activerecord/test/cases/arel/nodes/sql_literal_test.rb75
-rw-r--r--activerecord/test/cases/arel/nodes/sum_test.rb35
-rw-r--r--activerecord/test/cases/arel/nodes/table_alias_test.rb29
-rw-r--r--activerecord/test/cases/arel/nodes/true_test.rb21
-rw-r--r--activerecord/test/cases/arel/nodes/unary_operation_test.rb41
-rw-r--r--activerecord/test/cases/arel/nodes/update_statement_test.rb60
-rw-r--r--activerecord/test/cases/arel/nodes/window_test.rb81
-rw-r--r--activerecord/test/cases/arel/nodes_test.rb34
-rw-r--r--activerecord/test/cases/arel/select_manager_test.rb1225
-rw-r--r--activerecord/test/cases/arel/support/fake_record.rb129
-rw-r--r--activerecord/test/cases/arel/table_test.rb216
-rw-r--r--activerecord/test/cases/arel/update_manager_test.rb126
-rw-r--r--activerecord/test/cases/arel/visitors/depth_first_test.rb271
-rw-r--r--activerecord/test/cases/arel/visitors/dispatch_contamination_test.rb72
-rw-r--r--activerecord/test/cases/arel/visitors/dot_test.rb84
-rw-r--r--activerecord/test/cases/arel/visitors/ibm_db_test.rb34
-rw-r--r--activerecord/test/cases/arel/visitors/informix_test.rb59
-rw-r--r--activerecord/test/cases/arel/visitors/mssql_test.rb99
-rw-r--r--activerecord/test/cases/arel/visitors/mysql_test.rb80
-rw-r--r--activerecord/test/cases/arel/visitors/oracle12_test.rb61
-rw-r--r--activerecord/test/cases/arel/visitors/oracle_test.rb197
-rw-r--r--activerecord/test/cases/arel/visitors/postgres_test.rb281
-rw-r--r--activerecord/test/cases/arel/visitors/sqlite_test.rb32
-rw-r--r--activerecord/test/cases/arel/visitors/to_sql_test.rb654
-rw-r--r--activerecord/test/cases/associations/association_scope_test.rb16
-rw-r--r--activerecord/test/cases/associations/belongs_to_associations_test.rb223
-rw-r--r--activerecord/test/cases/associations/bidirectional_destroy_dependencies_test.rb2
-rw-r--r--activerecord/test/cases/associations/callbacks_test.rb22
-rw-r--r--activerecord/test/cases/associations/cascaded_eager_loading_test.rb14
-rw-r--r--activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb75
-rw-r--r--activerecord/test/cases/associations/eager_load_nested_include_test.rb2
-rw-r--r--activerecord/test/cases/associations/eager_singularization_test.rb2
-rw-r--r--activerecord/test/cases/associations/eager_test.rb185
-rw-r--r--activerecord/test/cases/associations/extension_test.rb13
-rw-r--r--activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb162
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb557
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb273
-rw-r--r--activerecord/test/cases/associations/has_one_associations_test.rb111
-rw-r--r--activerecord/test/cases/associations/has_one_through_associations_test.rb52
-rw-r--r--activerecord/test/cases/associations/inner_join_association_test.rb40
-rw-r--r--activerecord/test/cases/associations/inverse_associations_test.rb158
-rw-r--r--activerecord/test/cases/associations/join_model_test.rb64
-rw-r--r--activerecord/test/cases/associations/left_outer_join_association_test.rb15
-rw-r--r--activerecord/test/cases/associations/nested_through_associations_test.rb71
-rw-r--r--activerecord/test/cases/associations/required_test.rb44
-rw-r--r--activerecord/test/cases/associations_test.rb64
-rw-r--r--activerecord/test/cases/attribute_decorators_test.rb2
-rw-r--r--activerecord/test/cases/attribute_methods/read_test.rb7
-rw-r--r--activerecord/test/cases/attribute_methods_test.rb115
-rw-r--r--activerecord/test/cases/attribute_set_test.rb253
-rw-r--r--activerecord/test/cases/attribute_test.rb253
-rw-r--r--activerecord/test/cases/attributes_test.rb12
-rw-r--r--activerecord/test/cases/autosave_association_test.rb438
-rw-r--r--activerecord/test/cases/base_test.rb305
-rw-r--r--activerecord/test/cases/batches_test.rb132
-rw-r--r--activerecord/test/cases/binary_test.rb4
-rw-r--r--activerecord/test/cases/bind_parameter_test.rb122
-rw-r--r--activerecord/test/cases/boolean_test.rb43
-rw-r--r--activerecord/test/cases/cache_key_test.rb34
-rw-r--r--activerecord/test/cases/calculations_test.rb193
-rw-r--r--activerecord/test/cases/callbacks_test.rb65
-rw-r--r--activerecord/test/cases/clone_test.rb8
-rw-r--r--activerecord/test/cases/coders/json_test.rb2
-rw-r--r--activerecord/test/cases/coders/yaml_column_test.rb1
-rw-r--r--activerecord/test/cases/collection_cache_key_test.rb94
-rw-r--r--activerecord/test/cases/column_alias_test.rb2
-rw-r--r--activerecord/test/cases/column_definition_test.rb4
-rw-r--r--activerecord/test/cases/comment_test.rb44
-rw-r--r--activerecord/test/cases/connection_adapters/adapter_leasing_test.rb6
-rw-r--r--activerecord/test/cases/connection_adapters/connection_handler_test.rb160
-rw-r--r--activerecord/test/cases/connection_adapters/connection_specification_test.rb2
-rw-r--r--activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb9
-rw-r--r--activerecord/test/cases/connection_adapters/mysql_type_lookup_test.rb11
-rw-r--r--activerecord/test/cases/connection_adapters/schema_cache_test.rb10
-rw-r--r--activerecord/test/cases/connection_adapters/type_lookup_test.rb12
-rw-r--r--activerecord/test/cases/connection_management_test.rb16
-rw-r--r--activerecord/test/cases/connection_pool_test.rb184
-rw-r--r--activerecord/test/cases/connection_specification/resolver_test.rb12
-rw-r--r--activerecord/test/cases/core_test.rb91
-rw-r--r--activerecord/test/cases/counter_cache_test.rb14
-rw-r--r--activerecord/test/cases/custom_locking_test.rb2
-rw-r--r--activerecord/test/cases/database_statements_test.rb2
-rw-r--r--activerecord/test/cases/date_test.rb2
-rw-r--r--activerecord/test/cases/date_time_precision_test.rb20
-rw-r--r--activerecord/test/cases/date_time_test.rb17
-rw-r--r--activerecord/test/cases/defaults_test.rb45
-rw-r--r--activerecord/test/cases/dirty_test.rb310
-rw-r--r--activerecord/test/cases/disconnected_test.rb2
-rw-r--r--activerecord/test/cases/dup_test.rb42
-rw-r--r--activerecord/test/cases/enum_test.rb181
-rw-r--r--activerecord/test/cases/errors_test.rb4
-rw-r--r--activerecord/test/cases/explain_subscriber_test.rb14
-rw-r--r--activerecord/test/cases/explain_test.rb5
-rw-r--r--activerecord/test/cases/filter_attributes_test.rb80
-rw-r--r--activerecord/test/cases/finder_respond_to_test.rb14
-rw-r--r--activerecord/test/cases/finder_test.rb234
-rw-r--r--activerecord/test/cases/fixture_set/file_test.rb2
-rw-r--r--activerecord/test/cases/fixtures_test.rb368
-rw-r--r--activerecord/test/cases/forbidden_attributes_protection_test.rb2
-rw-r--r--activerecord/test/cases/habtm_destroy_order_test.rb8
-rw-r--r--activerecord/test/cases/helper.rb6
-rw-r--r--activerecord/test/cases/hot_compatibility_test.rb2
-rw-r--r--activerecord/test/cases/i18n_test.rb2
-rw-r--r--activerecord/test/cases/inheritance_test.rb98
-rw-r--r--activerecord/test/cases/instrumentation_test.rb72
-rw-r--r--activerecord/test/cases/integration_test.rb63
-rw-r--r--activerecord/test/cases/invalid_connection_test.rb2
-rw-r--r--activerecord/test/cases/invertible_migration_test.rb55
-rw-r--r--activerecord/test/cases/json_attribute_test.rb35
-rw-r--r--activerecord/test/cases/json_serialization_test.rb12
-rw-r--r--activerecord/test/cases/json_shared_test_cases.rb269
-rw-r--r--activerecord/test/cases/legacy_configurations_test.rb43
-rw-r--r--activerecord/test/cases/locking_test.rb248
-rw-r--r--activerecord/test/cases/log_subscriber_test.rb94
-rw-r--r--activerecord/test/cases/migration/change_schema_test.rb36
-rw-r--r--activerecord/test/cases/migration/change_table_test.rb2
-rw-r--r--activerecord/test/cases/migration/column_attributes_test.rb28
-rw-r--r--activerecord/test/cases/migration/column_positioning_test.rb12
-rw-r--r--activerecord/test/cases/migration/columns_test.rb20
-rw-r--r--activerecord/test/cases/migration/command_recorder_test.rb25
-rw-r--r--activerecord/test/cases/migration/compatibility_test.rb176
-rw-r--r--activerecord/test/cases/migration/create_join_table_test.rb31
-rw-r--r--activerecord/test/cases/migration/foreign_key_test.rb160
-rw-r--r--activerecord/test/cases/migration/helper.rb2
-rw-r--r--activerecord/test/cases/migration/index_test.rb21
-rw-r--r--activerecord/test/cases/migration/logger_test.rb2
-rw-r--r--activerecord/test/cases/migration/pending_migrations_test.rb52
-rw-r--r--activerecord/test/cases/migration/references_foreign_key_test.rb12
-rw-r--r--activerecord/test/cases/migration/references_index_test.rb2
-rw-r--r--activerecord/test/cases/migration/references_statements_test.rb17
-rw-r--r--activerecord/test/cases/migration/rename_table_test.rb27
-rw-r--r--activerecord/test/cases/migration_test.rb254
-rw-r--r--activerecord/test/cases/migrator_test.rb209
-rw-r--r--activerecord/test/cases/mixin_test.rb6
-rw-r--r--activerecord/test/cases/modules_test.rb8
-rw-r--r--activerecord/test/cases/multiparameter_attributes_test.rb64
-rw-r--r--activerecord/test/cases/multiple_db_test.rb11
-rw-r--r--activerecord/test/cases/nested_attributes_test.rb71
-rw-r--r--activerecord/test/cases/nested_attributes_with_callbacks_test.rb12
-rw-r--r--activerecord/test/cases/null_relation_test.rb84
-rw-r--r--activerecord/test/cases/numeric_data_test.rb67
-rw-r--r--activerecord/test/cases/persistence_test.rb462
-rw-r--r--activerecord/test/cases/pooled_connections_test.rb2
-rw-r--r--activerecord/test/cases/primary_keys_test.rb82
-rw-r--r--activerecord/test/cases/query_cache_test.rb154
-rw-r--r--activerecord/test/cases/quoting_test.rb111
-rw-r--r--activerecord/test/cases/readonly_test.rb60
-rw-r--r--activerecord/test/cases/reaper_test.rb22
-rw-r--r--activerecord/test/cases/reflection_test.rb153
-rw-r--r--activerecord/test/cases/relation/delegation_test.rb88
-rw-r--r--activerecord/test/cases/relation/merging_test.rb42
-rw-r--r--activerecord/test/cases/relation/mutation_test.rb60
-rw-r--r--activerecord/test/cases/relation/or_test.rb47
-rw-r--r--activerecord/test/cases/relation/predicate_builder_test.rb2
-rw-r--r--activerecord/test/cases/relation/record_fetch_warning_test.rb2
-rw-r--r--activerecord/test/cases/relation/select_test.rb15
-rw-r--r--activerecord/test/cases/relation/where_chain_test.rb4
-rw-r--r--activerecord/test/cases/relation/where_clause_test.rb163
-rw-r--r--activerecord/test/cases/relation/where_test.rb50
-rw-r--r--activerecord/test/cases/relation_test.rb193
-rw-r--r--activerecord/test/cases/relations_test.rb715
-rw-r--r--activerecord/test/cases/reload_models_test.rb8
-rw-r--r--activerecord/test/cases/reserved_word_test.rb141
-rw-r--r--activerecord/test/cases/result_test.rb13
-rw-r--r--activerecord/test/cases/sanitize_test.rb81
-rw-r--r--activerecord/test/cases/schema_dumper_test.rb159
-rw-r--r--activerecord/test/cases/schema_loading_test.rb2
-rw-r--r--activerecord/test/cases/scoping/default_scoping_test.rb177
-rw-r--r--activerecord/test/cases/scoping/named_scoping_test.rb124
-rw-r--r--activerecord/test/cases/scoping/relation_scoping_test.rb67
-rw-r--r--activerecord/test/cases/secure_token_test.rb2
-rw-r--r--activerecord/test/cases/serialization_test.rb6
-rw-r--r--activerecord/test/cases/serialized_attribute_test.rb50
-rw-r--r--activerecord/test/cases/statement_cache_test.rb23
-rw-r--r--activerecord/test/cases/store_test.rb64
-rw-r--r--activerecord/test/cases/suppressor_test.rb2
-rw-r--r--activerecord/test/cases/tasks/database_tasks_test.rb996
-rw-r--r--activerecord/test/cases/tasks/mysql_rake_test.rb380
-rw-r--r--activerecord/test/cases/tasks/postgresql_rake_test.rb452
-rw-r--r--activerecord/test/cases/tasks/sqlite_rake_test.rb186
-rw-r--r--activerecord/test/cases/test_case.rb14
-rw-r--r--activerecord/test/cases/test_fixtures_test.rb2
-rw-r--r--activerecord/test/cases/time_precision_test.rb20
-rw-r--r--activerecord/test/cases/timestamp_test.rb60
-rw-r--r--activerecord/test/cases/touch_later_test.rb6
-rw-r--r--activerecord/test/cases/transaction_callbacks_test.rb109
-rw-r--r--activerecord/test/cases/transaction_isolation_test.rb8
-rw-r--r--activerecord/test/cases/transactions_test.rb447
-rw-r--r--activerecord/test/cases/type/adapter_specific_registry_test.rb2
-rw-r--r--activerecord/test/cases/type/date_time_test.rb2
-rw-r--r--activerecord/test/cases/type/integer_test.rb2
-rw-r--r--activerecord/test/cases/type/string_test.rb8
-rw-r--r--activerecord/test/cases/type/type_map_test.rb2
-rw-r--r--activerecord/test/cases/type/unsigned_integer_test.rb2
-rw-r--r--activerecord/test/cases/type_test.rb2
-rw-r--r--activerecord/test/cases/types_test.rb2
-rw-r--r--activerecord/test/cases/unconnected_test.rb4
-rw-r--r--activerecord/test/cases/unsafe_raw_sql_test.rb319
-rw-r--r--activerecord/test/cases/validations/absence_validation_test.rb12
-rw-r--r--activerecord/test/cases/validations/association_validation_test.rb26
-rw-r--r--activerecord/test/cases/validations/i18n_generate_message_validation_test.rb2
-rw-r--r--activerecord/test/cases/validations/i18n_validation_test.rb2
-rw-r--r--activerecord/test/cases/validations/length_validation_test.rb38
-rw-r--r--activerecord/test/cases/validations/presence_validation_test.rb20
-rw-r--r--activerecord/test/cases/validations/uniqueness_validation_test.rb101
-rw-r--r--activerecord/test/cases/validations_repair_helper.rb2
-rw-r--r--activerecord/test/cases/validations_test.rb44
-rw-r--r--activerecord/test/cases/view_test.rb6
-rw-r--r--activerecord/test/cases/yaml_serialization_test.rb20
323 files changed, 19657 insertions, 5798 deletions
diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb
index 0c9d1dff9d..a93e5e2b40 100644
--- a/activerecord/test/cases/adapter_test.rb
+++ b/activerecord/test/cases/adapter_test.rb
@@ -1,4 +1,7 @@
+# frozen_string_literal: true
+
require "cases/helper"
+require "support/connection_helper"
require "models/book"
require "models/post"
require "models/author"
@@ -8,6 +11,7 @@ module ActiveRecord
class AdapterTest < ActiveRecord::TestCase
def setup
@connection = ActiveRecord::Base.connection
+ @connection.materialize_transactions
end
##
@@ -18,7 +22,7 @@ module ActiveRecord
b = Book.create(name: "my \x00 book")
b.reload
assert_equal "my \x00 book", b.name
- b.update_attributes(name: "my other \x00 book")
+ b.update(name: "my other \x00 book")
b.reload
assert_equal "my other \x00 book", b.name
end
@@ -30,6 +34,16 @@ module ActiveRecord
assert_nothing_raised { Book.destroy(0) }
end
+ def test_valid_column
+ @connection.native_database_types.each_key do |type|
+ assert @connection.valid_type?(type)
+ end
+ end
+
+ def test_invalid_column
+ assert_not @connection.valid_type?(:foobar)
+ end
+
def test_tables
tables = @connection.tables
assert_includes tables, "accounts"
@@ -66,13 +80,13 @@ module ActiveRecord
idx_name = "accounts_idx"
indexes = @connection.indexes("accounts")
- assert indexes.empty?
+ assert_empty indexes
@connection.add_index :accounts, :firm_id, name: idx_name
indexes = @connection.indexes("accounts")
assert_equal "accounts", indexes.first.table
assert_equal idx_name, indexes.first.name
- assert !indexes.first.unique
+ assert_not indexes.first.unique
assert_equal ["firm_id"], indexes.first.columns
ensure
@connection.remove_index(:accounts, name: idx_name) rescue nil
@@ -146,26 +160,6 @@ module ActiveRecord
end
end
- # test resetting sequences in odd tables in PostgreSQL
- if ActiveRecord::Base.connection.respond_to?(:reset_pk_sequence!)
- require "models/movie"
- require "models/subscriber"
-
- def test_reset_empty_table_with_custom_pk
- Movie.delete_all
- Movie.connection.reset_pk_sequence! "movies"
- assert_equal 1, Movie.create(name: "fight club").id
- end
-
- def test_reset_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
-
def test_uniqueness_violations_are_translated_to_specific_exception
@connection.execute "INSERT INTO subscribers(nick) VALUES('me')"
error = assert_raises(ActiveRecord::RecordNotUnique) do
@@ -201,16 +195,78 @@ module ActiveRecord
end
end
+ def test_exceptions_from_notifications_are_not_translated
+ original_error = StandardError.new("This StandardError shouldn't get translated")
+ subscriber = ActiveSupport::Notifications.subscribe("sql.active_record") { raise original_error }
+ actual_error = assert_raises(StandardError) do
+ @connection.execute("SELECT * FROM posts")
+ end
+
+ assert_equal original_error, actual_error
+
+ ensure
+ ActiveSupport::Notifications.unsubscribe(subscriber) if subscriber
+ end
+
+ def test_database_related_exceptions_are_translated_to_statement_invalid
+ error = assert_raises(ActiveRecord::StatementInvalid) do
+ @connection.execute("This is a syntax error")
+ end
+
+ assert_instance_of ActiveRecord::StatementInvalid, error
+ assert_kind_of Exception, error.cause
+ end
+
def test_select_all_always_return_activerecord_result
result = @connection.select_all "SELECT * FROM posts"
assert result.is_a?(ActiveRecord::Result)
end
+ if ActiveRecord::Base.connection.prepared_statements
+ def test_select_all_with_legacy_binds
+ post = Post.create!(title: "foo", body: "bar")
+ expected = @connection.select_all("SELECT * FROM posts WHERE id = #{post.id}")
+ result = @connection.select_all("SELECT * FROM posts WHERE id = #{Arel::Nodes::BindParam.new(nil).to_sql}", nil, [[nil, post.id]])
+ assert_equal expected.to_hash, result.to_hash
+ end
+
+ def test_insert_update_delete_with_legacy_binds
+ binds = [[nil, 1]]
+ bind_param = Arel::Nodes::BindParam.new(nil)
+
+ id = @connection.insert("INSERT INTO events(id) VALUES (#{bind_param.to_sql})", nil, nil, nil, nil, binds)
+ assert_equal 1, id
+
+ @connection.update("UPDATE events SET title = 'foo' WHERE id = #{bind_param.to_sql}", nil, binds)
+ result = @connection.select_all("SELECT * FROM events WHERE id = #{bind_param.to_sql}", nil, binds)
+ assert_equal({ "id" => 1, "title" => "foo" }, result.first)
+
+ @connection.delete("DELETE FROM events WHERE id = #{bind_param.to_sql}", nil, binds)
+ result = @connection.select_all("SELECT * FROM events WHERE id = #{bind_param.to_sql}", nil, binds)
+ assert_nil result.first
+ end
+
+ def test_insert_update_delete_with_binds
+ binds = [Relation::QueryAttribute.new("id", 1, Type.default_value)]
+ bind_param = Arel::Nodes::BindParam.new(nil)
+
+ id = @connection.insert("INSERT INTO events(id) VALUES (#{bind_param.to_sql})", nil, nil, nil, nil, binds)
+ assert_equal 1, id
+
+ @connection.update("UPDATE events SET title = 'foo' WHERE id = #{bind_param.to_sql}", nil, binds)
+ result = @connection.select_all("SELECT * FROM events WHERE id = #{bind_param.to_sql}", nil, binds)
+ assert_equal({ "id" => 1, "title" => "foo" }, result.first)
+
+ @connection.delete("DELETE FROM events WHERE id = #{bind_param.to_sql}", nil, binds)
+ result = @connection.select_all("SELECT * FROM events WHERE id = #{bind_param.to_sql}", nil, binds)
+ assert_nil result.first
+ end
+ end
+
def test_select_methods_passing_a_association_relation
author = Author.create!(name: "john")
Post.create!(author: author, title: "foo", body: "bar")
query = author.posts.where(title: "foo").select(:title)
- assert_equal({ "title" => "foo" }, @connection.select_one(query.arel, nil, query.bound_attributes))
assert_equal({ "title" => "foo" }, @connection.select_one(query))
assert @connection.select_all(query).is_a?(ActiveRecord::Result)
assert_equal "foo", @connection.select_value(query)
@@ -220,7 +276,6 @@ module ActiveRecord
def test_select_methods_passing_a_relation
Post.create!(title: "foo", body: "bar")
query = Post.where(title: "foo").select(:title)
- assert_equal({ "title" => "foo" }, @connection.select_one(query.arel, nil, query.bound_attributes))
assert_equal({ "title" => "foo" }, @connection.select_one(query))
assert @connection.select_all(query).is_a?(ActiveRecord::Result)
assert_equal "foo", @connection.select_value(query)
@@ -235,18 +290,52 @@ module ActiveRecord
def test_log_invalid_encoding
error = assert_raises RuntimeError do
@connection.send :log, "SELECT 'ы' FROM DUAL" do
- raise "ы".force_encoding(Encoding::ASCII_8BIT)
+ raise "ы".dup.force_encoding(Encoding::ASCII_8BIT)
end
end
- assert_not_nil error.message
+ assert_equal "ы", error.message
end
end
+
+ def test_supports_multi_insert_is_deprecated
+ assert_deprecated { @connection.supports_multi_insert? }
+ end
+
+ def test_column_name_length_is_deprecated
+ assert_deprecated { @connection.column_name_length }
+ end
+
+ def test_table_name_length_is_deprecated
+ assert_deprecated { @connection.table_name_length }
+ end
+
+ def test_columns_per_table_is_deprecated
+ assert_deprecated { @connection.columns_per_table }
+ end
+
+ def test_indexes_per_table_is_deprecated
+ assert_deprecated { @connection.indexes_per_table }
+ end
+
+ def test_columns_per_multicolumn_index_is_deprecated
+ assert_deprecated { @connection.columns_per_multicolumn_index }
+ end
+
+ def test_sql_query_length_is_deprecated
+ assert_deprecated { @connection.sql_query_length }
+ end
+
+ def test_joins_per_query_is_deprecated
+ assert_deprecated { @connection.joins_per_query }
+ end
end
class AdapterForeignKeyTest < ActiveRecord::TestCase
self.use_transactional_tests = false
+ fixtures :fk_test_has_pk
+
def setup
@connection = ActiveRecord::Base.connection
end
@@ -265,7 +354,7 @@ module ActiveRecord
assert_not_nil error.cause
end
- def test_foreign_key_violations_are_translated_to_specific_exception
+ def test_foreign_key_violations_on_insert_are_translated_to_specific_exception
error = assert_raises(ActiveRecord::InvalidForeignKey) do
insert_into_fk_test_has_fk
end
@@ -273,6 +362,16 @@ module ActiveRecord
assert_not_nil error.cause
end
+ def test_foreign_key_violations_on_delete_are_translated_to_specific_exception
+ insert_into_fk_test_has_fk fk_id: 1
+
+ error = assert_raises(ActiveRecord::InvalidForeignKey) do
+ @connection.execute "DELETE FROM fk_test_has_pk WHERE pk_id = 1"
+ end
+
+ assert_not_nil error.cause
+ end
+
def test_disable_referential_integrity
assert_nothing_raised do
@connection.disable_referential_integrity do
@@ -285,14 +384,13 @@ module ActiveRecord
end
private
-
- def insert_into_fk_test_has_fk
+ def insert_into_fk_test_has_fk(fk_id: 0)
# Oracle adapter uses prefetched primary key values from sequence and passes them to connection adapter insert method
if @connection.prefetch_primary_key?
id_value = @connection.next_sequence_value(@connection.default_sequence_name("fk_test_has_fk", "id"))
- @connection.execute "INSERT INTO fk_test_has_fk (id,fk_id) VALUES (#{id_value},0)"
+ @connection.execute "INSERT INTO fk_test_has_fk (id,fk_id) VALUES (#{id_value},#{fk_id})"
else
- @connection.execute "INSERT INTO fk_test_has_fk (fk_id) VALUES (0)"
+ @connection.execute "INSERT INTO fk_test_has_fk (fk_id) VALUES (#{fk_id})"
end
end
end
@@ -315,16 +413,60 @@ module ActiveRecord
unless in_memory_db?
test "transaction state is reset after a reconnect" do
@connection.begin_transaction
- assert @connection.transaction_open?
+ assert_predicate @connection, :transaction_open?
@connection.reconnect!
- assert !@connection.transaction_open?
+ assert_not_predicate @connection, :transaction_open?
end
test "transaction state is reset after a disconnect" do
@connection.begin_transaction
- assert @connection.transaction_open?
+ assert_predicate @connection, :transaction_open?
@connection.disconnect!
- assert !@connection.transaction_open?
+ assert_not_predicate @connection, :transaction_open?
+ end
+ end
+
+ # test resetting sequences in odd tables in PostgreSQL
+ if ActiveRecord::Base.connection.respond_to?(:reset_pk_sequence!)
+ require "models/movie"
+ require "models/subscriber"
+
+ def test_reset_empty_table_with_custom_pk
+ Movie.delete_all
+ Movie.connection.reset_pk_sequence! "movies"
+ assert_equal 1, Movie.create(name: "fight club").id
+ end
+
+ def test_reset_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
+
+if ActiveRecord::Base.connection.supports_advisory_locks?
+ class AdvisoryLocksEnabledTest < ActiveRecord::TestCase
+ include ConnectionHelper
+
+ def test_advisory_locks_enabled?
+ assert ActiveRecord::Base.connection.advisory_locks_enabled?
+
+ run_without_connection do |orig_connection|
+ ActiveRecord::Base.establish_connection(
+ orig_connection.merge(advisory_locks: false)
+ )
+
+ assert_not ActiveRecord::Base.connection.advisory_locks_enabled?
+
+ ActiveRecord::Base.establish_connection(
+ orig_connection.merge(advisory_locks: true)
+ )
+
+ assert ActiveRecord::Base.connection.advisory_locks_enabled?
end
end
end
diff --git a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
index 67e1efde27..261fee13eb 100644
--- a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "support/connection_helper"
@@ -7,7 +9,7 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase
def setup
ActiveRecord::Base.connection.singleton_class.class_eval do
alias_method :execute_without_stub, :execute
- def execute(sql, name = nil) return sql end
+ def execute(sql, name = nil) sql end
end
end
@@ -66,14 +68,14 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase
def (ActiveRecord::Base.connection).data_source_exists?(*); false; end
%w(SPATIAL FULLTEXT UNIQUE).each do |type|
- expected = "CREATE TABLE `people` (#{type} INDEX `index_people_on_last_name` (`last_name`)) ENGINE=InnoDB"
+ expected = "CREATE TABLE `people` (#{type} INDEX `index_people_on_last_name` (`last_name`))"
actual = ActiveRecord::Base.connection.create_table(:people, id: false) do |t|
t.index :last_name, type: type
end
assert_equal expected, actual
end
- expected = "CREATE TABLE `people` ( INDEX `index_people_on_last_name` USING btree (`last_name`(10))) ENGINE=InnoDB"
+ expected = "CREATE TABLE `people` ( INDEX `index_people_on_last_name` USING btree (`last_name`(10)))"
actual = ActiveRecord::Base.connection.create_table(:people, id: false) do |t|
t.index :last_name, length: 10, using: :btree
end
@@ -104,9 +106,9 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase
end
def test_create_mysql_database_with_encoding
- assert_equal "CREATE DATABASE `matt` DEFAULT CHARACTER SET `utf8`", create_database(:matt)
+ assert_equal "CREATE DATABASE `matt` DEFAULT CHARACTER SET `utf8mb4`", 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)
+ assert_equal "CREATE DATABASE `matt_aimonetti` DEFAULT COLLATE `utf8mb4_bin`", create_database(:matt_aimonetti, collation: "utf8mb4_bin")
end
def test_recreate_mysql_database_with_encoding
@@ -146,8 +148,8 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase
t.timestamps null: true
end
ActiveRecord::Base.connection.remove_timestamps :delete_me, null: true
- assert !column_present?("delete_me", "updated_at", "datetime")
- assert !column_present?("delete_me", "created_at", "datetime")
+ assert_not column_present?("delete_me", "updated_at", "datetime")
+ assert_not column_present?("delete_me", "created_at", "datetime")
ensure
ActiveRecord::Base.connection.drop_table :delete_me rescue nil
end
@@ -155,15 +157,19 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase
end
def test_indexes_in_create
- ActiveRecord::Base.connection.stubs(:data_source_exists?).with(:temp).returns(false)
- ActiveRecord::Base.connection.stubs(:index_name_exists?).with(:index_temp_on_zip).returns(false)
+ assert_called_with(
+ ActiveRecord::Base.connection,
+ :data_source_exists?,
+ [:temp],
+ returns: false
+ ) do
+ expected = "CREATE TEMPORARY TABLE `temp` ( INDEX `index_temp_on_zip` (`zip`)) AS SELECT id, name, zip FROM a_really_complicated_query"
+ actual = ActiveRecord::Base.connection.create_table(:temp, temporary: true, as: "SELECT id, name, zip FROM a_really_complicated_query") do |t|
+ t.index :zip
+ end
- expected = "CREATE TEMPORARY TABLE `temp` ( INDEX `index_temp_on_zip` (`zip`)) ENGINE=InnoDB AS SELECT id, name, zip FROM a_really_complicated_query"
- actual = ActiveRecord::Base.connection.create_table(:temp, temporary: true, as: "SELECT id, name, zip FROM a_really_complicated_query") do |t|
- t.index :zip
+ assert_equal expected, actual
end
-
- assert_equal expected, actual
end
private
diff --git a/activerecord/test/cases/adapters/mysql2/auto_increment_test.rb b/activerecord/test/cases/adapters/mysql2/auto_increment_test.rb
new file mode 100644
index 0000000000..4c67633946
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql2/auto_increment_test.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+require "cases/helper"
+require "support/schema_dumping_helper"
+
+class Mysql2AutoIncrementTest < ActiveRecord::Mysql2TestCase
+ include SchemaDumpingHelper
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+ end
+
+ def teardown
+ @connection.drop_table :auto_increments, if_exists: true
+ end
+
+ def test_auto_increment_without_primary_key
+ @connection.create_table :auto_increments, id: false, force: true do |t|
+ t.integer :id, null: false, auto_increment: true
+ t.index :id
+ end
+ output = dump_table_schema("auto_increments")
+ assert_match(/t\.integer\s+"id",\s+null: false,\s+auto_increment: true$/, output)
+ end
+
+ def test_auto_increment_with_composite_primary_key
+ @connection.create_table :auto_increments, primary_key: [:id, :created_at], force: true do |t|
+ t.integer :id, null: false, auto_increment: true
+ t.datetime :created_at, null: false
+ end
+ output = dump_table_schema("auto_increments")
+ assert_match(/t\.integer\s+"id",\s+null: false,\s+auto_increment: true$/, output)
+ end
+end
diff --git a/activerecord/test/cases/adapters/mysql2/bind_parameter_test.rb b/activerecord/test/cases/adapters/mysql2/bind_parameter_test.rb
index 8f7c803a21..825bddfb73 100644
--- a/activerecord/test/cases/adapters/mysql2/bind_parameter_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/bind_parameter_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/topic"
diff --git a/activerecord/test/cases/adapters/mysql2/boolean_test.rb b/activerecord/test/cases/adapters/mysql2/boolean_test.rb
index 2fa39282fb..db09b30361 100644
--- a/activerecord/test/cases/adapters/mysql2/boolean_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/boolean_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
class Mysql2BooleanTest < ActiveRecord::Mysql2TestCase
@@ -38,7 +40,7 @@ class Mysql2BooleanTest < ActiveRecord::Mysql2TestCase
assert_equal :string, string_column.type
end
- test "test type casting with emulated booleans" do
+ test "type casting with emulated booleans" do
emulate_booleans true
boolean = BooleanType.create!(archived: true, published: true)
@@ -55,7 +57,7 @@ class Mysql2BooleanTest < ActiveRecord::Mysql2TestCase
assert_equal 0, @connection.type_cast(false)
end
- test "test type casting without emulated booleans" do
+ test "type casting without emulated booleans" do
emulate_booleans false
boolean = BooleanType.create!(archived: true, published: true)
diff --git a/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb b/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb
index 50ba9ab831..aa870349be 100644
--- a/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
class Mysql2CaseSensitivityTest < ActiveRecord::Mysql2TestCase
@@ -12,8 +14,8 @@ class Mysql2CaseSensitivityTest < ActiveRecord::Mysql2TestCase
end
def test_case_sensitive
- assert !CollationTest.columns_hash["string_ci_column"].case_sensitive?
- assert CollationTest.columns_hash["string_cs_column"].case_sensitive?
+ assert_not_predicate CollationTest.columns_hash["string_ci_column"], :case_sensitive?
+ assert_predicate CollationTest.columns_hash["string_cs_column"], :case_sensitive?
end
def test_case_insensitive_comparison_for_ci_column
diff --git a/activerecord/test/cases/adapters/mysql2/charset_collation_test.rb b/activerecord/test/cases/adapters/mysql2/charset_collation_test.rb
index 8826ad7fd1..d0c57de65d 100644
--- a/activerecord/test/cases/adapters/mysql2/charset_collation_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/charset_collation_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "support/schema_dumping_helper"
@@ -48,7 +50,7 @@ class Mysql2CharsetCollationTest < ActiveRecord::Mysql2TestCase
test "schema dump includes collation" do
output = dump_table_schema("charset_collations")
- assert_match %r{t.string\s+"string_ascii_bin",\s+collation: "ascii_bin"$}, output
- assert_match %r{t.text\s+"text_ucs2_unicode_ci",\s+collation: "ucs2_unicode_ci"$}, output
+ assert_match %r{t\.string\s+"string_ascii_bin",\s+collation: "ascii_bin"$}, output
+ assert_match %r{t\.text\s+"text_ucs2_unicode_ci",\s+collation: "ucs2_unicode_ci"$}, output
end
end
diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb
index a2faf43b0d..3103589186 100644
--- a/activerecord/test/cases/adapters/mysql2/connection_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "support/connection_helper"
@@ -38,41 +40,29 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase
end
def test_no_automatic_reconnection_after_timeout
- assert @connection.active?
+ assert_predicate @connection, :active?
@connection.update("set @@wait_timeout=1")
sleep 2
- assert !@connection.active?
+ assert_not_predicate @connection, :active?
ensure
# Repair all fixture connections so other tests won't break.
@fixture_connections.each(&:verify!)
end
def test_successful_reconnection_after_timeout_with_manual_reconnect
- assert @connection.active?
+ assert_predicate @connection, :active?
@connection.update("set @@wait_timeout=1")
sleep 2
@connection.reconnect!
- assert @connection.active?
+ assert_predicate @connection, :active?
end
def test_successful_reconnection_after_timeout_with_verify
- assert @connection.active?
+ assert_predicate @connection, :active?
@connection.update("set @@wait_timeout=1")
sleep 2
@connection.verify!
- assert @connection.active?
- end
-
- def test_verify_with_args_is_deprecated
- assert_deprecated do
- @connection.verify!(option: true)
- end
- assert_deprecated do
- @connection.verify!([])
- end
- assert_deprecated do
- @connection.verify!({})
- end
+ assert_predicate @connection, :active?
end
def test_execute_after_disconnect
@@ -114,8 +104,8 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase
end
def test_mysql_connection_collation_is_configured
- assert_equal "utf8_unicode_ci", @connection.show_variable("collation_connection")
- assert_equal "utf8_general_ci", ARUnit2Model.connection.show_variable("collation_connection")
+ assert_equal "utf8mb4_unicode_ci", @connection.show_variable("collation_connection")
+ assert_equal "utf8mb4_general_ci", ARUnit2Model.connection.show_variable("collation_connection")
end
def test_mysql_default_in_strict_mode
@@ -180,14 +170,16 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase
end
def test_logs_name_show_variable
+ ActiveRecord::Base.connection.materialize_transactions
+ @subscriber.logged.clear
@connection.show_variable "foo"
assert_equal "SCHEMA", @subscriber.logged[0][1]
end
- def test_logs_name_rename_column_sql
+ def test_logs_name_rename_column_for_alter
@connection.execute "CREATE TABLE `bar_baz` (`foo` varchar(255))"
@subscriber.logged.clear
- @connection.send(:rename_column_sql, "bar_baz", "foo", "foo2")
+ @connection.send(:rename_column_for_alter, "bar_baz", "foo", "foo2")
assert_equal "SCHEMA", @subscriber.logged[0][1]
ensure
@connection.execute "DROP TABLE `bar_baz`"
diff --git a/activerecord/test/cases/adapters/mysql2/datetime_precision_quoting_test.rb b/activerecord/test/cases/adapters/mysql2/datetime_precision_quoting_test.rb
index c131a5169c..00a075e063 100644
--- a/activerecord/test/cases/adapters/mysql2/datetime_precision_quoting_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/datetime_precision_quoting_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
class Mysql2DatetimePrecisionQuotingTest < ActiveRecord::Mysql2TestCase
@@ -43,9 +45,10 @@ class Mysql2DatetimePrecisionQuotingTest < ActiveRecord::Mysql2TestCase
end
def stub_version(full_version_string)
- @connection.stubs(:full_version).returns(full_version_string)
- @connection.remove_instance_variable(:@version) if @connection.instance_variable_defined?(:@version)
- yield
+ @connection.stub(:full_version, full_version_string) do
+ @connection.remove_instance_variable(:@version) if @connection.instance_variable_defined?(:@version)
+ yield
+ end
ensure
@connection.remove_instance_variable(:@version) if @connection.instance_variable_defined?(:@version)
end
diff --git a/activerecord/test/cases/adapters/mysql2/enum_test.rb b/activerecord/test/cases/adapters/mysql2/enum_test.rb
index 7ad3e3ca2d..832f5d61d1 100644
--- a/activerecord/test/cases/adapters/mysql2/enum_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/enum_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
class Mysql2EnumTest < ActiveRecord::Mysql2TestCase
@@ -11,11 +13,11 @@ class Mysql2EnumTest < ActiveRecord::Mysql2TestCase
def test_should_not_be_unsigned
column = EnumTest.columns_hash["enum_column"]
- assert_not column.unsigned?
+ assert_not_predicate column, :unsigned?
end
def test_should_not_be_bigint
column = EnumTest.columns_hash["enum_column"]
- assert_not column.bigint?
+ assert_not_predicate column, :bigint?
end
end
diff --git a/activerecord/test/cases/adapters/mysql2/explain_test.rb b/activerecord/test/cases/adapters/mysql2/explain_test.rb
index 7916921e5a..b8e778f0b0 100644
--- a/activerecord/test/cases/adapters/mysql2/explain_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/explain_test.rb
@@ -1,21 +1,23 @@
+# frozen_string_literal: true
+
require "cases/helper"
-require "models/developer"
-require "models/computer"
+require "models/author"
+require "models/post"
class Mysql2ExplainTest < ActiveRecord::Mysql2TestCase
- fixtures :developers
+ fixtures :authors
def test_explain_for_one_query
- explain = Developer.where(id: 1).explain
- assert_match %(EXPLAIN for: SELECT `developers`.* FROM `developers` WHERE `developers`.`id` = 1), explain
- assert_match %r(developers |.* const), explain
+ explain = Author.where(id: 1).explain
+ assert_match %(EXPLAIN for: SELECT `authors`.* FROM `authors` WHERE `authors`.`id` = 1), explain
+ assert_match %r(authors |.* const), explain
end
def test_explain_with_eager_loading
- explain = Developer.where(id: 1).includes(:audit_logs).explain
- assert_match %(EXPLAIN for: SELECT `developers`.* FROM `developers` WHERE `developers`.`id` = 1), explain
- assert_match %r(developers |.* const), explain
- assert_match %(EXPLAIN for: SELECT `audit_logs`.* FROM `audit_logs` WHERE `audit_logs`.`developer_id` = 1), explain
- assert_match %r(audit_logs |.* ALL), explain
+ explain = Author.where(id: 1).includes(:posts).explain
+ assert_match %(EXPLAIN for: SELECT `authors`.* FROM `authors` WHERE `authors`.`id` = 1), explain
+ assert_match %r(authors |.* const), explain
+ assert_match %(EXPLAIN for: SELECT `posts`.* FROM `posts` WHERE `posts`.`author_id` = 1), explain
+ assert_match %r(posts |.* ALL), explain
end
end
diff --git a/activerecord/test/cases/adapters/mysql2/json_test.rb b/activerecord/test/cases/adapters/mysql2/json_test.rb
index 6954006003..de78ba91f5 100644
--- a/activerecord/test/cases/adapters/mysql2/json_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/json_test.rb
@@ -1,195 +1,24 @@
+# frozen_string_literal: true
+
require "cases/helper"
-require "support/schema_dumping_helper"
+require "cases/json_shared_test_cases"
if ActiveRecord::Base.connection.supports_json?
class Mysql2JSONTest < ActiveRecord::Mysql2TestCase
- include SchemaDumpingHelper
+ include JSONSharedTestCases
self.use_transactional_tests = false
- class JsonDataType < ActiveRecord::Base
- self.table_name = "json_data_type"
-
- store_accessor :settings, :resolution
- end
-
def setup
- @connection = ActiveRecord::Base.connection
- begin
- @connection.create_table("json_data_type") do |t|
- t.json "payload"
- t.json "settings"
- end
+ super
+ @connection.create_table("json_data_type") do |t|
+ t.json "payload"
+ t.json "settings"
end
end
- def teardown
- @connection.drop_table :json_data_type, if_exists: true
- JsonDataType.reset_column_information
- end
-
- def test_column
- column = JsonDataType.columns_hash["payload"]
- assert_equal :json, column.type
- assert_equal "json", column.sql_type
-
- type = JsonDataType.type_for_attribute("payload")
- assert_not type.binary?
- end
-
- def test_change_table_supports_json
- @connection.change_table("json_data_type") do |t|
- t.json "users"
+ private
+ def column_type
+ :json
end
- JsonDataType.reset_column_information
- column = JsonDataType.columns_hash["users"]
- assert_equal :json, column.type
- end
-
- def test_schema_dumping
- output = dump_table_schema("json_data_type")
- assert_match(/t\.json\s+"settings"/, output)
- end
-
- def test_cast_value_on_write
- x = JsonDataType.new payload: { "string" => "foo", :symbol => :bar }
- assert_equal({ "string" => "foo", :symbol => :bar }, x.payload_before_type_cast)
- assert_equal({ "string" => "foo", "symbol" => "bar" }, x.payload)
- x.save
- assert_equal({ "string" => "foo", "symbol" => "bar" }, x.reload.payload)
- end
-
- def test_type_cast_json
- type = JsonDataType.type_for_attribute("payload")
-
- data = "{\"a_key\":\"a_value\"}"
- hash = type.deserialize(data)
- assert_equal({ "a_key" => "a_value" }, hash)
- assert_equal({ "a_key" => "a_value" }, type.deserialize(data))
-
- assert_equal({}, type.deserialize("{}"))
- assert_equal({ "key" => nil }, type.deserialize('{"key": null}'))
- assert_equal({ "c" => "}", '"a"' => 'b "a b' }, type.deserialize(%q({"c":"}", "\"a\"":"b \"a b"})))
- end
-
- def test_rewrite
- @connection.execute "insert into json_data_type (payload) VALUES ('{\"k\":\"v\"}')"
- x = JsonDataType.first
- x.payload = { '"a\'' => "b" }
- assert x.save!
- end
-
- def test_select
- @connection.execute "insert into json_data_type (payload) VALUES ('{\"k\":\"v\"}')"
- x = JsonDataType.first
- assert_equal({ "k" => "v" }, x.payload)
- end
-
- def test_select_multikey
- @connection.execute %q|insert into json_data_type (payload) VALUES ('{"k1":"v1", "k2":"v2", "k3":[1,2,3]}')|
- x = JsonDataType.first
- assert_equal({ "k1" => "v1", "k2" => "v2", "k3" => [1, 2, 3] }, x.payload)
- end
-
- def test_null_json
- @connection.execute "insert into json_data_type (payload) VALUES(null)"
- x = JsonDataType.first
- assert_nil(x.payload)
- end
-
- def test_select_array_json_value
- @connection.execute %q|insert into json_data_type (payload) VALUES ('["v0",{"k1":"v1"}]')|
- x = JsonDataType.first
- assert_equal(["v0", { "k1" => "v1" }], x.payload)
- end
-
- def test_select_nil_json_after_create
- json = JsonDataType.create(payload: nil)
- x = JsonDataType.where(payload: nil).first
- assert_equal(json, x)
- end
-
- def test_select_nil_json_after_update
- json = JsonDataType.create(payload: "foo")
- x = JsonDataType.where(payload: nil).first
- assert_nil(x)
-
- json.update_attributes payload: nil
- x = JsonDataType.where(payload: nil).first
- assert_equal(json.reload, x)
- end
-
- def test_rewrite_array_json_value
- @connection.execute %q|insert into json_data_type (payload) VALUES ('["v0",{"k1":"v1"}]')|
- x = JsonDataType.first
- x.payload = ["v1", { "k2" => "v2" }, "v3"]
- assert x.save!
- end
-
- def test_with_store_accessors
- x = JsonDataType.new(resolution: "320×480")
- assert_equal "320×480", x.resolution
-
- x.save!
- x = JsonDataType.first
- assert_equal "320×480", x.resolution
-
- x.resolution = "640×1136"
- x.save!
-
- x = JsonDataType.first
- assert_equal "640×1136", x.resolution
- end
-
- def test_duplication_with_store_accessors
- x = JsonDataType.new(resolution: "320×480")
- assert_equal "320×480", x.resolution
-
- y = x.dup
- assert_equal "320×480", y.resolution
- end
-
- def test_yaml_round_trip_with_store_accessors
- x = JsonDataType.new(resolution: "320×480")
- assert_equal "320×480", x.resolution
-
- y = YAML.load(YAML.dump(x))
- assert_equal "320×480", y.resolution
- end
-
- def test_changes_in_place
- json = JsonDataType.new
- assert_not json.changed?
-
- json.payload = { "one" => "two" }
- assert json.changed?
- assert json.payload_changed?
-
- json.save!
- assert_not json.changed?
-
- json.payload["three"] = "four"
- assert json.payload_changed?
-
- json.save!
- json.reload
-
- assert_equal({ "one" => "two", "three" => "four" }, json.payload)
- assert_not json.changed?
- end
-
- def test_assigning_string_literal
- json = JsonDataType.create(payload: "foo")
- assert_equal "foo", json.payload
- end
-
- def test_assigning_number
- json = JsonDataType.create(payload: 1.234)
- assert_equal 1.234, json.payload
- end
-
- def test_assigning_boolean
- json = JsonDataType.create(payload: true)
- assert_equal true, json.payload
- end
end
end
diff --git a/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb b/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb
index aab3dcb724..0719baaa23 100644
--- a/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "support/ddl_helper"
@@ -17,42 +19,31 @@ class Mysql2AdapterTest < ActiveRecord::Mysql2TestCase
end
end
- def test_valid_column
- with_example_table do
- column = @conn.columns("ex").find { |col| col.name == "id" }
- assert @conn.valid_type?(column.type)
- end
- end
-
- def test_invalid_column
- assert_not @conn.valid_type?(:foobar)
- end
-
def test_columns_for_distinct_zero_orders
assert_equal "posts.id",
@conn.columns_for_distinct("posts.id", [])
end
def test_columns_for_distinct_one_order
- assert_equal "posts.id, posts.created_at AS alias_0",
+ assert_equal "posts.created_at AS alias_0, posts.id",
@conn.columns_for_distinct("posts.id", ["posts.created_at desc"])
end
def test_columns_for_distinct_few_orders
- assert_equal "posts.id, posts.created_at AS alias_0, posts.position AS alias_1",
+ assert_equal "posts.created_at AS alias_0, posts.position AS alias_1, posts.id",
@conn.columns_for_distinct("posts.id", ["posts.created_at desc", "posts.position asc"])
end
def test_columns_for_distinct_with_case
assert_equal(
- "posts.id, CASE WHEN author.is_active THEN UPPER(author.name) ELSE UPPER(author.email) END AS alias_0",
+ "CASE WHEN author.is_active THEN UPPER(author.name) ELSE UPPER(author.email) END AS alias_0, posts.id",
@conn.columns_for_distinct("posts.id",
["CASE WHEN author.is_active THEN UPPER(author.name) ELSE UPPER(author.email) END"])
)
end
def test_columns_for_distinct_blank_not_nil_orders
- assert_equal "posts.id, posts.created_at AS alias_0",
+ assert_equal "posts.created_at AS alias_0, posts.id",
@conn.columns_for_distinct("posts.id", ["posts.created_at desc", "", " "])
end
@@ -61,7 +52,7 @@ class Mysql2AdapterTest < ActiveRecord::Mysql2TestCase
def order.to_sql
"posts.created_at desc"
end
- assert_equal "posts.id, posts.created_at AS alias_0",
+ assert_equal "posts.created_at AS alias_0, posts.id",
@conn.columns_for_distinct("posts.id", [order])
end
diff --git a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb
deleted file mode 100644
index 2c778b1150..0000000000
--- a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb
+++ /dev/null
@@ -1,151 +0,0 @@
-require "cases/helper"
-
-# a suite of tests to ensure the ConnectionAdapters#MysqlAdapter can handle tables with
-# reserved word names (ie: group, order, values, etc...)
-class Mysql2ReservedWordTest < ActiveRecord::Mysql2TestCase
- class Group < ActiveRecord::Base
- Group.table_name = "group"
- belongs_to :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
-
- 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",
- "distinct_select" => "distinct_id int, select_id int"
- end
-
- teardown do
- drop_tables_directly ["group", "select", "values", "distinct", "distinct_select", "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
-
- # 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_tests = false
-
- #activerecord model class with reserved-word table name
- def test_activerecord_model
- create_test_fixtures :select, :distinct, :group, :values, :distinct_select
- 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 { Group.find_by_order("y") }
- assert_nothing_raised { Group.find(1) }
- end
-
- # has_one association with reserved-word table name
- def test_has_one_associations
- create_test_fixtures :select, :distinct, :group, :values, :distinct_select
- v = nil
- assert_nothing_raised { v = Group.find(1).values }
- assert_equal 2, v.id
- end
-
- # belongs_to association with reserved-word table name
- def test_belongs_to_associations
- create_test_fixtures :select, :distinct, :group, :values, :distinct_select
- gs = nil
- assert_nothing_raised { gs = Select.find(2).groups }
- assert_equal gs.length, 2
- assert(gs.collect(&: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, :distinct_select
- s = nil
- assert_nothing_raised { s = Distinct.find(1).selects }
- assert_equal s.length, 2
- assert(s.collect(&: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.all.merge!(includes: [:groups]).to_a }
- end
-
- #the following functions were added to DRY test cases
-
- private
- # custom fixture loader, uses FixtureSet#create_fixtures and appends base_path to the current file's path
- def create_test_fixtures(*fixture_names)
- ActiveRecord::FixtureSet.create_fixtures(FIXTURES_ROOT + "/reserved_words", 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.drop_table name, if_exists: true
- 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/adapters/mysql2/schema_migrations_test.rb b/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb
index 605baa9905..d7d9a2d732 100644
--- a/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb
@@ -1,6 +1,10 @@
+# frozen_string_literal: true
+
require "cases/helper"
class SchemaMigrationsTest < ActiveRecord::Mysql2TestCase
+ self.use_transactional_tests = false
+
def test_renaming_index_on_foreign_key
connection.add_index "engines", "car_id"
connection.add_foreign_key :engines, :cars, name: "fk_engines_cars"
@@ -18,7 +22,7 @@ class SchemaMigrationsTest < ActiveRecord::Mysql2TestCase
ActiveRecord::SchemaMigration.create_table
- assert connection.column_exists?(table_name, :version, :string, collation: "utf8_general_ci")
+ assert connection.column_exists?(table_name, :version, :string)
end
end
@@ -29,8 +33,10 @@ class SchemaMigrationsTest < ActiveRecord::Mysql2TestCase
ActiveRecord::InternalMetadata.create_table
- assert connection.column_exists?(table_name, :key, :string, collation: "utf8_general_ci")
+ assert connection.column_exists?(table_name, :key, :string)
end
+ ensure
+ ActiveRecord::InternalMetadata[:environment] = connection.migration_context.current_environment
end
private
diff --git a/activerecord/test/cases/adapters/mysql2/schema_test.rb b/activerecord/test/cases/adapters/mysql2/schema_test.rb
index 1fad5585de..1283b0642c 100644
--- a/activerecord/test/cases/adapters/mysql2/schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/schema_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/post"
require "models/comment"
@@ -65,7 +67,7 @@ module ActiveRecord
end
def test_data_source_exists_wrong_schema
- assert(!@connection.data_source_exists?("#{@db_name}.zomg"), "data_source should not exist")
+ assert_not(@connection.data_source_exists?("#{@db_name}.zomg"), "data_source should not exist")
end
def test_dump_indexes
diff --git a/activerecord/test/cases/adapters/mysql2/sp_test.rb b/activerecord/test/cases/adapters/mysql2/sp_test.rb
index 4182532535..7b6dce71e9 100644
--- a/activerecord/test/cases/adapters/mysql2/sp_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/sp_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/topic"
require "models/reply"
@@ -15,7 +17,7 @@ class Mysql2StoredProcedureTest < ActiveRecord::Mysql2TestCase
# Test that MySQL allows multiple results for stored procedures
#
# In MySQL 5.6, CLIENT_MULTI_RESULTS is enabled by default.
- # http://dev.mysql.com/doc/refman/5.6/en/call.html
+ # https://dev.mysql.com/doc/refman/5.6/en/call.html
def test_multi_results
rows = @connection.select_rows("CALL ten();")
assert_equal 10, rows[0][0].to_i, "ten() did not return 10 as expected: #{rows.inspect}"
diff --git a/activerecord/test/cases/adapters/mysql2/sql_types_test.rb b/activerecord/test/cases/adapters/mysql2/sql_types_test.rb
index d6e7f29a5c..e10642cbb4 100644
--- a/activerecord/test/cases/adapters/mysql2/sql_types_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/sql_types_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
class Mysql2SqlTypesTest < ActiveRecord::Mysql2TestCase
diff --git a/activerecord/test/cases/adapters/mysql2/table_options_test.rb b/activerecord/test/cases/adapters/mysql2/table_options_test.rb
index 61a8ce9bc0..1c92df940f 100644
--- a/activerecord/test/cases/adapters/mysql2/table_options_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/table_options_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "support/schema_dumping_helper"
@@ -15,28 +17,103 @@ class Mysql2TableOptionsTest < ActiveRecord::Mysql2TestCase
test "table options with ENGINE" do
@connection.create_table "mysql_table_options", force: true, options: "ENGINE=MyISAM"
output = dump_table_schema("mysql_table_options")
- options = %r{create_table "mysql_table_options", force: :cascade, options: "(?<options>.*)"}.match(output)[:options]
+ options = %r{create_table "mysql_table_options", options: "(?<options>.*)"}.match(output)[:options]
assert_match %r{ENGINE=MyISAM}, options
end
test "table options with ROW_FORMAT" do
@connection.create_table "mysql_table_options", force: true, options: "ROW_FORMAT=REDUNDANT"
output = dump_table_schema("mysql_table_options")
- options = %r{create_table "mysql_table_options", force: :cascade, options: "(?<options>.*)"}.match(output)[:options]
+ options = %r{create_table "mysql_table_options", options: "(?<options>.*)"}.match(output)[:options]
assert_match %r{ROW_FORMAT=REDUNDANT}, options
end
test "table options with CHARSET" do
@connection.create_table "mysql_table_options", force: true, options: "CHARSET=utf8mb4"
output = dump_table_schema("mysql_table_options")
- options = %r{create_table "mysql_table_options", force: :cascade, options: "(?<options>.*)"}.match(output)[:options]
+ options = %r{create_table "mysql_table_options", options: "(?<options>.*)"}.match(output)[:options]
assert_match %r{CHARSET=utf8mb4}, options
end
test "table options with COLLATE" do
@connection.create_table "mysql_table_options", force: true, options: "COLLATE=utf8mb4_bin"
output = dump_table_schema("mysql_table_options")
- options = %r{create_table "mysql_table_options", force: :cascade, options: "(?<options>.*)"}.match(output)[:options]
+ options = %r{create_table "mysql_table_options", options: "(?<options>.*)"}.match(output)[:options]
assert_match %r{COLLATE=utf8mb4_bin}, options
end
end
+
+class Mysql2DefaultEngineOptionSchemaDumpTest < ActiveRecord::Mysql2TestCase
+ include SchemaDumpingHelper
+ self.use_transactional_tests = false
+
+ def setup
+ @verbose_was = ActiveRecord::Migration.verbose
+ ActiveRecord::Migration.verbose = false
+ end
+
+ def teardown
+ ActiveRecord::Base.connection.drop_table "mysql_table_options", if_exists: true
+ ActiveRecord::Migration.verbose = @verbose_was
+ ActiveRecord::SchemaMigration.delete_all rescue nil
+ end
+
+ test "schema dump includes ENGINE=InnoDB if not provided" do
+ ActiveRecord::Base.connection.create_table "mysql_table_options", force: true
+
+ output = dump_table_schema("mysql_table_options")
+ options = %r{create_table "mysql_table_options", options: "(?<options>.*)"}.match(output)[:options]
+ assert_match %r{ENGINE=InnoDB}, options
+ end
+
+ test "schema dump includes ENGINE=InnoDB in legacy migrations" do
+ migration = Class.new(ActiveRecord::Migration[5.1]) do
+ def migrate(x)
+ create_table "mysql_table_options", force: true
+ end
+ end.new
+
+ ActiveRecord::Migrator.new(:up, [migration]).migrate
+
+ output = dump_table_schema("mysql_table_options")
+ options = %r{create_table "mysql_table_options", options: "(?<options>.*)"}.match(output)[:options]
+ assert_match %r{ENGINE=InnoDB}, options
+ end
+end
+
+class Mysql2DefaultEngineOptionSqlOutputTest < ActiveRecord::Mysql2TestCase
+ self.use_transactional_tests = false
+
+ def setup
+ @logger_was = ActiveRecord::Base.logger
+ @log = StringIO.new
+ @verbose_was = ActiveRecord::Migration.verbose
+ ActiveRecord::Base.logger = ActiveSupport::Logger.new(@log)
+ ActiveRecord::Migration.verbose = false
+ end
+
+ def teardown
+ ActiveRecord::Base.logger = @logger_was
+ ActiveRecord::Migration.verbose = @verbose_was
+ ActiveRecord::Base.connection.drop_table "mysql_table_options", if_exists: true
+ ActiveRecord::SchemaMigration.delete_all rescue nil
+ end
+
+ test "new migrations do not contain default ENGINE=InnoDB option" do
+ ActiveRecord::Base.connection.create_table "mysql_table_options", force: true
+
+ assert_no_match %r{ENGINE=InnoDB}, @log.string
+ end
+
+ test "legacy migrations contain default ENGINE=InnoDB option" do
+ migration = Class.new(ActiveRecord::Migration[5.1]) do
+ def migrate(x)
+ create_table "mysql_table_options", force: true
+ end
+ end.new
+
+ ActiveRecord::Migrator.new(:up, [migration]).migrate
+
+ assert_match %r{ENGINE=InnoDB}, @log.string
+ end
+end
diff --git a/activerecord/test/cases/adapters/mysql2/transaction_test.rb b/activerecord/test/cases/adapters/mysql2/transaction_test.rb
index 16101e38cb..52e283f247 100644
--- a/activerecord/test/cases/adapters/mysql2/transaction_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/transaction_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "support/connection_helper"
@@ -11,6 +13,7 @@ module ActiveRecord
setup do
@abort, Thread.abort_on_exception = Thread.abort_on_exception, false
+ Thread.report_on_exception, @original_report_on_exception = false, Thread.report_on_exception
@connection = ActiveRecord::Base.connection
@connection.clear_cache!
@@ -29,6 +32,7 @@ module ActiveRecord
@connection.drop_table "samples", if_exists: true
Thread.abort_on_exception = @abort
+ Thread.report_on_exception = @original_report_on_exception
end
test "raises Deadlocked when a deadlock is encountered" do
@@ -42,7 +46,7 @@ module ActiveRecord
Sample.transaction do
s1.lock!
barrier.wait
- s2.update_attributes value: 1
+ s2.update value: 1
end
end
@@ -50,7 +54,91 @@ module ActiveRecord
Sample.transaction do
s2.lock!
barrier.wait
- s1.update_attributes value: 2
+ s1.update value: 2
+ end
+ ensure
+ thread.join
+ end
+ end
+ end
+
+ test "raises LockWaitTimeout when lock wait timeout exceeded" do
+ assert_raises(ActiveRecord::LockWaitTimeout) do
+ s = Sample.create!(value: 1)
+ latch1 = Concurrent::CountDownLatch.new
+ latch2 = Concurrent::CountDownLatch.new
+
+ thread = Thread.new do
+ Sample.transaction do
+ Sample.lock.find(s.id)
+ latch1.count_down
+ latch2.wait
+ end
+ end
+
+ begin
+ Sample.transaction do
+ latch1.wait
+ Sample.connection.execute("SET innodb_lock_wait_timeout = 1")
+ Sample.lock.find(s.id)
+ end
+ ensure
+ Sample.connection.execute("SET innodb_lock_wait_timeout = DEFAULT")
+ latch2.count_down
+ thread.join
+ end
+ end
+ end
+
+ test "raises StatementTimeout when statement timeout exceeded" do
+ skip unless ActiveRecord::Base.connection.show_variable("max_execution_time")
+ assert_raises(ActiveRecord::StatementTimeout) do
+ s = Sample.create!(value: 1)
+ latch1 = Concurrent::CountDownLatch.new
+ latch2 = Concurrent::CountDownLatch.new
+
+ thread = Thread.new do
+ Sample.transaction do
+ Sample.lock.find(s.id)
+ latch1.count_down
+ latch2.wait
+ end
+ end
+
+ begin
+ Sample.transaction do
+ latch1.wait
+ Sample.connection.execute("SET max_execution_time = 1")
+ Sample.lock.find(s.id)
+ end
+ ensure
+ Sample.connection.execute("SET max_execution_time = DEFAULT")
+ latch2.count_down
+ thread.join
+ end
+ end
+ end
+
+ test "raises QueryCanceled when canceling statement due to user request" do
+ assert_raises(ActiveRecord::QueryCanceled) do
+ s = Sample.create!(value: 1)
+ latch = Concurrent::CountDownLatch.new
+
+ thread = Thread.new do
+ Sample.transaction do
+ Sample.lock.find(s.id)
+ latch.count_down
+ sleep(0.5)
+ conn = Sample.connection
+ pid = conn.query_value("SELECT id FROM information_schema.processlist WHERE info LIKE '% FOR UPDATE'")
+ conn.execute("KILL QUERY #{pid}")
+ end
+ end
+
+ begin
+ Sample.transaction do
+ latch.wait
+ Sample.lock.find(s.id)
end
ensure
thread.join
diff --git a/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb b/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb
index a0823be143..97da96003d 100644
--- a/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "support/schema_dumping_helper"
@@ -52,15 +54,15 @@ class Mysql2UnsignedTypeTest < ActiveRecord::Mysql2TestCase
end
@connection.columns("unsigned_types").select { |c| /^unsigned_/.match?(c.name) }.each do |column|
- assert column.unsigned?
+ assert_predicate column, :unsigned?
end
end
test "schema dump includes unsigned option" do
schema = dump_table_schema "unsigned_types"
- assert_match %r{t.integer\s+"unsigned_integer",\s+unsigned: true$}, schema
- assert_match %r{t.bigint\s+"unsigned_bigint",\s+unsigned: true$}, schema
- assert_match %r{t.float\s+"unsigned_float",\s+limit: 24,\s+unsigned: true$}, schema
- assert_match %r{t.decimal\s+"unsigned_decimal",\s+precision: 10,\s+scale: 2,\s+unsigned: true$}, schema
+ assert_match %r{t\.integer\s+"unsigned_integer",\s+unsigned: true$}, schema
+ assert_match %r{t\.bigint\s+"unsigned_bigint",\s+unsigned: true$}, schema
+ assert_match %r{t\.float\s+"unsigned_float",\s+unsigned: true$}, schema
+ assert_match %r{t\.decimal\s+"unsigned_decimal",\s+precision: 10,\s+scale: 2,\s+unsigned: true$}, schema
end
end
diff --git a/activerecord/test/cases/adapters/mysql2/virtual_column_test.rb b/activerecord/test/cases/adapters/mysql2/virtual_column_test.rb
index 442a4fb7b5..8494acee3b 100644
--- a/activerecord/test/cases/adapters/mysql2/virtual_column_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/virtual_column_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "support/schema_dumping_helper"
@@ -16,6 +18,7 @@ if ActiveRecord::Base.connection.supports_virtual_columns?
t.string :name
t.virtual :upper_name, type: :string, as: "UPPER(`name`)"
t.virtual :name_length, type: :integer, as: "LENGTH(`name`)", stored: true
+ t.virtual :name_octet_length, type: :integer, as: "OCTET_LENGTH(`name`)", stored: true
end
VirtualColumn.create(name: "Rails")
end
@@ -52,8 +55,9 @@ if ActiveRecord::Base.connection.supports_virtual_columns?
def test_schema_dumping
output = dump_table_schema("virtual_columns")
- assert_match(/t\.virtual\s+"upper_name",\s+type: :string,\s+as: "UPPER\(`name`\)"$/i, output)
- assert_match(/t\.virtual\s+"name_length",\s+type: :integer,\s+as: "LENGTH\(`name`\)",\s+stored: true$/i, output)
+ assert_match(/t\.virtual\s+"upper_name",\s+type: :string,\s+as: "(?:UPPER|UCASE)\(`name`\)"$/i, output)
+ assert_match(/t\.virtual\s+"name_length",\s+type: :integer,\s+as: "(?:octet_length|length)\(`name`\)",\s+stored: true$/i, output)
+ assert_match(/t\.virtual\s+"name_octet_length",\s+type: :integer,\s+as: "(?:octet_length|length)\(`name`\)",\s+stored: true$/i, output)
end
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
index b787de8453..afd422881b 100644
--- a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
@@ -1,7 +1,11 @@
+# frozen_string_literal: true
+
require "cases/helper"
class PostgresqlActiveSchemaTest < ActiveRecord::PostgreSQLTestCase
def setup
+ ActiveRecord::Base.connection.materialize_transactions
+
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.class_eval do
def execute(sql, name = nil) sql end
end
@@ -57,6 +61,15 @@ class PostgresqlActiveSchemaTest < ActiveRecord::PostgreSQLTestCase
assert_equal expected, add_index(:people, "lower(last_name)", using: type, unique: true)
end
+ expected = %(CREATE INDEX "index_people_on_last_name" ON "people" USING gist ("last_name" bpchar_pattern_ops))
+ assert_equal expected, add_index(:people, :last_name, using: :gist, opclass: { last_name: :bpchar_pattern_ops })
+
+ expected = %(CREATE INDEX "index_people_on_last_name_and_first_name" ON "people" ("last_name" DESC NULLS LAST, "first_name" ASC))
+ assert_equal expected, add_index(:people, [:last_name, :first_name], order: { last_name: "DESC NULLS LAST", first_name: :asc })
+
+ expected = %(CREATE INDEX "index_people_on_last_name" ON "people" ("last_name" NULLS FIRST))
+ assert_equal expected, add_index(:people, :last_name, order: "NULLS FIRST")
+
assert_raise ArgumentError do
add_index(:people, :last_name, algorithm: :copy)
end
diff --git a/activerecord/test/cases/adapters/postgresql/array_test.rb b/activerecord/test/cases/adapters/postgresql/array_test.rb
index c78c6178ff..42618c2ec3 100644
--- a/activerecord/test/cases/adapters/postgresql/array_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/array_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "support/schema_dumping_helper"
@@ -37,12 +39,45 @@ class PostgresqlArrayTest < ActiveRecord::PostgreSQLTestCase
def test_column
assert_equal :string, @column.type
assert_equal "character varying(255)", @column.sql_type
- assert @column.array?
- assert_not @type.binary?
+ assert_predicate @column, :array?
+ assert_not_predicate @type, :binary?
ratings_column = PgArray.columns_hash["ratings"]
assert_equal :integer, ratings_column.type
- assert ratings_column.array?
+ assert_predicate ratings_column, :array?
+ end
+
+ def test_not_compatible_with_serialize_array
+ new_klass = Class.new(PgArray) do
+ serialize :tags, Array
+ end
+ assert_raises(ActiveRecord::AttributeMethods::Serialization::ColumnNotSerializableError) do
+ new_klass.new
+ end
+ end
+
+ class MyTags
+ def initialize(tags); @tags = tags end
+ def to_a; @tags end
+ def self.load(tags); new(tags) end
+ def self.dump(object); object.to_a end
+ end
+
+ def test_array_with_serialized_attributes
+ new_klass = Class.new(PgArray) do
+ serialize :tags, MyTags
+ end
+
+ new_klass.create!(tags: MyTags.new(["one", "two"]))
+ record = new_klass.first
+
+ assert_instance_of MyTags, record.tags
+ assert_equal ["one", "two"], record.tags.to_a
+
+ record.tags = MyTags.new(["three", "four"])
+ record.save!
+
+ assert_equal ["three", "four"], record.reload.tags.to_a
end
def test_default
@@ -74,7 +109,7 @@ class PostgresqlArrayTest < ActiveRecord::PostgreSQLTestCase
assert_equal :text, column.type
assert_equal [], PgArray.column_defaults["snippets"]
- assert column.array?
+ assert_predicate column, :array?
end
def test_change_column_cant_make_non_array_column_to_array
@@ -191,6 +226,14 @@ class PostgresqlArrayTest < ActiveRecord::PostgreSQLTestCase
assert_equal(PgArray.last.tags, tag_values)
end
+ def test_insert_fixtures
+ tag_values = ["val1", "val2", "val3_with_'_multiple_quote_'_chars"]
+ assert_deprecated do
+ @connection.insert_fixtures([{ "tags" => tag_values }], "pg_arrays")
+ end
+ assert_equal(PgArray.last.tags, tag_values)
+ end
+
def test_attribute_for_inspect_for_array_field
record = PgArray.new { |a| a.ratings = (1..10).to_a }
assert_equal("[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]", record.attribute_for_inspect(:ratings))
@@ -214,7 +257,7 @@ class PostgresqlArrayTest < ActiveRecord::PostgreSQLTestCase
x = PgArray.create!(tags: tags)
x.reload
- refute x.changed?
+ assert_not_predicate x, :changed?
end
def test_quoting_non_standard_delimiters
@@ -236,7 +279,7 @@ class PostgresqlArrayTest < ActiveRecord::PostgreSQLTestCase
x.reload
assert_equal %w(one two three), x.tags
- assert_not x.changed?
+ assert_not_predicate x, :changed?
end
def test_mutate_value_in_array
@@ -247,7 +290,7 @@ class PostgresqlArrayTest < ActiveRecord::PostgreSQLTestCase
x.reload
assert_equal [{ "a" => "c" }, { "b" => "b" }], x.hstores
- assert_not x.changed?
+ assert_not_predicate x, :changed?
end
def test_datetime_with_timezone_awareness
@@ -310,7 +353,7 @@ class PostgresqlArrayTest < ActiveRecord::PostgreSQLTestCase
assert e1.persisted?, "Saving e1"
e2 = klass.create("tags" => ["black", "blue"])
- assert !e2.persisted?, "e2 shouldn't be valid"
+ assert_not e2.persisted?, "e2 shouldn't be valid"
assert e2.errors[:tags].any?, "Should have errors for tags"
assert_equal ["has already been taken"], e2.errors[:tags], "Should have uniqueness message for tags"
end
diff --git a/activerecord/test/cases/adapters/postgresql/bit_string_test.rb b/activerecord/test/cases/adapters/postgresql/bit_string_test.rb
index 7712e809a2..c8e728bbb6 100644
--- a/activerecord/test/cases/adapters/postgresql/bit_string_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/bit_string_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "support/connection_helper"
require "support/schema_dumping_helper"
@@ -27,20 +29,20 @@ class PostgresqlBitStringTest < ActiveRecord::PostgreSQLTestCase
column = PostgresqlBitString.columns_hash["a_bit"]
assert_equal :bit, column.type
assert_equal "bit(8)", column.sql_type
- assert_not column.array?
+ assert_not_predicate column, :array?
type = PostgresqlBitString.type_for_attribute("a_bit")
- assert_not type.binary?
+ assert_not_predicate type, :binary?
end
def test_bit_string_varying_column
column = PostgresqlBitString.columns_hash["a_bit_varying"]
assert_equal :bit_varying, column.type
assert_equal "bit varying(4)", column.sql_type
- assert_not column.array?
+ assert_not_predicate column, :array?
type = PostgresqlBitString.type_for_attribute("a_bit_varying")
- assert_not type.binary?
+ assert_not_predicate type, :binary?
end
def test_default
diff --git a/activerecord/test/cases/adapters/postgresql/bytea_test.rb b/activerecord/test/cases/adapters/postgresql/bytea_test.rb
index 505c297cd4..64bb6906cd 100644
--- a/activerecord/test/cases/adapters/postgresql/bytea_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/bytea_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "support/schema_dumping_helper"
@@ -47,7 +49,7 @@ class PostgresqlByteaTest < ActiveRecord::PostgreSQLTestCase
end
def test_type_cast_binary_value
- data = "\u001F\x8B".force_encoding("BINARY")
+ data = "\u001F\x8B".dup.force_encoding("BINARY")
assert_equal(data, @type.deserialize(data))
end
@@ -73,7 +75,7 @@ class PostgresqlByteaTest < ActiveRecord::PostgreSQLTestCase
def test_write_value
data = "\u001F"
record = ByteaDataType.create(payload: data)
- assert_not record.new_record?
+ assert_not_predicate record, :new_record?
assert_equal(data, record.payload)
end
@@ -89,23 +91,24 @@ class PostgresqlByteaTest < ActiveRecord::PostgreSQLTestCase
Thread.new do
other_conn = ActiveRecord::Base.connection
other_conn.execute("SET standard_conforming_strings = off")
+ other_conn.execute("SET escape_string_warning = off")
end.join
test_via_to_sql
end
def test_write_binary
- data = File.read(File.join(File.dirname(__FILE__), "..", "..", "..", "assets", "example.log"))
+ data = File.read(File.join(__dir__, "..", "..", "..", "assets", "example.log"))
assert(data.size > 1)
record = ByteaDataType.create(payload: data)
- assert_not record.new_record?
+ assert_not_predicate record, :new_record?
assert_equal(data, record.payload)
assert_equal(data, ByteaDataType.where(id: record.id).first.payload)
end
def test_write_nil
record = ByteaDataType.create(payload: nil)
- assert_not record.new_record?
+ assert_not_predicate record, :new_record?
assert_nil(record.payload)
assert_nil(ByteaDataType.where(id: record.id).first.payload)
end
diff --git a/activerecord/test/cases/adapters/postgresql/case_insensitive_test.rb b/activerecord/test/cases/adapters/postgresql/case_insensitive_test.rb
index 03b44feab6..305e033642 100644
--- a/activerecord/test/cases/adapters/postgresql/case_insensitive_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/case_insensitive_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
class PostgresqlCaseInsensitiveTest < ActiveRecord::PostgreSQLTestCase
diff --git a/activerecord/test/cases/adapters/postgresql/change_schema_test.rb b/activerecord/test/cases/adapters/postgresql/change_schema_test.rb
index ea642069d2..6dba4f3e14 100644
--- a/activerecord/test/cases/adapters/postgresql/change_schema_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/change_schema_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
module ActiveRecord
@@ -31,7 +33,7 @@ module ActiveRecord
connection.change_column :strings, :somedate, :timestamp, array: true, cast_as: :timestamp
column = connection.columns(:strings).find { |c| c.name == "somedate" }
assert_equal :datetime, column.type
- assert column.array?
+ assert_predicate column, :array?
end
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/cidr_test.rb b/activerecord/test/cases/adapters/postgresql/cidr_test.rb
index 52f2a0096c..f20958fbd2 100644
--- a/activerecord/test/cases/adapters/postgresql/cidr_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/cidr_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "ipaddr"
diff --git a/activerecord/test/cases/adapters/postgresql/citext_test.rb b/activerecord/test/cases/adapters/postgresql/citext_test.rb
index ca95e4b626..9eb0b7d99c 100644
--- a/activerecord/test/cases/adapters/postgresql/citext_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/citext_test.rb
@@ -1,78 +1,78 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "support/schema_dumping_helper"
-if ActiveRecord::Base.connection.supports_extensions?
- class PostgresqlCitextTest < ActiveRecord::PostgreSQLTestCase
- include SchemaDumpingHelper
- class Citext < ActiveRecord::Base
- self.table_name = "citexts"
- end
+class PostgresqlCitextTest < ActiveRecord::PostgreSQLTestCase
+ include SchemaDumpingHelper
+ class Citext < ActiveRecord::Base
+ self.table_name = "citexts"
+ end
- def setup
- @connection = ActiveRecord::Base.connection
+ def setup
+ @connection = ActiveRecord::Base.connection
- enable_extension!("citext", @connection)
+ enable_extension!("citext", @connection)
- @connection.create_table("citexts") do |t|
- t.citext "cival"
- end
+ @connection.create_table("citexts") do |t|
+ t.citext "cival"
end
+ end
- teardown do
- @connection.drop_table "citexts", if_exists: true
- disable_extension!("citext", @connection)
- end
+ teardown do
+ @connection.drop_table "citexts", if_exists: true
+ disable_extension!("citext", @connection)
+ end
- def test_citext_enabled
- assert @connection.extension_enabled?("citext")
- end
+ def test_citext_enabled
+ assert @connection.extension_enabled?("citext")
+ end
- def test_column
- column = Citext.columns_hash["cival"]
- assert_equal :citext, column.type
- assert_equal "citext", column.sql_type
- assert_not column.array?
+ def test_column
+ column = Citext.columns_hash["cival"]
+ assert_equal :citext, column.type
+ assert_equal "citext", column.sql_type
+ assert_not_predicate column, :array?
- type = Citext.type_for_attribute("cival")
- assert_not type.binary?
- end
-
- def test_change_table_supports_json
- @connection.transaction do
- @connection.change_table("citexts") do |t|
- t.citext "username"
- end
- Citext.reset_column_information
- column = Citext.columns_hash["username"]
- assert_equal :citext, column.type
+ type = Citext.type_for_attribute("cival")
+ assert_not_predicate type, :binary?
+ end
- raise ActiveRecord::Rollback # reset the schema change
+ def test_change_table_supports_json
+ @connection.transaction do
+ @connection.change_table("citexts") do |t|
+ t.citext "username"
end
- ensure
Citext.reset_column_information
+ column = Citext.columns_hash["username"]
+ assert_equal :citext, column.type
+
+ raise ActiveRecord::Rollback # reset the schema change
end
+ ensure
+ Citext.reset_column_information
+ end
- def test_write
- x = Citext.new(cival: "Some CI Text")
- x.save!
- citext = Citext.first
- assert_equal "Some CI Text", citext.cival
+ def test_write
+ x = Citext.new(cival: "Some CI Text")
+ x.save!
+ citext = Citext.first
+ assert_equal "Some CI Text", citext.cival
- citext.cival = "Some NEW CI Text"
- citext.save!
+ citext.cival = "Some NEW CI Text"
+ citext.save!
- assert_equal "Some NEW CI Text", citext.reload.cival
- end
+ assert_equal "Some NEW CI Text", citext.reload.cival
+ end
- def test_select_case_insensitive
- @connection.execute "insert into citexts (cival) values('Cased Text')"
- x = Citext.where(cival: "cased text").first
- assert_equal "Cased Text", x.cival
- end
+ def test_select_case_insensitive
+ @connection.execute "insert into citexts (cival) values('Cased Text')"
+ x = Citext.where(cival: "cased text").first
+ assert_equal "Cased Text", x.cival
+ end
- def test_schema_dump_with_shorthand
- output = dump_table_schema("citexts")
- assert_match %r[t\.citext "cival"], output
- end
+ def test_schema_dump_with_shorthand
+ output = dump_table_schema("citexts")
+ assert_match %r[t\.citext "cival"], output
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/collation_test.rb b/activerecord/test/cases/adapters/postgresql/collation_test.rb
index b39e298a5d..7468f4c4f8 100644
--- a/activerecord/test/cases/adapters/postgresql/collation_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/collation_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "support/schema_dumping_helper"
@@ -47,7 +49,7 @@ class PostgresqlCollationTest < ActiveRecord::PostgreSQLTestCase
test "schema dump includes collation" do
output = dump_table_schema("postgresql_collations")
- assert_match %r{t.string\s+"string_c",\s+collation: "C"$}, output
- assert_match %r{t.text\s+"text_posix",\s+collation: "POSIX"$}, output
+ assert_match %r{t\.string\s+"string_c",\s+collation: "C"$}, output
+ assert_match %r{t\.text\s+"text_posix",\s+collation: "POSIX"$}, output
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/composite_test.rb b/activerecord/test/cases/adapters/postgresql/composite_test.rb
index 1da2a9e2ac..b0ce2694a3 100644
--- a/activerecord/test/cases/adapters/postgresql/composite_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/composite_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "support/connection_helper"
@@ -49,10 +51,10 @@ class PostgresqlCompositeTest < ActiveRecord::PostgreSQLTestCase
column = PostgresqlComposite.columns_hash["address"]
assert_nil column.type
assert_equal "full_address", column.sql_type
- assert_not column.array?
+ assert_not_predicate column, :array?
type = PostgresqlComposite.type_for_attribute("address")
- assert_not type.binary?
+ assert_not_predicate type, :binary?
end
def test_composite_mapping
@@ -104,17 +106,17 @@ class PostgresqlCompositeWithCustomOIDTest < ActiveRecord::PostgreSQLTestCase
def setup
super
- @connection.type_map.register_type "full_address", FullAddressType.new
+ @connection.send(:type_map).register_type "full_address", FullAddressType.new
end
def test_column
column = PostgresqlComposite.columns_hash["address"]
assert_equal :full_address, column.type
assert_equal "full_address", column.sql_type
- assert_not column.array?
+ assert_not_predicate column, :array?
type = PostgresqlComposite.type_for_attribute("address")
- assert_not type.binary?
+ assert_not_predicate type, :binary?
end
def test_composite_mapping
diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb
index c52d9e37cc..70aa189893 100644
--- a/activerecord/test/cases/adapters/postgresql/connection_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "support/connection_helper"
@@ -13,8 +15,9 @@ module ActiveRecord
def setup
super
@subscriber = SQLSubscriber.new
- @subscription = ActiveSupport::Notifications.subscribe("sql.active_record", @subscriber)
@connection = ActiveRecord::Base.connection
+ @connection.materialize_transactions
+ @subscription = ActiveSupport::Notifications.subscribe("sql.active_record", @subscriber)
end
def teardown
@@ -31,15 +34,21 @@ module ActiveRecord
end
def test_encoding
- assert_not_nil @connection.encoding
+ assert_queries(1) do
+ assert_not_nil @connection.encoding
+ end
end
def test_collation
- assert_not_nil @connection.collation
+ assert_queries(1) do
+ assert_not_nil @connection.collation
+ end
end
def test_ctype
- assert_not_nil @connection.ctype
+ assert_queries(1) do
+ assert_not_nil @connection.ctype
+ end
end
def test_default_client_min_messages
@@ -95,7 +104,7 @@ module ActiveRecord
end
def test_indexes_logs_name
- assert_deprecated { @connection.indexes("items", "hello") }
+ @connection.indexes("items")
assert_equal "SCHEMA", @subscriber.logged[0][1]
end
@@ -143,13 +152,13 @@ module ActiveRecord
# When prompted, restart the PostgreSQL server with the
# "-m fast" option or kill the individual connection assuming
# you know the incantation to do that.
- # To restart PostgreSQL 9.1 on OS X, installed via MacPorts, ...
+ # To restart PostgreSQL 9.1 on macOS, installed via MacPorts, ...
# sudo su postgres -c "pg_ctl restart -D /opt/local/var/db/postgresql91/defaultdb/ -m fast"
def test_reconnection_after_actual_disconnection_with_verify
original_connection_pid = @connection.query("select pg_backend_pid()")
# Sanity check.
- assert @connection.active?
+ assert_predicate @connection, :active?
if @connection.send(:postgresql_version) >= 90200
secondary_connection = ActiveRecord::Base.connection_pool.checkout
@@ -168,7 +177,7 @@ module ActiveRecord
@connection.verify!
- assert @connection.active?
+ assert_predicate @connection, :active?
# If we get no exception here, then either we re-connected successfully, or
# we never actually got disconnected.
@@ -212,6 +221,13 @@ module ActiveRecord
end
end
+ def test_set_session_timezone
+ run_without_connection do |orig_connection|
+ ActiveRecord::Base.establish_connection(orig_connection.deep_merge(variables: { timezone: "America/New_York" }))
+ assert_equal "America/New_York", ActiveRecord::Base.connection.query_value("SHOW TIME ZONE")
+ end
+ end
+
def test_get_and_release_advisory_lock
lock_id = 5295901941911233559
list_advisory_locks = <<-SQL
diff --git a/activerecord/test/cases/adapters/postgresql/datatype_test.rb b/activerecord/test/cases/adapters/postgresql/datatype_test.rb
index 0725fde5ae..b7535d5c9a 100644
--- a/activerecord/test/cases/adapters/postgresql/datatype_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/datatype_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "support/ddl_helper"
diff --git a/activerecord/test/cases/adapters/postgresql/date_test.rb b/activerecord/test/cases/adapters/postgresql/date_test.rb
new file mode 100644
index 0000000000..a86abac2be
--- /dev/null
+++ b/activerecord/test/cases/adapters/postgresql/date_test.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+require "cases/helper"
+require "models/topic"
+
+class PostgresqlDateTest < ActiveRecord::PostgreSQLTestCase
+ def test_load_infinity_and_beyond
+ topic = Topic.find_by_sql("SELECT 'infinity'::date AS last_read").first
+ assert topic.last_read.infinite?, "timestamp should be infinite"
+ assert_operator topic.last_read, :>, 0
+
+ topic = Topic.find_by_sql("SELECT '-infinity'::date AS last_read").first
+ assert topic.last_read.infinite?, "timestamp should be infinite"
+ assert_operator topic.last_read, :<, 0
+ end
+
+ def test_save_infinity_and_beyond
+ topic = Topic.create!(last_read: 1.0 / 0.0)
+ assert_equal(1.0 / 0.0, topic.last_read)
+
+ topic = Topic.create!(last_read: -1.0 / 0.0)
+ assert_equal(-1.0 / 0.0, topic.last_read)
+ end
+
+ def test_bc_date
+ date = Date.new(0) - 1.week
+ topic = Topic.create!(last_read: date)
+ assert_equal date, Topic.find(topic.id).last_read
+ end
+
+ def test_bc_date_leap_year
+ date = Time.utc(-4, 2, 29).to_date
+ topic = Topic.create!(last_read: date)
+ assert_equal date, Topic.find(topic.id).last_read
+ end
+
+ def test_bc_date_year_zero
+ date = Time.utc(0, 4, 7).to_date
+ topic = Topic.create!(last_read: date)
+ assert_equal date, Topic.find(topic.id).last_read
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/domain_test.rb b/activerecord/test/cases/adapters/postgresql/domain_test.rb
index f1eb8adb15..eeaad94c27 100644
--- a/activerecord/test/cases/adapters/postgresql/domain_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/domain_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "support/connection_helper"
@@ -28,10 +30,10 @@ class PostgresqlDomainTest < ActiveRecord::PostgreSQLTestCase
column = PostgresqlDomain.columns_hash["price"]
assert_equal :decimal, column.type
assert_equal "custom_money", column.sql_type
- assert_not column.array?
+ assert_not_predicate column, :array?
type = PostgresqlDomain.type_for_attribute("price")
- assert_not type.binary?
+ assert_not_predicate type, :binary?
end
def test_domain_acts_like_basetype
@@ -42,6 +44,6 @@ class PostgresqlDomainTest < ActiveRecord::PostgreSQLTestCase
record.price = "34.15"
record.save!
- assert_equal BigDecimal.new("34.15"), record.reload.price
+ assert_equal BigDecimal("34.15"), record.reload.price
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/enum_test.rb b/activerecord/test/cases/adapters/postgresql/enum_test.rb
index 5e5a3158ba..6789ff63e7 100644
--- a/activerecord/test/cases/adapters/postgresql/enum_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/enum_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "support/connection_helper"
@@ -30,10 +32,10 @@ class PostgresqlEnumTest < ActiveRecord::PostgreSQLTestCase
column = PostgresqlEnum.columns_hash["current_mood"]
assert_equal :enum, column.type
assert_equal "mood", column.sql_type
- assert_not column.array?
+ assert_not_predicate column, :array?
type = PostgresqlEnum.type_for_attribute("current_mood")
- assert_not type.binary?
+ assert_not_predicate type, :binary?
end
def test_enum_defaults
@@ -71,7 +73,7 @@ class PostgresqlEnumTest < ActiveRecord::PostgreSQLTestCase
@connection.execute "INSERT INTO postgresql_enums VALUES (1, 'sad');"
stderr_output = capture(:stderr) { PostgresqlEnum.first }
- assert stderr_output.blank?
+ assert_predicate stderr_output, :blank?
end
def test_enum_type_cast
diff --git a/activerecord/test/cases/adapters/postgresql/explain_test.rb b/activerecord/test/cases/adapters/postgresql/explain_test.rb
index 7493bce4fb..be525383e9 100644
--- a/activerecord/test/cases/adapters/postgresql/explain_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/explain_test.rb
@@ -1,20 +1,22 @@
+# frozen_string_literal: true
+
require "cases/helper"
-require "models/developer"
-require "models/computer"
+require "models/author"
+require "models/post"
class PostgreSQLExplainTest < ActiveRecord::PostgreSQLTestCase
- fixtures :developers
+ fixtures :authors
def test_explain_for_one_query
- explain = Developer.where(id: 1).explain
- assert_match %r(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = (?:\$1 \[\["id", 1\]\]|1)), explain
+ explain = Author.where(id: 1).explain
+ assert_match %r(EXPLAIN for: SELECT "authors"\.\* FROM "authors" WHERE "authors"\."id" = (?:\$1 \[\["id", 1\]\]|1)), explain
assert_match %(QUERY PLAN), explain
end
def test_explain_with_eager_loading
- explain = Developer.where(id: 1).includes(:audit_logs).explain
+ explain = Author.where(id: 1).includes(:posts).explain
assert_match %(QUERY PLAN), explain
- assert_match %r(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = (?:\$1 \[\["id", 1\]\]|1)), explain
- assert_match %(EXPLAIN for: SELECT "audit_logs".* FROM "audit_logs" WHERE "audit_logs"."developer_id" = 1), explain
+ assert_match %r(EXPLAIN for: SELECT "authors"\.\* FROM "authors" WHERE "authors"\."id" = (?:\$1 \[\["id", 1\]\]|1)), explain
+ assert_match %r(EXPLAIN for: SELECT "posts"\.\* FROM "posts" WHERE "posts"\."author_id" = (?:\$1 \[\["author_id", 1\]\]|1)), explain
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/extension_migration_test.rb b/activerecord/test/cases/adapters/postgresql/extension_migration_test.rb
index b56c226763..df97ab11e7 100644
--- a/activerecord/test/cases/adapters/postgresql/extension_migration_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/extension_migration_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
class PostgresqlExtensionMigrationTest < ActiveRecord::PostgreSQLTestCase
@@ -20,10 +22,6 @@ class PostgresqlExtensionMigrationTest < ActiveRecord::PostgreSQLTestCase
@connection = ActiveRecord::Base.connection
- unless @connection.supports_extensions?
- return skip("no extension support")
- end
-
@old_schema_migration_table_name = ActiveRecord::SchemaMigration.table_name
@old_table_name_prefix = ActiveRecord::Base.table_name_prefix
@old_table_name_suffix = ActiveRecord::Base.table_name_suffix
diff --git a/activerecord/test/cases/adapters/postgresql/foreign_table_test.rb b/activerecord/test/cases/adapters/postgresql/foreign_table_test.rb
new file mode 100644
index 0000000000..4fa315ad23
--- /dev/null
+++ b/activerecord/test/cases/adapters/postgresql/foreign_table_test.rb
@@ -0,0 +1,109 @@
+# frozen_string_literal: true
+
+require "cases/helper"
+require "models/professor"
+
+if ActiveRecord::Base.connection.supports_foreign_tables?
+ class ForeignTableTest < ActiveRecord::TestCase
+ self.use_transactional_tests = false
+
+ class ForeignProfessor < ActiveRecord::Base
+ self.table_name = "foreign_professors"
+ end
+
+ class ForeignProfessorWithPk < ForeignProfessor
+ self.primary_key = "id"
+ end
+
+ def setup
+ @professor = Professor.create(name: "Nicola")
+
+ @connection = ActiveRecord::Base.connection
+ enable_extension!("postgres_fdw", @connection)
+
+ foreign_db_config = ARTest.connection_config["arunit2"]
+ @connection.execute <<-SQL
+ CREATE SERVER foreign_server
+ FOREIGN DATA WRAPPER postgres_fdw
+ OPTIONS (dbname '#{foreign_db_config["database"]}')
+ SQL
+
+ @connection.execute <<-SQL
+ CREATE USER MAPPING FOR CURRENT_USER
+ SERVER foreign_server
+ SQL
+
+ @connection.execute <<-SQL
+ CREATE FOREIGN TABLE foreign_professors (
+ id int,
+ name character varying NOT NULL
+ ) SERVER foreign_server OPTIONS (
+ table_name 'professors'
+ )
+ SQL
+ end
+
+ def teardown
+ disable_extension!("postgres_fdw", @connection)
+ @connection.execute <<-SQL
+ DROP SERVER IF EXISTS foreign_server CASCADE
+ SQL
+ end
+
+ def test_table_exists
+ table_name = ForeignProfessor.table_name
+ assert_not ActiveRecord::Base.connection.table_exists?(table_name)
+ end
+
+ def test_foreign_tables_are_valid_data_sources
+ table_name = ForeignProfessor.table_name
+ assert @connection.data_source_exists?(table_name), "'#{table_name}' should be a data source"
+ end
+
+ def test_foreign_tables
+ assert_equal ["foreign_professors"], @connection.foreign_tables
+ end
+
+ def test_foreign_table_exists
+ assert @connection.foreign_table_exists?("foreign_professors")
+ assert @connection.foreign_table_exists?(:foreign_professors)
+ assert_not @connection.foreign_table_exists?("nonexistingtable")
+ assert_not @connection.foreign_table_exists?("'")
+ assert_not @connection.foreign_table_exists?(nil)
+ end
+
+ def test_attribute_names
+ assert_equal ["id", "name"], ForeignProfessor.attribute_names
+ end
+
+ def test_attributes
+ professor = ForeignProfessorWithPk.find(@professor.id)
+ assert_equal @professor.attributes, professor.attributes
+ end
+
+ def test_does_not_have_a_primary_key
+ assert_nil ForeignProfessor.primary_key
+ end
+
+ def test_insert_record
+ # Explicit `id` here to avoid complex configurations to implicitly work with remote table
+ ForeignProfessorWithPk.create!(id: 100, name: "Leonardo")
+
+ professor = ForeignProfessorWithPk.last
+ assert_equal "Leonardo", professor.name
+ end
+
+ def test_update_record
+ professor = ForeignProfessorWithPk.find(@professor.id)
+ professor.name = "Albert"
+ professor.save!
+ professor.reload
+ assert_equal "Albert", professor.name
+ end
+
+ def test_delete_record
+ professor = ForeignProfessorWithPk.find(@professor.id)
+ assert_difference("ForeignProfessor.count", -1) { professor.destroy }
+ end
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/full_text_test.rb b/activerecord/test/cases/adapters/postgresql/full_text_test.rb
index 5ddfe32007..95dee3bf44 100644
--- a/activerecord/test/cases/adapters/postgresql/full_text_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/full_text_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "support/schema_dumping_helper"
@@ -20,10 +22,10 @@ class PostgresqlFullTextTest < ActiveRecord::PostgreSQLTestCase
column = Tsvector.columns_hash["text_vector"]
assert_equal :tsvector, column.type
assert_equal "tsvector", column.sql_type
- assert_not column.array?
+ assert_not_predicate column, :array?
type = Tsvector.type_for_attribute("text_vector")
- assert_not type.binary?
+ assert_not_predicate type, :binary?
end
def test_update_tsvector
diff --git a/activerecord/test/cases/adapters/postgresql/geometric_test.rb b/activerecord/test/cases/adapters/postgresql/geometric_test.rb
index c1f3a4ae2c..8c6f046553 100644
--- a/activerecord/test/cases/adapters/postgresql/geometric_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/geometric_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "support/connection_helper"
require "support/schema_dumping_helper"
@@ -37,10 +39,10 @@ class PostgresqlPointTest < ActiveRecord::PostgreSQLTestCase
column = PostgresqlPoint.columns_hash["x"]
assert_equal :point, column.type
assert_equal "point", column.sql_type
- assert_not column.array?
+ assert_not_predicate column, :array?
type = PostgresqlPoint.type_for_attribute("x")
- assert_not type.binary?
+ assert_not_predicate type, :binary?
end
def test_default
@@ -77,7 +79,7 @@ class PostgresqlPointTest < ActiveRecord::PostgreSQLTestCase
p.reload
assert_equal ActiveRecord::Point.new(10.0, 25.0), p.x
- assert_not p.changed?
+ assert_not_predicate p, :changed?
end
def test_array_assignment
@@ -93,8 +95,6 @@ class PostgresqlPointTest < ActiveRecord::PostgreSQLTestCase
end
def test_empty_string_assignment
- assert_nothing_raised { PostgresqlPoint.new(x: "") }
-
p = PostgresqlPoint.new(x: "")
assert_nil p.x
end
@@ -117,10 +117,10 @@ class PostgresqlPointTest < ActiveRecord::PostgreSQLTestCase
column = PostgresqlPoint.columns_hash["legacy_x"]
assert_equal :point, column.type
assert_equal "point", column.sql_type
- assert_not column.array?
+ assert_not_predicate column, :array?
type = PostgresqlPoint.type_for_attribute("legacy_x")
- assert_not type.binary?
+ assert_not_predicate type, :binary?
end
def test_legacy_default
@@ -157,7 +157,7 @@ class PostgresqlPointTest < ActiveRecord::PostgreSQLTestCase
p.reload
assert_equal [10.0, 25.0], p.legacy_x
- assert_not p.changed?
+ assert_not_predicate p, :changed?
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/hstore_test.rb b/activerecord/test/cases/adapters/postgresql/hstore_test.rb
index f9cce10fb8..4b061a9375 100644
--- a/activerecord/test/cases/adapters/postgresql/hstore_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/hstore_test.rb
@@ -1,382 +1,378 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "support/schema_dumping_helper"
-if ActiveRecord::Base.connection.supports_extensions?
- class PostgresqlHstoreTest < ActiveRecord::PostgreSQLTestCase
- include SchemaDumpingHelper
- class Hstore < ActiveRecord::Base
- self.table_name = "hstores"
+class PostgresqlHstoreTest < ActiveRecord::PostgreSQLTestCase
+ include SchemaDumpingHelper
+ class Hstore < ActiveRecord::Base
+ self.table_name = "hstores"
- store_accessor :settings, :language, :timezone
- end
+ store_accessor :settings, :language, :timezone
+ end
- class FakeParameters
- def to_unsafe_h
- { "hi" => "hi" }
- end
+ class FakeParameters
+ def to_unsafe_h
+ { "hi" => "hi" }
end
+ end
- def setup
- @connection = ActiveRecord::Base.connection
-
- unless @connection.extension_enabled?("hstore")
- @connection.enable_extension "hstore"
- @connection.commit_db_transaction
- end
+ def setup
+ @connection = ActiveRecord::Base.connection
- @connection.reconnect!
+ enable_extension!("hstore", @connection)
- @connection.transaction do
- @connection.create_table("hstores") do |t|
- t.hstore "tags", default: ""
- t.hstore "payload", array: true
- t.hstore "settings"
- end
+ @connection.transaction do
+ @connection.create_table("hstores") do |t|
+ t.hstore "tags", default: ""
+ t.hstore "payload", array: true
+ t.hstore "settings"
end
- Hstore.reset_column_information
- @column = Hstore.columns_hash["tags"]
- @type = Hstore.type_for_attribute("tags")
- end
-
- teardown do
- @connection.drop_table "hstores", if_exists: true
end
+ Hstore.reset_column_information
+ @column = Hstore.columns_hash["tags"]
+ @type = Hstore.type_for_attribute("tags")
+ end
- def test_hstore_included_in_extensions
- assert @connection.respond_to?(:extensions), "connection should have a list of extensions"
- assert_includes @connection.extensions, "hstore", "extension list should include hstore"
- end
+ teardown do
+ @connection.drop_table "hstores", if_exists: true
+ disable_extension!("hstore", @connection)
+ end
- def test_disable_enable_hstore
- assert @connection.extension_enabled?("hstore")
- @connection.disable_extension "hstore"
- assert_not @connection.extension_enabled?("hstore")
- @connection.enable_extension "hstore"
- assert @connection.extension_enabled?("hstore")
- ensure
- # Restore column(s) dropped by `drop extension hstore cascade;`
- load_schema
- end
+ def test_hstore_included_in_extensions
+ assert_respond_to @connection, :extensions
+ assert_includes @connection.extensions, "hstore", "extension list should include hstore"
+ end
- def test_column
- assert_equal :hstore, @column.type
- assert_equal "hstore", @column.sql_type
- assert_not @column.array?
+ def test_disable_enable_hstore
+ assert @connection.extension_enabled?("hstore")
+ @connection.disable_extension "hstore"
+ assert_not @connection.extension_enabled?("hstore")
+ @connection.enable_extension "hstore"
+ assert @connection.extension_enabled?("hstore")
+ ensure
+ # Restore column(s) dropped by `drop extension hstore cascade;`
+ load_schema
+ end
- assert_not @type.binary?
- end
+ def test_column
+ assert_equal :hstore, @column.type
+ assert_equal "hstore", @column.sql_type
+ assert_not_predicate @column, :array?
- def test_default
- @connection.add_column "hstores", "permissions", :hstore, default: '"users"=>"read", "articles"=>"write"'
- Hstore.reset_column_information
+ assert_not_predicate @type, :binary?
+ end
- assert_equal({ "users" => "read", "articles" => "write" }, Hstore.column_defaults["permissions"])
- assert_equal({ "users" => "read", "articles" => "write" }, Hstore.new.permissions)
- ensure
- Hstore.reset_column_information
- end
+ def test_default
+ @connection.add_column "hstores", "permissions", :hstore, default: '"users"=>"read", "articles"=>"write"'
+ Hstore.reset_column_information
- def test_change_table_supports_hstore
- @connection.transaction do
- @connection.change_table("hstores") do |t|
- t.hstore "users", default: ""
- end
- Hstore.reset_column_information
- column = Hstore.columns_hash["users"]
- assert_equal :hstore, column.type
+ assert_equal({ "users" => "read", "articles" => "write" }, Hstore.column_defaults["permissions"])
+ assert_equal({ "users" => "read", "articles" => "write" }, Hstore.new.permissions)
+ ensure
+ Hstore.reset_column_information
+ end
- raise ActiveRecord::Rollback # reset the schema change
+ def test_change_table_supports_hstore
+ @connection.transaction do
+ @connection.change_table("hstores") do |t|
+ t.hstore "users", default: ""
end
- ensure
Hstore.reset_column_information
+ column = Hstore.columns_hash["users"]
+ assert_equal :hstore, column.type
+
+ raise ActiveRecord::Rollback # reset the schema change
end
+ ensure
+ Hstore.reset_column_information
+ end
- def test_hstore_migration
- hstore_migration = Class.new(ActiveRecord::Migration::Current) do
- def change
- change_table("hstores") do |t|
- t.hstore :keys
- end
+ def test_hstore_migration
+ hstore_migration = Class.new(ActiveRecord::Migration::Current) do
+ def change
+ change_table("hstores") do |t|
+ t.hstore :keys
end
end
-
- hstore_migration.new.suppress_messages do
- hstore_migration.migrate(:up)
- assert_includes @connection.columns(:hstores).map(&:name), "keys"
- hstore_migration.migrate(:down)
- assert_not_includes @connection.columns(:hstores).map(&:name), "keys"
- end
end
- def test_cast_value_on_write
- x = Hstore.new tags: { "bool" => true, "number" => 5 }
- assert_equal({ "bool" => true, "number" => 5 }, x.tags_before_type_cast)
- assert_equal({ "bool" => "true", "number" => "5" }, x.tags)
- x.save
- assert_equal({ "bool" => "true", "number" => "5" }, x.reload.tags)
+ hstore_migration.new.suppress_messages do
+ hstore_migration.migrate(:up)
+ assert_includes @connection.columns(:hstores).map(&:name), "keys"
+ hstore_migration.migrate(:down)
+ assert_not_includes @connection.columns(:hstores).map(&:name), "keys"
end
+ end
- def test_type_cast_hstore
- assert_equal({ "1" => "2" }, @type.deserialize("\"1\"=>\"2\""))
- assert_equal({}, @type.deserialize(""))
- assert_equal({ "key" => nil }, @type.deserialize("key => NULL"))
- assert_equal({ "c" => "}", '"a"' => 'b "a b' }, @type.deserialize(%q(c=>"}", "\"a\""=>"b \"a b")))
- end
+ def test_cast_value_on_write
+ x = Hstore.new tags: { "bool" => true, "number" => 5 }
+ assert_equal({ "bool" => true, "number" => 5 }, x.tags_before_type_cast)
+ assert_equal({ "bool" => "true", "number" => "5" }, x.tags)
+ x.save
+ assert_equal({ "bool" => "true", "number" => "5" }, x.reload.tags)
+ end
- def test_with_store_accessors
- x = Hstore.new(language: "fr", timezone: "GMT")
- assert_equal "fr", x.language
- assert_equal "GMT", x.timezone
+ def test_type_cast_hstore
+ assert_equal({ "1" => "2" }, @type.deserialize("\"1\"=>\"2\""))
+ assert_equal({}, @type.deserialize(""))
+ assert_equal({ "key" => nil }, @type.deserialize("key => NULL"))
+ assert_equal({ "c" => "}", '"a"' => 'b "a b' }, @type.deserialize(%q(c=>"}", "\"a\""=>"b \"a b")))
+ end
- x.save!
- x = Hstore.first
- assert_equal "fr", x.language
- assert_equal "GMT", x.timezone
+ def test_with_store_accessors
+ x = Hstore.new(language: "fr", timezone: "GMT")
+ assert_equal "fr", x.language
+ assert_equal "GMT", x.timezone
- x.language = "de"
- x.save!
+ x.save!
+ x = Hstore.first
+ assert_equal "fr", x.language
+ assert_equal "GMT", x.timezone
- x = Hstore.first
- assert_equal "de", x.language
- assert_equal "GMT", x.timezone
- end
+ x.language = "de"
+ x.save!
- def test_duplication_with_store_accessors
- x = Hstore.new(language: "fr", timezone: "GMT")
- assert_equal "fr", x.language
- assert_equal "GMT", x.timezone
+ x = Hstore.first
+ assert_equal "de", x.language
+ assert_equal "GMT", x.timezone
+ end
- y = x.dup
- assert_equal "fr", y.language
- assert_equal "GMT", y.timezone
- end
+ def test_duplication_with_store_accessors
+ x = Hstore.new(language: "fr", timezone: "GMT")
+ assert_equal "fr", x.language
+ assert_equal "GMT", x.timezone
- def test_yaml_round_trip_with_store_accessors
- x = Hstore.new(language: "fr", timezone: "GMT")
- assert_equal "fr", x.language
- assert_equal "GMT", x.timezone
+ y = x.dup
+ assert_equal "fr", y.language
+ assert_equal "GMT", y.timezone
+ end
- y = YAML.load(YAML.dump(x))
- assert_equal "fr", y.language
- assert_equal "GMT", y.timezone
- end
+ def test_yaml_round_trip_with_store_accessors
+ x = Hstore.new(language: "fr", timezone: "GMT")
+ assert_equal "fr", x.language
+ assert_equal "GMT", x.timezone
- def test_changes_in_place
- hstore = Hstore.create!(settings: { "one" => "two" })
- hstore.settings["three"] = "four"
- hstore.save!
- hstore.reload
+ y = YAML.load(YAML.dump(x))
+ assert_equal "fr", y.language
+ assert_equal "GMT", y.timezone
+ end
- assert_equal "four", hstore.settings["three"]
- assert_not hstore.changed?
- end
+ def test_changes_in_place
+ hstore = Hstore.create!(settings: { "one" => "two" })
+ hstore.settings["three"] = "four"
+ hstore.save!
+ hstore.reload
- def test_dirty_from_user_equal
- settings = { "alongkey" => "anything", "key" => "value" }
- hstore = Hstore.create!(settings: settings)
+ assert_equal "four", hstore.settings["three"]
+ assert_not_predicate hstore, :changed?
+ end
- hstore.settings = { "key" => "value", "alongkey" => "anything" }
- assert_equal settings, hstore.settings
- refute hstore.changed?
- end
+ def test_dirty_from_user_equal
+ settings = { "alongkey" => "anything", "key" => "value" }
+ hstore = Hstore.create!(settings: settings)
- def test_hstore_dirty_from_database_equal
- settings = { "alongkey" => "anything", "key" => "value" }
- hstore = Hstore.create!(settings: settings)
- hstore.reload
+ hstore.settings = { "key" => "value", "alongkey" => "anything" }
+ assert_equal settings, hstore.settings
+ assert_not_predicate hstore, :changed?
+ end
- assert_equal settings, hstore.settings
- hstore.settings = settings
- refute hstore.changed?
- end
+ def test_hstore_dirty_from_database_equal
+ settings = { "alongkey" => "anything", "key" => "value" }
+ hstore = Hstore.create!(settings: settings)
+ hstore.reload
- def test_gen1
- assert_equal('" "=>""', @type.serialize(" " => ""))
- end
+ assert_equal settings, hstore.settings
+ hstore.settings = settings
+ assert_not_predicate hstore, :changed?
+ end
- def test_gen2
- assert_equal('","=>""', @type.serialize("," => ""))
- end
+ def test_gen1
+ assert_equal('" "=>""', @type.serialize(" " => ""))
+ end
- def test_gen3
- assert_equal('"="=>""', @type.serialize("=" => ""))
- end
+ def test_gen2
+ assert_equal('","=>""', @type.serialize("," => ""))
+ end
- def test_gen4
- assert_equal('">"=>""', @type.serialize(">" => ""))
- end
+ def test_gen3
+ assert_equal('"="=>""', @type.serialize("=" => ""))
+ end
- def test_parse1
- assert_equal({ "a" => nil, "b" => nil, "c" => "NuLl", "null" => "c" }, @type.deserialize('a=>null,b=>NuLl,c=>"NuLl",null=>c'))
- end
+ def test_gen4
+ assert_equal('">"=>""', @type.serialize(">" => ""))
+ end
- def test_parse2
- assert_equal({ " " => " " }, @type.deserialize("\\ =>\\ "))
- end
+ def test_parse1
+ assert_equal({ "a" => nil, "b" => nil, "c" => "NuLl", "null" => "c" }, @type.deserialize('a=>null,b=>NuLl,c=>"NuLl",null=>c'))
+ end
- def test_parse3
- assert_equal({ "=" => ">" }, @type.deserialize("==>>"))
- end
+ def test_parse2
+ assert_equal({ " " => " " }, @type.deserialize("\\ =>\\ "))
+ end
- def test_parse4
- assert_equal({ "=a" => "q=w" }, @type.deserialize('\=a=>q=w'))
- end
+ def test_parse3
+ assert_equal({ "=" => ">" }, @type.deserialize("==>>"))
+ end
- def test_parse5
- assert_equal({ "=a" => "q=w" }, @type.deserialize('"=a"=>q\=w'))
- end
+ def test_parse4
+ assert_equal({ "=a" => "q=w" }, @type.deserialize('\=a=>q=w'))
+ end
- def test_parse6
- assert_equal({ "\"a" => "q>w" }, @type.deserialize('"\"a"=>q>w'))
- end
+ def test_parse5
+ assert_equal({ "=a" => "q=w" }, @type.deserialize('"=a"=>q\=w'))
+ end
- def test_parse7
- assert_equal({ "\"a" => "q\"w" }, @type.deserialize('\"a=>q"w'))
- end
+ def test_parse6
+ assert_equal({ "\"a" => "q>w" }, @type.deserialize('"\"a"=>q>w'))
+ end
- def test_rewrite
- @connection.execute "insert into hstores (tags) VALUES ('1=>2')"
- x = Hstore.first
- x.tags = { '"a\'' => "b" }
- assert x.save!
- end
+ def test_parse7
+ assert_equal({ "\"a" => "q\"w" }, @type.deserialize('\"a=>q"w'))
+ end
- def test_select
- @connection.execute "insert into hstores (tags) VALUES ('1=>2')"
- x = Hstore.first
- assert_equal({ "1" => "2" }, x.tags)
- end
+ def test_rewrite
+ @connection.execute "insert into hstores (tags) VALUES ('1=>2')"
+ x = Hstore.first
+ x.tags = { '"a\'' => "b" }
+ assert x.save!
+ end
- def test_array_cycle
- assert_array_cycle([{ "AA" => "BB", "CC" => "DD" }, { "AA" => nil }])
- end
+ def test_select
+ @connection.execute "insert into hstores (tags) VALUES ('1=>2')"
+ x = Hstore.first
+ assert_equal({ "1" => "2" }, x.tags)
+ end
- def test_array_strings_with_quotes
- assert_array_cycle([{ "this has" => 'some "s that need to be escaped"' }])
- end
+ def test_array_cycle
+ assert_array_cycle([{ "AA" => "BB", "CC" => "DD" }, { "AA" => nil }])
+ end
- def test_array_strings_with_commas
- assert_array_cycle([{ "this,has" => "many,values" }])
- end
+ def test_array_strings_with_quotes
+ assert_array_cycle([{ "this has" => 'some "s that need to be escaped"' }])
+ end
- def test_array_strings_with_array_delimiters
- assert_array_cycle(["{" => "}"])
- end
+ def test_array_strings_with_commas
+ assert_array_cycle([{ "this,has" => "many,values" }])
+ end
- def test_array_strings_with_null_strings
- assert_array_cycle([{ "NULL" => "NULL" }])
- end
+ def test_array_strings_with_array_delimiters
+ assert_array_cycle(["{" => "}"])
+ end
- def test_contains_nils
- assert_array_cycle([{ "NULL" => nil }])
- end
+ def test_array_strings_with_null_strings
+ assert_array_cycle([{ "NULL" => "NULL" }])
+ end
- def test_select_multikey
- @connection.execute "insert into hstores (tags) VALUES ('1=>2,2=>3')"
- x = Hstore.first
- assert_equal({ "1" => "2", "2" => "3" }, x.tags)
- end
+ def test_contains_nils
+ assert_array_cycle([{ "NULL" => nil }])
+ end
- def test_create
- assert_cycle("a" => "b", "1" => "2")
- end
+ def test_select_multikey
+ @connection.execute "insert into hstores (tags) VALUES ('1=>2,2=>3')"
+ x = Hstore.first
+ assert_equal({ "1" => "2", "2" => "3" }, x.tags)
+ end
- def test_nil
- assert_cycle("a" => nil)
- end
+ def test_create
+ assert_cycle("a" => "b", "1" => "2")
+ end
- def test_quotes
- assert_cycle("a" => 'b"ar', '1"foo' => "2")
- end
+ def test_nil
+ assert_cycle("a" => nil)
+ end
- def test_whitespace
- assert_cycle("a b" => "b ar", '1"foo' => "2")
- end
+ def test_quotes
+ assert_cycle("a" => 'b"ar', '1"foo' => "2")
+ end
- def test_backslash
- assert_cycle('a\\b' => 'b\\ar', '1"foo' => "2")
- end
+ def test_whitespace
+ assert_cycle("a b" => "b ar", '1"foo' => "2")
+ end
- def test_comma
- assert_cycle("a, b" => "bar", '1"foo' => "2")
- end
+ def test_backslash
+ assert_cycle('a\\b' => 'b\\ar', '1"foo' => "2")
+ end
- def test_arrow
- assert_cycle("a=>b" => "bar", '1"foo' => "2")
- end
+ def test_comma
+ assert_cycle("a, b" => "bar", '1"foo' => "2")
+ end
- def test_quoting_special_characters
- assert_cycle("ca" => "cà", "ac" => "àc")
- end
+ def test_arrow
+ assert_cycle("a=>b" => "bar", '1"foo' => "2")
+ end
- def test_multiline
- assert_cycle("a\nb" => "c\nd")
- end
+ def test_quoting_special_characters
+ assert_cycle("ca" => "cà", "ac" => "àc")
+ end
- class TagCollection
- def initialize(hash); @hash = hash end
- def to_hash; @hash end
- def self.load(hash); new(hash) end
- def self.dump(object); object.to_hash end
- end
+ def test_multiline
+ assert_cycle("a\nb" => "c\nd")
+ end
- class HstoreWithSerialize < Hstore
- serialize :tags, TagCollection
- end
+ class TagCollection
+ def initialize(hash); @hash = hash end
+ def to_hash; @hash end
+ def self.load(hash); new(hash) end
+ def self.dump(object); object.to_hash end
+ end
- def test_hstore_with_serialized_attributes
- HstoreWithSerialize.create! tags: TagCollection.new("one" => "two")
- record = HstoreWithSerialize.first
- assert_instance_of TagCollection, record.tags
- assert_equal({ "one" => "two" }, record.tags.to_hash)
- record.tags = TagCollection.new("three" => "four")
- record.save!
- assert_equal({ "three" => "four" }, HstoreWithSerialize.first.tags.to_hash)
- end
+ class HstoreWithSerialize < Hstore
+ serialize :tags, TagCollection
+ end
- def test_clone_hstore_with_serialized_attributes
- HstoreWithSerialize.create! tags: TagCollection.new("one" => "two")
- record = HstoreWithSerialize.first
- dupe = record.dup
- assert_equal({ "one" => "two" }, dupe.tags.to_hash)
- end
+ def test_hstore_with_serialized_attributes
+ HstoreWithSerialize.create! tags: TagCollection.new("one" => "two")
+ record = HstoreWithSerialize.first
+ assert_instance_of TagCollection, record.tags
+ assert_equal({ "one" => "two" }, record.tags.to_hash)
+ record.tags = TagCollection.new("three" => "four")
+ record.save!
+ assert_equal({ "three" => "four" }, HstoreWithSerialize.first.tags.to_hash)
+ end
- def test_schema_dump_with_shorthand
- output = dump_table_schema("hstores")
- assert_match %r[t\.hstore "tags",\s+default: {}], output
- end
+ def test_clone_hstore_with_serialized_attributes
+ HstoreWithSerialize.create! tags: TagCollection.new("one" => "two")
+ record = HstoreWithSerialize.first
+ dupe = record.dup
+ assert_equal({ "one" => "two" }, dupe.tags.to_hash)
+ end
+
+ def test_schema_dump_with_shorthand
+ output = dump_table_schema("hstores")
+ assert_match %r[t\.hstore "tags",\s+default: {}], output
+ end
+
+ def test_supports_to_unsafe_h_values
+ assert_equal("\"hi\"=>\"hi\"", @type.serialize(FakeParameters.new))
+ end
- def test_supports_to_unsafe_h_values
- assert_equal("\"hi\"=>\"hi\"", @type.serialize(FakeParameters.new))
+ private
+ def assert_array_cycle(array)
+ # test creation
+ x = Hstore.create!(payload: array)
+ x.reload
+ assert_equal(array, x.payload)
+
+ # test updating
+ x = Hstore.create!(payload: [])
+ x.payload = array
+ x.save!
+ x.reload
+ assert_equal(array, x.payload)
end
- private
- def assert_array_cycle(array)
- # test creation
- x = Hstore.create!(payload: array)
- x.reload
- assert_equal(array, x.payload)
-
- # test updating
- x = Hstore.create!(payload: [])
- x.payload = array
- x.save!
- x.reload
- assert_equal(array, x.payload)
- end
+ def assert_cycle(hash)
+ # test creation
+ x = Hstore.create!(tags: hash)
+ x.reload
+ assert_equal(hash, x.tags)
- def assert_cycle(hash)
- # test creation
- x = Hstore.create!(tags: hash)
- x.reload
- assert_equal(hash, x.tags)
-
- # test updating
- x = Hstore.create!(tags: {})
- x.tags = hash
- x.save!
- x.reload
- assert_equal(hash, x.tags)
- end
- end
+ # test updating
+ x = Hstore.create!(tags: {})
+ x.tags = hash
+ x.save!
+ x.reload
+ assert_equal(hash, x.tags)
+ end
end
diff --git a/activerecord/test/cases/adapters/postgresql/infinity_test.rb b/activerecord/test/cases/adapters/postgresql/infinity_test.rb
index b9e177e6ec..5e56ce8427 100644
--- a/activerecord/test/cases/adapters/postgresql/infinity_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/infinity_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
class PostgresqlInfinityTest < ActiveRecord::PostgreSQLTestCase
@@ -11,6 +13,7 @@ class PostgresqlInfinityTest < ActiveRecord::PostgreSQLTestCase
@connection.create_table(:postgresql_infinities) do |t|
t.float :float
t.datetime :datetime
+ t.date :date
end
end
@@ -41,11 +44,25 @@ class PostgresqlInfinityTest < ActiveRecord::PostgreSQLTestCase
end
test "type casting infinity on a datetime column" do
+ record = PostgresqlInfinity.create!(datetime: "infinity")
+ record.reload
+ assert_equal Float::INFINITY, record.datetime
+
record = PostgresqlInfinity.create!(datetime: Float::INFINITY)
record.reload
assert_equal Float::INFINITY, record.datetime
end
+ test "type casting infinity on a date column" do
+ record = PostgresqlInfinity.create!(date: "infinity")
+ record.reload
+ assert_equal Float::INFINITY, record.date
+
+ record = PostgresqlInfinity.create!(date: Float::INFINITY)
+ record.reload
+ assert_equal Float::INFINITY, record.date
+ end
+
test "update_all with infinity on a datetime column" do
record = PostgresqlInfinity.create!
PostgresqlInfinity.update_all(datetime: Float::INFINITY)
@@ -66,4 +83,28 @@ class PostgresqlInfinityTest < ActiveRecord::PostgreSQLTestCase
PostgresqlInfinity.reset_column_information
end
end
+
+ test "where clause with infinite range on a datetime column" do
+ record = PostgresqlInfinity.create!(datetime: Time.current)
+
+ string = PostgresqlInfinity.where(datetime: "-infinity".."infinity")
+ assert_equal record, string.take
+
+ infinity = PostgresqlInfinity.where(datetime: -::Float::INFINITY..::Float::INFINITY)
+ assert_equal record, infinity.take
+
+ assert_equal infinity.to_sql, string.to_sql
+ end
+
+ test "where clause with infinite range on a date column" do
+ record = PostgresqlInfinity.create!(date: Date.current)
+
+ string = PostgresqlInfinity.where(date: "-infinity".."infinity")
+ assert_equal record, string.take
+
+ infinity = PostgresqlInfinity.where(date: -::Float::INFINITY..::Float::INFINITY)
+ assert_equal record, infinity.take
+
+ assert_equal infinity.to_sql, string.to_sql
+ end
end
diff --git a/activerecord/test/cases/adapters/postgresql/integer_test.rb b/activerecord/test/cases/adapters/postgresql/integer_test.rb
index b4e55964b9..3e45b057ff 100644
--- a/activerecord/test/cases/adapters/postgresql/integer_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/integer_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "active_support/core_ext/numeric/bytes"
diff --git a/activerecord/test/cases/adapters/postgresql/json_test.rb b/activerecord/test/cases/adapters/postgresql/json_test.rb
index 93558ac4d2..ee08841eb3 100644
--- a/activerecord/test/cases/adapters/postgresql/json_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/json_test.rb
@@ -1,212 +1,37 @@
+# frozen_string_literal: true
+
require "cases/helper"
-require "support/schema_dumping_helper"
+require "cases/json_shared_test_cases"
module PostgresqlJSONSharedTestCases
- include SchemaDumpingHelper
-
- class JsonDataType < ActiveRecord::Base
- self.table_name = "json_data_type"
-
- store_accessor :settings, :resolution
- end
+ include JSONSharedTestCases
def setup
- @connection = ActiveRecord::Base.connection
- begin
- @connection.create_table("json_data_type") do |t|
- t.public_send column_type, "payload", default: {} # t.json 'payload', default: {}
- t.public_send column_type, "settings" # t.json 'settings'
- end
- rescue ActiveRecord::StatementInvalid
- skip "do not test on PostgreSQL without #{column_type} type."
+ super
+ @connection.create_table("json_data_type") do |t|
+ t.public_send column_type, "payload", default: {} # t.json 'payload', default: {}
+ t.public_send column_type, "settings" # t.json 'settings'
+ t.public_send column_type, "objects", array: true # t.json 'objects', array: true
end
- end
-
- def teardown
- @connection.drop_table :json_data_type, if_exists: true
- JsonDataType.reset_column_information
- end
-
- def test_column
- column = JsonDataType.columns_hash["payload"]
- assert_equal column_type, column.type
- assert_equal column_type.to_s, column.sql_type
- assert_not column.array?
-
- type = JsonDataType.type_for_attribute("payload")
- assert_not type.binary?
+ rescue ActiveRecord::StatementInvalid
+ skip "do not test on PostgreSQL without #{column_type} type."
end
def test_default
@connection.add_column "json_data_type", "permissions", column_type, default: { "users": "read", "posts": ["read", "write"] }
- JsonDataType.reset_column_information
-
- assert_equal({ "users" => "read", "posts" => ["read", "write"] }, JsonDataType.column_defaults["permissions"])
- assert_equal({ "users" => "read", "posts" => ["read", "write"] }, JsonDataType.new.permissions)
- ensure
- JsonDataType.reset_column_information
- end
-
- def test_change_table_supports_json
- @connection.transaction do
- @connection.change_table("json_data_type") do |t|
- t.public_send column_type, "users", default: "{}" # t.json 'users', default: '{}'
- end
- JsonDataType.reset_column_information
- column = JsonDataType.columns_hash["users"]
- assert_equal column_type, column.type
-
- raise ActiveRecord::Rollback # reset the schema change
- end
- ensure
- JsonDataType.reset_column_information
- end
-
- def test_schema_dumping
- output = dump_table_schema("json_data_type")
- assert_match(/t\.#{column_type.to_s}\s+"payload",\s+default: {}/, output)
- end
-
- def test_cast_value_on_write
- x = JsonDataType.new payload: { "string" => "foo", :symbol => :bar }
- assert_equal({ "string" => "foo", :symbol => :bar }, x.payload_before_type_cast)
- assert_equal({ "string" => "foo", "symbol" => "bar" }, x.payload)
- x.save
- assert_equal({ "string" => "foo", "symbol" => "bar" }, x.reload.payload)
- end
-
- def test_type_cast_json
- type = JsonDataType.type_for_attribute("payload")
-
- data = "{\"a_key\":\"a_value\"}"
- hash = type.deserialize(data)
- assert_equal({ "a_key" => "a_value" }, hash)
- assert_equal({ "a_key" => "a_value" }, type.deserialize(data))
-
- assert_equal({}, type.deserialize("{}"))
- assert_equal({ "key" => nil }, type.deserialize('{"key": null}'))
- assert_equal({ "c" => "}", '"a"' => 'b "a b' }, type.deserialize(%q({"c":"}", "\"a\"":"b \"a b"})))
- end
-
- def test_rewrite
- @connection.execute "insert into json_data_type (payload) VALUES ('{\"k\":\"v\"}')"
- x = JsonDataType.first
- x.payload = { '"a\'' => "b" }
- assert x.save!
- end
-
- def test_select
- @connection.execute "insert into json_data_type (payload) VALUES ('{\"k\":\"v\"}')"
- x = JsonDataType.first
- assert_equal({ "k" => "v" }, x.payload)
- end
-
- def test_select_multikey
- @connection.execute %q|insert into json_data_type (payload) VALUES ('{"k1":"v1", "k2":"v2", "k3":[1,2,3]}')|
- x = JsonDataType.first
- assert_equal({ "k1" => "v1", "k2" => "v2", "k3" => [1, 2, 3] }, x.payload)
- end
-
- def test_null_json
- @connection.execute "insert into json_data_type (payload) VALUES(null)"
- x = JsonDataType.first
- assert_nil(x.payload)
- end
+ klass.reset_column_information
- def test_select_nil_json_after_create
- json = JsonDataType.create(payload: nil)
- x = JsonDataType.where(payload: nil).first
- assert_equal(json, x)
+ assert_equal({ "users" => "read", "posts" => ["read", "write"] }, klass.column_defaults["permissions"])
+ assert_equal({ "users" => "read", "posts" => ["read", "write"] }, klass.new.permissions)
end
- def test_select_nil_json_after_update
- json = JsonDataType.create(payload: "foo")
- x = JsonDataType.where(payload: nil).first
- assert_nil(x)
-
- json.update_attributes payload: nil
- x = JsonDataType.where(payload: nil).first
- assert_equal(json.reload, x)
- end
-
- def test_select_array_json_value
- @connection.execute %q|insert into json_data_type (payload) VALUES ('["v0",{"k1":"v1"}]')|
- x = JsonDataType.first
- assert_equal(["v0", { "k1" => "v1" }], x.payload)
- end
-
- def test_rewrite_array_json_value
- @connection.execute %q|insert into json_data_type (payload) VALUES ('["v0",{"k1":"v1"}]')|
- x = JsonDataType.first
- x.payload = ["v1", { "k2" => "v2" }, "v3"]
- assert x.save!
- end
-
- def test_with_store_accessors
- x = JsonDataType.new(resolution: "320×480")
- assert_equal "320×480", x.resolution
-
+ def test_deserialize_with_array
+ x = klass.new(objects: ["foo" => "bar"])
+ assert_equal ["foo" => "bar"], x.objects
x.save!
- x = JsonDataType.first
- assert_equal "320×480", x.resolution
-
- x.resolution = "640×1136"
- x.save!
-
- x = JsonDataType.first
- assert_equal "640×1136", x.resolution
- end
-
- def test_duplication_with_store_accessors
- x = JsonDataType.new(resolution: "320×480")
- assert_equal "320×480", x.resolution
-
- y = x.dup
- assert_equal "320×480", y.resolution
- end
-
- def test_yaml_round_trip_with_store_accessors
- x = JsonDataType.new(resolution: "320×480")
- assert_equal "320×480", x.resolution
-
- y = YAML.load(YAML.dump(x))
- assert_equal "320×480", y.resolution
- end
-
- def test_changes_in_place
- json = JsonDataType.new
- assert_not json.changed?
-
- json.payload = { "one" => "two" }
- assert json.changed?
- assert json.payload_changed?
-
- json.save!
- assert_not json.changed?
-
- json.payload["three"] = "four"
- assert json.payload_changed?
-
- json.save!
- json.reload
-
- assert_equal({ "one" => "two", "three" => "four" }, json.payload)
- assert_not json.changed?
- end
-
- def test_assigning_string_literal
- json = JsonDataType.create(payload: "foo")
- assert_equal "foo", json.payload
- end
-
- def test_assigning_number
- json = JsonDataType.create(payload: 1.234)
- assert_equal 1.234, json.payload
- end
-
- def test_assigning_boolean
- json = JsonDataType.create(payload: true)
- assert_equal true, json.payload
+ assert_equal ["foo" => "bar"], x.objects
+ x.reload
+ assert_equal ["foo" => "bar"], x.objects
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/ltree_test.rb b/activerecord/test/cases/adapters/postgresql/ltree_test.rb
index 2b5ac1cac6..8349ee6ee2 100644
--- a/activerecord/test/cases/adapters/postgresql/ltree_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/ltree_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "support/schema_dumping_helper"
@@ -29,10 +31,10 @@ class PostgresqlLtreeTest < ActiveRecord::PostgreSQLTestCase
column = Ltree.columns_hash["path"]
assert_equal :ltree, column.type
assert_equal "ltree", column.sql_type
- assert_not column.array?
+ assert_not_predicate column, :array?
type = Ltree.type_for_attribute("path")
- assert_not type.binary?
+ assert_not_predicate type, :binary?
end
def test_write
diff --git a/activerecord/test/cases/adapters/postgresql/money_test.rb b/activerecord/test/cases/adapters/postgresql/money_test.rb
index 1b5d8362af..61e75e772d 100644
--- a/activerecord/test/cases/adapters/postgresql/money_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/money_test.rb
@@ -1,10 +1,14 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "support/schema_dumping_helper"
class PostgresqlMoneyTest < ActiveRecord::PostgreSQLTestCase
include SchemaDumpingHelper
- class PostgresqlMoney < ActiveRecord::Base; end
+ class PostgresqlMoney < ActiveRecord::Base
+ validates :depth, numericality: true
+ end
setup do
@connection = ActiveRecord::Base.connection
@@ -24,15 +28,16 @@ class PostgresqlMoneyTest < ActiveRecord::PostgreSQLTestCase
assert_equal :money, column.type
assert_equal "money", column.sql_type
assert_equal 2, column.scale
- assert_not column.array?
+ assert_not_predicate column, :array?
type = PostgresqlMoney.type_for_attribute("wealth")
- assert_not type.binary?
+ assert_not_predicate type, :binary?
end
def test_default
- assert_equal BigDecimal.new("150.55"), PostgresqlMoney.column_defaults["depth"]
- assert_equal BigDecimal.new("150.55"), PostgresqlMoney.new.depth
+ assert_equal BigDecimal("150.55"), PostgresqlMoney.column_defaults["depth"]
+ assert_equal BigDecimal("150.55"), PostgresqlMoney.new.depth
+ assert_equal "$150.55", PostgresqlMoney.new.depth_before_type_cast
end
def test_money_values
@@ -47,10 +52,10 @@ class PostgresqlMoneyTest < ActiveRecord::PostgreSQLTestCase
def test_money_type_cast
type = PostgresqlMoney.type_for_attribute("wealth")
- assert_equal(12345678.12, type.cast("$12,345,678.12"))
- assert_equal(12345678.12, type.cast("$12.345.678,12"))
- assert_equal(-1.15, type.cast("-$1.15"))
- assert_equal(-2.25, type.cast("($2.25)"))
+ assert_equal(12345678.12, type.cast("$12,345,678.12".dup))
+ assert_equal(12345678.12, type.cast("$12.345.678,12".dup))
+ assert_equal(-1.15, type.cast("-$1.15".dup))
+ assert_equal(-2.25, type.cast("($2.25)".dup))
end
def test_schema_dumping
@@ -60,10 +65,10 @@ class PostgresqlMoneyTest < ActiveRecord::PostgreSQLTestCase
end
def test_create_and_update_money
- money = PostgresqlMoney.create(wealth: "987.65")
+ money = PostgresqlMoney.create(wealth: "987.65".dup)
assert_equal 987.65, money.wealth
- new_value = BigDecimal.new("123.45")
+ new_value = BigDecimal("123.45")
money.wealth = new_value
money.save!
money.reload
diff --git a/activerecord/test/cases/adapters/postgresql/network_test.rb b/activerecord/test/cases/adapters/postgresql/network_test.rb
index a33b0ef8a7..736570451b 100644
--- a/activerecord/test/cases/adapters/postgresql/network_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/network_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "support/schema_dumping_helper"
@@ -22,30 +24,30 @@ class PostgresqlNetworkTest < ActiveRecord::PostgreSQLTestCase
column = PostgresqlNetworkAddress.columns_hash["cidr_address"]
assert_equal :cidr, column.type
assert_equal "cidr", column.sql_type
- assert_not column.array?
+ assert_not_predicate column, :array?
type = PostgresqlNetworkAddress.type_for_attribute("cidr_address")
- assert_not type.binary?
+ assert_not_predicate type, :binary?
end
def test_inet_column
column = PostgresqlNetworkAddress.columns_hash["inet_address"]
assert_equal :inet, column.type
assert_equal "inet", column.sql_type
- assert_not column.array?
+ assert_not_predicate column, :array?
type = PostgresqlNetworkAddress.type_for_attribute("inet_address")
- assert_not type.binary?
+ assert_not_predicate type, :binary?
end
def test_macaddr_column
column = PostgresqlNetworkAddress.columns_hash["mac_address"]
assert_equal :macaddr, column.type
assert_equal "macaddr", column.sql_type
- assert_not column.array?
+ assert_not_predicate column, :array?
type = PostgresqlNetworkAddress.type_for_attribute("mac_address")
- assert_not type.binary?
+ assert_not_predicate type, :binary?
end
def test_network_types
diff --git a/activerecord/test/cases/adapters/postgresql/numbers_test.rb b/activerecord/test/cases/adapters/postgresql/numbers_test.rb
index bfb2b7c27a..b53a12254d 100644
--- a/activerecord/test/cases/adapters/postgresql/numbers_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/numbers_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
class PostgresqlNumberTest < ActiveRecord::PostgreSQLTestCase
diff --git a/activerecord/test/cases/adapters/postgresql/partitions_test.rb b/activerecord/test/cases/adapters/postgresql/partitions_test.rb
new file mode 100644
index 0000000000..0ac9ca1200
--- /dev/null
+++ b/activerecord/test/cases/adapters/postgresql/partitions_test.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+require "cases/helper"
+
+class PostgreSQLPartitionsTest < ActiveRecord::PostgreSQLTestCase
+ def setup
+ @connection = ActiveRecord::Base.connection
+ end
+
+ def teardown
+ @connection.drop_table "partitioned_events", if_exists: true
+ end
+
+ def test_partitions_table_exists
+ skip unless ActiveRecord::Base.connection.postgresql_version >= 100000
+ @connection.create_table :partitioned_events, force: true, id: false,
+ options: "partition by range (issued_at)" do |t|
+ t.timestamp :issued_at
+ end
+ assert @connection.table_exists?("partitioned_events")
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
index 3054f0271f..cbb6cd42b5 100644
--- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "support/ddl_helper"
require "support/connection_helper"
@@ -21,17 +23,6 @@ module ActiveRecord
end
end
- def test_valid_column
- with_example_table do
- column = @connection.columns("ex").find { |col| col.name == "id" }
- assert @connection.valid_type?(column.type)
- end
- end
-
- def test_invalid_column
- assert_not @connection.valid_type?(:foobar)
- end
-
def test_primary_key
with_example_table do
assert_equal "id", @connection.primary_key("ex")
@@ -257,12 +248,12 @@ module ActiveRecord
def test_index_with_opclass
with_example_table do
- @connection.add_index "ex", "data varchar_pattern_ops"
- index = @connection.indexes("ex").find { |idx| idx.name == "index_ex_on_data_varchar_pattern_ops" }
- assert_equal "data varchar_pattern_ops", index.columns
+ @connection.add_index "ex", "data", opclass: "varchar_pattern_ops"
+ index = @connection.indexes("ex").find { |idx| idx.name == "index_ex_on_data" }
+ assert_equal ["data"], index.columns
- @connection.remove_index "ex", "data varchar_pattern_ops"
- assert_not @connection.indexes("ex").find { |idx| idx.name == "index_ex_on_data_varchar_pattern_ops" }
+ @connection.remove_index "ex", "data"
+ assert_not @connection.indexes("ex").find { |idx| idx.name == "index_ex_on_data" }
end
end
@@ -272,25 +263,25 @@ module ActiveRecord
end
def test_columns_for_distinct_one_order
- assert_equal "posts.id, posts.created_at AS alias_0",
+ assert_equal "posts.created_at AS alias_0, posts.id",
@connection.columns_for_distinct("posts.id", ["posts.created_at desc"])
end
def test_columns_for_distinct_few_orders
- assert_equal "posts.id, posts.created_at AS alias_0, posts.position AS alias_1",
+ assert_equal "posts.created_at AS alias_0, posts.position AS alias_1, posts.id",
@connection.columns_for_distinct("posts.id", ["posts.created_at desc", "posts.position asc"])
end
def test_columns_for_distinct_with_case
assert_equal(
- "posts.id, CASE WHEN author.is_active THEN UPPER(author.name) ELSE UPPER(author.email) END AS alias_0",
+ "CASE WHEN author.is_active THEN UPPER(author.name) ELSE UPPER(author.email) END AS alias_0, posts.id",
@connection.columns_for_distinct("posts.id",
["CASE WHEN author.is_active THEN UPPER(author.name) ELSE UPPER(author.email) END"])
)
end
def test_columns_for_distinct_blank_not_nil_orders
- assert_equal "posts.id, posts.created_at AS alias_0",
+ assert_equal "posts.created_at AS alias_0, posts.id",
@connection.columns_for_distinct("posts.id", ["posts.created_at desc", "", " "])
end
@@ -299,23 +290,23 @@ module ActiveRecord
def order.to_sql
"posts.created_at desc"
end
- assert_equal "posts.id, posts.created_at AS alias_0",
+ assert_equal "posts.created_at AS alias_0, posts.id",
@connection.columns_for_distinct("posts.id", [order])
end
def test_columns_for_distinct_with_nulls
- assert_equal "posts.title, posts.updater_id AS alias_0", @connection.columns_for_distinct("posts.title", ["posts.updater_id desc nulls first"])
- assert_equal "posts.title, posts.updater_id AS alias_0", @connection.columns_for_distinct("posts.title", ["posts.updater_id desc nulls last"])
+ assert_equal "posts.updater_id AS alias_0, posts.title", @connection.columns_for_distinct("posts.title", ["posts.updater_id desc nulls first"])
+ assert_equal "posts.updater_id AS alias_0, posts.title", @connection.columns_for_distinct("posts.title", ["posts.updater_id desc nulls last"])
end
def test_columns_for_distinct_without_order_specifiers
- assert_equal "posts.title, posts.updater_id AS alias_0",
+ assert_equal "posts.updater_id AS alias_0, posts.title",
@connection.columns_for_distinct("posts.title", ["posts.updater_id"])
- assert_equal "posts.title, posts.updater_id AS alias_0",
+ assert_equal "posts.updater_id AS alias_0, posts.title",
@connection.columns_for_distinct("posts.title", ["posts.updater_id nulls last"])
- assert_equal "posts.title, posts.updater_id AS alias_0",
+ assert_equal "posts.updater_id AS alias_0, posts.title",
@connection.columns_for_distinct("posts.title", ["posts.updater_id nulls first"])
end
@@ -335,29 +326,35 @@ module ActiveRecord
reset_connection
end
- def test_only_reload_type_map_once_for_every_unknown_type
+ def test_only_reload_type_map_once_for_every_unrecognized_type
+ reset_connection
+ connection = ActiveRecord::Base.connection
+
silence_warnings do
assert_queries 2, ignore_none: true do
- @connection.select_all "SELECT NULL::anyelement"
+ connection.select_all "select 'pg_catalog.pg_class'::regclass"
end
assert_queries 1, ignore_none: true do
- @connection.select_all "SELECT NULL::anyelement"
+ connection.select_all "select 'pg_catalog.pg_class'::regclass"
end
assert_queries 2, ignore_none: true do
- @connection.select_all "SELECT NULL::anyarray"
+ connection.select_all "SELECT NULL::anyarray"
end
end
ensure
reset_connection
end
- def test_only_warn_on_first_encounter_of_unknown_oid
+ def test_only_warn_on_first_encounter_of_unrecognized_oid
+ reset_connection
+ connection = ActiveRecord::Base.connection
+
warning = capture(:stderr) {
- @connection.select_all "SELECT NULL::anyelement"
- @connection.select_all "SELECT NULL::anyelement"
- @connection.select_all "SELECT NULL::anyelement"
+ connection.select_all "select 'pg_catalog.pg_class'::regclass"
+ connection.select_all "select 'pg_catalog.pg_class'::regclass"
+ connection.select_all "select 'pg_catalog.pg_class'::regclass"
}
- assert_match(/\Aunknown OID \d+: failed to recognize type of 'anyelement'. It will be treated as String.\n\z/, warning)
+ assert_match(/\Aunknown OID \d+: failed to recognize type of 'regclass'\. It will be treated as String\.\n\z/, warning)
ensure
reset_connection
end
diff --git a/activerecord/test/cases/adapters/postgresql/prepared_statements_disabled_test.rb b/activerecord/test/cases/adapters/postgresql/prepared_statements_disabled_test.rb
index 8c62690866..f7478b50c3 100644
--- a/activerecord/test/cases/adapters/postgresql/prepared_statements_disabled_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/prepared_statements_disabled_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/computer"
require "models/developer"
diff --git a/activerecord/test/cases/adapters/postgresql/quoting_test.rb b/activerecord/test/cases/adapters/postgresql/quoting_test.rb
index a1e966b915..d50dc49276 100644
--- a/activerecord/test/cases/adapters/postgresql/quoting_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/quoting_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
module ActiveRecord
@@ -9,11 +11,11 @@ module ActiveRecord
end
def test_type_cast_true
- assert_equal "t", @conn.type_cast(true)
+ assert_equal true, @conn.type_cast(true)
end
def test_type_cast_false
- assert_equal "f", @conn.type_cast(false)
+ assert_equal false, @conn.type_cast(false)
end
def test_quote_float_nan
diff --git a/activerecord/test/cases/adapters/postgresql/range_test.rb b/activerecord/test/cases/adapters/postgresql/range_test.rb
index f411884dfd..433598500d 100644
--- a/activerecord/test/cases/adapters/postgresql/range_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/range_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "support/connection_helper"
@@ -132,10 +134,10 @@ _SQL
end
def test_numrange_values
- assert_equal BigDecimal.new("0.1")..BigDecimal.new("0.2"), @first_range.num_range
- assert_equal BigDecimal.new("0.1")...BigDecimal.new("0.2"), @second_range.num_range
- assert_equal BigDecimal.new("0.1")...BigDecimal.new("Infinity"), @third_range.num_range
- assert_equal BigDecimal.new("-Infinity")...BigDecimal.new("Infinity"), @fourth_range.num_range
+ assert_equal BigDecimal("0.1")..BigDecimal("0.2"), @first_range.num_range
+ assert_equal BigDecimal("0.1")...BigDecimal("0.2"), @second_range.num_range
+ assert_equal BigDecimal("0.1")...BigDecimal("Infinity"), @third_range.num_range
+ assert_equal BigDecimal("-Infinity")...BigDecimal("Infinity"), @fourth_range.num_range
assert_nil @empty_range.num_range
end
@@ -230,16 +232,67 @@ _SQL
end
end
+ def test_create_tstzrange_preserve_usec
+ tstzrange = Time.parse("2010-01-01 14:30:00.670277 +0100")...Time.parse("2011-02-02 14:30:00.745125 CDT")
+ round_trip(@new_range, :tstz_range, tstzrange)
+ assert_equal @new_range.tstz_range, tstzrange
+ assert_equal @new_range.tstz_range, Time.parse("2010-01-01 13:30:00.670277 UTC")...Time.parse("2011-02-02 19:30:00.745125 UTC")
+ end
+
+ def test_update_tstzrange_preserve_usec
+ assert_equal_round_trip(@first_range, :tstz_range,
+ Time.parse("2010-01-01 14:30:00.245124 CDT")...Time.parse("2011-02-02 14:30:00.451274 CET"))
+ assert_nil_round_trip(@first_range, :tstz_range,
+ Time.parse("2010-01-01 14:30:00.245124 +0100")...Time.parse("2010-01-01 13:30:00.245124 +0000"))
+ end
+
+ def test_create_tsrange_preseve_usec
+ tz = ::ActiveRecord::Base.default_timezone
+ assert_equal_round_trip(@new_range, :ts_range,
+ Time.send(tz, 2010, 1, 1, 14, 30, 0, 125435)...Time.send(tz, 2011, 2, 2, 14, 30, 0, 225435))
+ end
+
+ def test_update_tsrange_preserve_usec
+ tz = ::ActiveRecord::Base.default_timezone
+ assert_equal_round_trip(@first_range, :ts_range,
+ Time.send(tz, 2010, 1, 1, 14, 30, 0, 142432)...Time.send(tz, 2011, 2, 2, 14, 30, 0, 224242))
+ assert_nil_round_trip(@first_range, :ts_range,
+ Time.send(tz, 2010, 1, 1, 14, 30, 0, 142432)...Time.send(tz, 2010, 1, 1, 14, 30, 0, 142432))
+ end
+
+ def test_timezone_awareness_tsrange_preserve_usec
+ tz = "Pacific Time (US & Canada)"
+
+ in_time_zone tz do
+ PostgresqlRange.reset_column_information
+ time_string = "2017-09-26 07:30:59.132451 -0700"
+ time = Time.zone.parse(time_string)
+ assert time.usec > 0
+
+ record = PostgresqlRange.new(ts_range: time_string..time_string)
+ assert_equal time..time, record.ts_range
+ assert_equal ActiveSupport::TimeZone[tz], record.ts_range.begin.time_zone
+ assert_equal time.usec, record.ts_range.begin.usec
+
+ record.save!
+ record.reload
+
+ assert_equal time..time, record.ts_range
+ assert_equal ActiveSupport::TimeZone[tz], record.ts_range.begin.time_zone
+ assert_equal time.usec, record.ts_range.begin.usec
+ end
+ end
+
def test_create_numrange
assert_equal_round_trip(@new_range, :num_range,
- BigDecimal.new("0.5")...BigDecimal.new("1"))
+ BigDecimal("0.5")...BigDecimal("1"))
end
def test_update_numrange
assert_equal_round_trip(@first_range, :num_range,
- BigDecimal.new("0.5")...BigDecimal.new("1"))
+ BigDecimal("0.5")...BigDecimal("1"))
assert_nil_round_trip(@first_range, :num_range,
- BigDecimal.new("0.5")...BigDecimal.new("0.5"))
+ BigDecimal("0.5")...BigDecimal("0.5"))
end
def test_create_daterange
@@ -288,6 +341,12 @@ _SQL
assert_equal record, PostgresqlRange.where(int4_range: range).take
end
+ def test_where_by_attribute_with_range_in_array
+ range = 1..100
+ record = PostgresqlRange.create!(int4_range: range)
+ assert_equal record, PostgresqlRange.where(int4_range: [range]).take
+ end
+
def test_update_all_with_ranges
PostgresqlRange.create!
@@ -305,6 +364,18 @@ _SQL
end
end
+ def test_infinity_values
+ PostgresqlRange.create!(int4_range: 1..Float::INFINITY,
+ int8_range: -Float::INFINITY..0,
+ float_range: -Float::INFINITY..Float::INFINITY)
+
+ record = PostgresqlRange.first
+
+ assert_equal(1...Float::INFINITY, record.int4_range)
+ assert_equal(-Float::INFINITY...1, record.int8_range)
+ assert_equal(-Float::INFINITY...Float::INFINITY, record.float_range)
+ end
+
private
def assert_equal_round_trip(range, attribute, value)
round_trip(range, attribute, value)
diff --git a/activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb b/activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb
index 0ff04bfa27..0bcc214c24 100644
--- a/activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "support/connection_helper"
diff --git a/activerecord/test/cases/adapters/postgresql/rename_table_test.rb b/activerecord/test/cases/adapters/postgresql/rename_table_test.rb
index e9e7f717ac..100d247113 100644
--- a/activerecord/test/cases/adapters/postgresql/rename_table_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/rename_table_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
class PostgresqlRenameTableTest < ActiveRecord::PostgreSQLTestCase
diff --git a/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb b/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb
index f6a07da85f..fcb0aec81b 100644
--- a/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
class SchemaThing < ActiveRecord::Base
@@ -75,17 +77,6 @@ class SchemaAuthorizationTest < ActiveRecord::PostgreSQLTestCase
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|
diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb
index 7b065ff320..a36d066c80 100644
--- a/activerecord/test/cases/adapters/postgresql/schema_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/default"
require "support/schema_dumping_helper"
@@ -91,6 +93,7 @@ class SchemaTest < ActiveRecord::PostgreSQLTestCase
@connection.execute "CREATE INDEX #{INDEX_E_NAME} ON #{SCHEMA_NAME}.#{TABLE_NAME} USING gin (#{INDEX_E_COLUMN});"
@connection.execute "CREATE INDEX #{INDEX_E_NAME} ON #{SCHEMA2_NAME}.#{TABLE_NAME} USING gin (#{INDEX_E_COLUMN});"
@connection.execute "CREATE TABLE #{SCHEMA_NAME}.#{PK_TABLE_NAME} (id serial primary key)"
+ @connection.execute "CREATE TABLE #{SCHEMA2_NAME}.#{PK_TABLE_NAME} (id serial primary key)"
@connection.execute "CREATE SEQUENCE #{SCHEMA_NAME}.#{UNMATCHED_SEQUENCE_NAME}"
@connection.execute "CREATE TABLE #{SCHEMA_NAME}.#{UNMATCHED_PK_TABLE_NAME} (id integer NOT NULL DEFAULT nextval('#{SCHEMA_NAME}.#{UNMATCHED_SEQUENCE_NAME}'::regclass), CONSTRAINT unmatched_pkey PRIMARY KEY (id))"
end
@@ -201,12 +204,12 @@ class SchemaTest < ActiveRecord::PostgreSQLTestCase
def test_data_source_exists_when_not_on_schema_search_path
with_schema_search_path("PUBLIC") do
- assert(!@connection.data_source_exists?(TABLE_NAME), "data_source exists but should not be found")
+ assert_not(@connection.data_source_exists?(TABLE_NAME), "data_source exists but should not be found")
end
end
def test_data_source_exists_wrong_schema
- assert(!@connection.data_source_exists?("foo.things"), "data_source should not exist")
+ assert_not(@connection.data_source_exists?("foo.things"), "data_source should not exist")
end
def test_data_source_exists_quoted_names
@@ -361,7 +364,7 @@ class SchemaTest < ActiveRecord::PostgreSQLTestCase
end
def test_primary_key_assuming_schema_search_path
- with_schema_search_path(SCHEMA_NAME) do
+ with_schema_search_path("#{SCHEMA_NAME}, #{SCHEMA2_NAME}") do
assert_equal "id", @connection.primary_key(PK_TABLE_NAME), "primary key should be found"
end
end
@@ -456,7 +459,7 @@ class SchemaTest < ActiveRecord::PostgreSQLTestCase
assert_equal :btree, index_d.using
assert_equal :gin, index_e.using
- assert_equal :desc, index_d.orders[INDEX_D_COLUMN]
+ assert_equal :desc, index_d.orders
end
end
@@ -497,6 +500,66 @@ class SchemaForeignKeyTest < ActiveRecord::PostgreSQLTestCase
end
end
+class SchemaIndexOpclassTest < ActiveRecord::PostgreSQLTestCase
+ include SchemaDumpingHelper
+
+ setup do
+ @connection = ActiveRecord::Base.connection
+ @connection.create_table "trains" do |t|
+ t.string :name
+ t.text :description
+ end
+ end
+
+ teardown do
+ @connection.drop_table "trains", if_exists: true
+ end
+
+ def test_string_opclass_is_dumped
+ @connection.execute "CREATE INDEX trains_name_and_description ON trains USING btree(name text_pattern_ops, description text_pattern_ops)"
+
+ output = dump_table_schema "trains"
+
+ assert_match(/opclass: :text_pattern_ops/, output)
+ end
+
+ def test_non_default_opclass_is_dumped
+ @connection.execute "CREATE INDEX trains_name_and_description ON trains USING btree(name, description text_pattern_ops)"
+
+ output = dump_table_schema "trains"
+
+ assert_match(/opclass: \{ description: :text_pattern_ops \}/, output)
+ end
+end
+
+class SchemaIndexNullsOrderTest < ActiveRecord::PostgreSQLTestCase
+ include SchemaDumpingHelper
+
+ setup do
+ @connection = ActiveRecord::Base.connection
+ @connection.create_table "trains" do |t|
+ t.string :name
+ t.text :description
+ end
+ end
+
+ teardown do
+ @connection.drop_table "trains", if_exists: true
+ end
+
+ def test_nulls_order_is_dumped
+ @connection.execute "CREATE INDEX trains_name_and_description ON trains USING btree(name NULLS FIRST, description)"
+ output = dump_table_schema "trains"
+ assert_match(/order: \{ name: "NULLS FIRST" \}/, output)
+ end
+
+ def test_non_default_order_with_nulls_is_dumped
+ @connection.execute "CREATE INDEX trains_name_and_desc ON trains USING btree(name DESC NULLS LAST, description)"
+ output = dump_table_schema "trains"
+ assert_match(/order: \{ name: "DESC NULLS LAST" \}/, output)
+ end
+end
+
class DefaultsUsingMultipleSchemasAndDomainTest < ActiveRecord::PostgreSQLTestCase
setup do
@connection = ActiveRecord::Base.connection
@@ -531,7 +594,7 @@ class DefaultsUsingMultipleSchemasAndDomainTest < ActiveRecord::PostgreSQLTestCa
end
def test_decimal_defaults_in_new_schema_when_overriding_domain
- assert_equal BigDecimal.new("3.14159265358979323846"), Default.new.decimal_col, "Default of decimal column was not correctly parsed"
+ assert_equal BigDecimal("3.14159265358979323846"), Default.new.decimal_col, "Default of decimal column was not correctly parsed"
end
def test_bpchar_defaults_in_new_schema_when_overriding_domain
diff --git a/activerecord/test/cases/adapters/postgresql/serial_test.rb b/activerecord/test/cases/adapters/postgresql/serial_test.rb
index d711b3b729..83ea86be6d 100644
--- a/activerecord/test/cases/adapters/postgresql/serial_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/serial_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "support/schema_dumping_helper"
@@ -22,14 +24,14 @@ class PostgresqlSerialTest < ActiveRecord::PostgreSQLTestCase
column = PostgresqlSerial.columns_hash["seq"]
assert_equal :integer, column.type
assert_equal "integer", column.sql_type
- assert column.serial?
+ assert_predicate column, :serial?
end
def test_not_serial_column
column = PostgresqlSerial.columns_hash["serials_id"]
assert_equal :integer, column.type
assert_equal "integer", column.sql_type
- assert_not column.serial?
+ assert_not_predicate column, :serial?
end
def test_schema_dump_with_shorthand
@@ -64,14 +66,14 @@ class PostgresqlBigSerialTest < ActiveRecord::PostgreSQLTestCase
column = PostgresqlBigSerial.columns_hash["seq"]
assert_equal :integer, column.type
assert_equal "bigint", column.sql_type
- assert column.serial?
+ assert_predicate column, :serial?
end
def test_not_bigserial_column
column = PostgresqlBigSerial.columns_hash["serials_id"]
assert_equal :integer, column.type
assert_equal "bigint", column.sql_type
- assert_not column.serial?
+ assert_not_predicate column, :serial?
end
def test_schema_dump_with_shorthand
@@ -84,3 +86,71 @@ class PostgresqlBigSerialTest < ActiveRecord::PostgreSQLTestCase
assert_match %r{t\.bigint\s+"serials_id",\s+default: -> \{ "nextval\('postgresql_big_serials_id_seq'::regclass\)" \}$}, output
end
end
+
+module SequenceNameDetectionTestCases
+ class CollidedSequenceNameTest < ActiveRecord::PostgreSQLTestCase
+ include SchemaDumpingHelper
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+ @connection.create_table :foo_bar, force: true do |t|
+ t.serial :baz_id
+ end
+ @connection.create_table :foo, force: true do |t|
+ t.serial :bar_id
+ t.bigserial :bar_baz_id
+ end
+ end
+
+ def teardown
+ @connection.drop_table :foo_bar, if_exists: true
+ @connection.drop_table :foo, if_exists: true
+ end
+
+ def test_serial_columns
+ columns = @connection.columns(:foo)
+ columns.each do |column|
+ assert_equal :integer, column.type
+ assert_predicate column, :serial?
+ end
+ end
+
+ def test_schema_dump_with_collided_sequence_name
+ output = dump_table_schema "foo"
+ assert_match %r{t\.serial\s+"bar_id",\s+null: false$}, output
+ assert_match %r{t\.bigserial\s+"bar_baz_id",\s+null: false$}, output
+ end
+ end
+
+ class LongerSequenceNameDetectionTest < ActiveRecord::PostgreSQLTestCase
+ include SchemaDumpingHelper
+
+ def setup
+ @table_name = "long_table_name_to_test_sequence_name_detection_for_serial_cols"
+ @connection = ActiveRecord::Base.connection
+ @connection.create_table @table_name, force: true do |t|
+ t.serial :seq
+ t.bigserial :bigseq
+ end
+ end
+
+ def teardown
+ @connection.drop_table @table_name, if_exists: true
+ end
+
+ def test_serial_columns
+ columns = @connection.columns(@table_name)
+ columns.each do |column|
+ assert_equal :integer, column.type
+ assert_predicate column, :serial?
+ end
+ end
+
+ def test_schema_dump_with_long_table_name
+ output = dump_table_schema @table_name
+ assert_match %r{create_table "#{@table_name}", force: :cascade}, output
+ assert_match %r{t\.serial\s+"seq",\s+null: false$}, output
+ assert_match %r{t\.bigserial\s+"bigseq",\s+null: false$}, output
+ end
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/statement_pool_test.rb b/activerecord/test/cases/adapters/postgresql/statement_pool_test.rb
index eb9978a898..fef4b02b04 100644
--- a/activerecord/test/cases/adapters/postgresql/statement_pool_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/statement_pool_test.rb
@@ -1,15 +1,17 @@
+# frozen_string_literal: true
+
require "cases/helper"
module ActiveRecord
module ConnectionAdapters
class PostgreSQLAdapter < AbstractAdapter
- class InactivePGconn
+ class InactivePgConnection
def query(*args)
- raise PGError
+ raise PG::Error
end
def status
- PGconn::CONNECTION_BAD
+ PG::CONNECTION_BAD
end
end
@@ -21,7 +23,7 @@ module ActiveRecord
assert_equal "bar", cache["foo"]
pid = fork {
- lookup = cache["foo"];
+ lookup = cache["foo"]
exit!(!lookup)
}
@@ -31,7 +33,7 @@ module ActiveRecord
end
def test_dealloc_does_not_raise_on_inactive_connection
- cache = StatementPool.new InactivePGconn.new, 10
+ cache = StatementPool.new InactivePgConnection.new, 10
cache["foo"] = "bar"
assert_nothing_raised { cache.clear }
end
diff --git a/activerecord/test/cases/adapters/postgresql/timestamp_test.rb b/activerecord/test/cases/adapters/postgresql/timestamp_test.rb
index 962450aada..b7f213efc8 100644
--- a/activerecord/test/cases/adapters/postgresql/timestamp_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/timestamp_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/developer"
require "models/topic"
diff --git a/activerecord/test/cases/adapters/postgresql/transaction_test.rb b/activerecord/test/cases/adapters/postgresql/transaction_test.rb
index 9b42d0383d..984b2f5ea4 100644
--- a/activerecord/test/cases/adapters/postgresql/transaction_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/transaction_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "support/connection_helper"
require "concurrent/atomic/cyclic_barrier"
@@ -12,6 +14,7 @@ module ActiveRecord
setup do
@abort, Thread.abort_on_exception = Thread.abort_on_exception, false
+ Thread.report_on_exception, @original_report_on_exception = false, Thread.report_on_exception
@connection = ActiveRecord::Base.connection
@@ -29,6 +32,7 @@ module ActiveRecord
@connection.drop_table "samples", if_exists: true
Thread.abort_on_exception = @abort
+ Thread.report_on_exception = @original_report_on_exception
end
test "raises SerializationFailure when a serialization failure occurs" do
@@ -72,7 +76,7 @@ module ActiveRecord
Sample.transaction do
s1.lock!
barrier.wait
- s2.update_attributes value: 1
+ s2.update value: 1
end
end
@@ -80,7 +84,7 @@ module ActiveRecord
Sample.transaction do
s2.lock!
barrier.wait
- s1.update_attributes value: 2
+ s1.update value: 2
end
ensure
thread.join
@@ -89,6 +93,90 @@ module ActiveRecord
end
end
+ test "raises LockWaitTimeout when lock wait timeout exceeded" do
+ skip unless ActiveRecord::Base.connection.postgresql_version >= 90300
+ assert_raises(ActiveRecord::LockWaitTimeout) do
+ s = Sample.create!(value: 1)
+ latch1 = Concurrent::CountDownLatch.new
+ latch2 = Concurrent::CountDownLatch.new
+
+ thread = Thread.new do
+ Sample.transaction do
+ Sample.lock.find(s.id)
+ latch1.count_down
+ latch2.wait
+ end
+ end
+
+ begin
+ Sample.transaction do
+ latch1.wait
+ Sample.connection.execute("SET lock_timeout = 1")
+ Sample.lock.find(s.id)
+ end
+ ensure
+ Sample.connection.execute("SET lock_timeout = DEFAULT")
+ latch2.count_down
+ thread.join
+ end
+ end
+ end
+
+ test "raises QueryCanceled when statement timeout exceeded" do
+ assert_raises(ActiveRecord::QueryCanceled) do
+ s = Sample.create!(value: 1)
+ latch1 = Concurrent::CountDownLatch.new
+ latch2 = Concurrent::CountDownLatch.new
+
+ thread = Thread.new do
+ Sample.transaction do
+ Sample.lock.find(s.id)
+ latch1.count_down
+ latch2.wait
+ end
+ end
+
+ begin
+ Sample.transaction do
+ latch1.wait
+ Sample.connection.execute("SET statement_timeout = 1")
+ Sample.lock.find(s.id)
+ end
+ ensure
+ Sample.connection.execute("SET statement_timeout = DEFAULT")
+ latch2.count_down
+ thread.join
+ end
+ end
+ end
+
+ test "raises QueryCanceled when canceling statement due to user request" do
+ assert_raises(ActiveRecord::QueryCanceled) do
+ s = Sample.create!(value: 1)
+ latch = Concurrent::CountDownLatch.new
+
+ thread = Thread.new do
+ Sample.transaction do
+ Sample.lock.find(s.id)
+ latch.count_down
+ sleep(0.5)
+ conn = Sample.connection
+ pid = conn.query_value("SELECT pid FROM pg_stat_activity WHERE query LIKE '% FOR UPDATE'")
+ conn.execute("SELECT pg_cancel_backend(#{pid})")
+ end
+ end
+
+ begin
+ Sample.transaction do
+ latch.wait
+ Sample.lock.find(s.id)
+ end
+ ensure
+ thread.join
+ end
+ end
+ end
+
private
def with_warning_suppression
diff --git a/activerecord/test/cases/adapters/postgresql/type_lookup_test.rb b/activerecord/test/cases/adapters/postgresql/type_lookup_test.rb
index 784d77a8d1..8212ed4263 100644
--- a/activerecord/test/cases/adapters/postgresql/type_lookup_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/type_lookup_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
class PostgresqlTypeLookupTest < ActiveRecord::PostgreSQLTestCase
@@ -6,16 +8,16 @@ class PostgresqlTypeLookupTest < ActiveRecord::PostgreSQLTestCase
end
test "array delimiters are looked up correctly" do
- box_array = @connection.type_map.lookup(1020)
- int_array = @connection.type_map.lookup(1007)
+ box_array = @connection.send(:type_map).lookup(1020)
+ int_array = @connection.send(:type_map).lookup(1007)
assert_equal ";", box_array.delimiter
assert_equal ",", int_array.delimiter
end
test "array types correctly respect registration of subtypes" do
- int_array = @connection.type_map.lookup(1007, -1, "integer[]")
- bigint_array = @connection.type_map.lookup(1016, -1, "bigint[]")
+ int_array = @connection.send(:type_map).lookup(1007, -1, "integer[]")
+ bigint_array = @connection.send(:type_map).lookup(1016, -1, "bigint[]")
big_array = [123456789123456789]
assert_raises(ActiveModel::RangeError) { int_array.serialize(big_array) }
@@ -23,11 +25,11 @@ class PostgresqlTypeLookupTest < ActiveRecord::PostgreSQLTestCase
end
test "range types correctly respect registration of subtypes" do
- int_range = @connection.type_map.lookup(3904, -1, "int4range")
- bigint_range = @connection.type_map.lookup(3926, -1, "int8range")
+ int_range = @connection.send(:type_map).lookup(3904, -1, "int4range")
+ bigint_range = @connection.send(:type_map).lookup(3926, -1, "int8range")
big_range = 0..123456789123456789
assert_raises(ActiveModel::RangeError) { int_range.serialize(big_range) }
- assert_equal "[0,123456789123456789]", bigint_range.serialize(big_range)
+ assert_equal "[0,123456789123456789]", @connection.type_cast(bigint_range.serialize(big_range))
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/utils_test.rb b/activerecord/test/cases/adapters/postgresql/utils_test.rb
index 9f9e3bda2f..c91884f384 100644
--- a/activerecord/test/cases/adapters/postgresql/utils_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/utils_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "active_record/connection_adapters/postgresql/utils"
diff --git a/activerecord/test/cases/adapters/postgresql/uuid_test.rb b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
index 6aa6a79705..71d07e2f4c 100644
--- a/activerecord/test/cases/adapters/postgresql/uuid_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "support/schema_dumping_helper"
@@ -13,6 +15,10 @@ module PostgresqlUUIDHelper
def uuid_function
connection.supports_pgcrypto_uuid? ? "gen_random_uuid()" : "uuid_generate_v4()"
end
+
+ def uuid_default
+ connection.supports_pgcrypto_uuid? ? {} : { default: uuid_function }
+ end
end
class PostgresqlUUIDTest < ActiveRecord::PostgreSQLTestCase
@@ -36,7 +42,8 @@ class PostgresqlUUIDTest < ActiveRecord::PostgreSQLTestCase
drop_table "uuid_data_type"
end
- if ActiveRecord::Base.connection.supports_pgcrypto_uuid?
+ if ActiveRecord::Base.connection.respond_to?(:supports_pgcrypto_uuid?) &&
+ ActiveRecord::Base.connection.supports_pgcrypto_uuid?
def test_uuid_column_default
connection.add_column :uuid_data_type, :thingy, :uuid, null: false, default: "gen_random_uuid()"
UUIDType.reset_column_information
@@ -59,14 +66,37 @@ class PostgresqlUUIDTest < ActiveRecord::PostgreSQLTestCase
UUIDType.reset_column_information
end
+ def test_add_column_with_null_true_and_default_nil
+ connection.add_column :uuid_data_type, :thingy, :uuid, null: true, default: nil
+
+ UUIDType.reset_column_information
+ column = UUIDType.columns_hash["thingy"]
+
+ assert column.null
+ assert_nil column.default
+ end
+
+ def test_add_column_with_default_array
+ connection.add_column :uuid_data_type, :thingy, :uuid, array: true, default: []
+
+ UUIDType.reset_column_information
+ column = UUIDType.columns_hash["thingy"]
+
+ assert_predicate column, :array?
+ assert_equal "{}", column.default
+
+ schema = dump_table_schema "uuid_data_type"
+ assert_match %r{t\.uuid "thingy", default: \[\], array: true$}, schema
+ end
+
def test_data_type_of_uuid_types
column = UUIDType.columns_hash["guid"]
assert_equal :uuid, column.type
assert_equal "uuid", column.sql_type
- assert_not column.array?
+ assert_not_predicate column, :array?
type = UUIDType.type_for_attribute("guid")
- assert_not type.binary?
+ assert_not_predicate type, :binary?
end
def test_treat_blank_uuid_as_nil
@@ -109,7 +139,9 @@ class PostgresqlUUIDTest < ActiveRecord::PostgreSQLTestCase
"Z0000C99-9C0B-4EF8-BB6D-6BB9BD380A11",
"a0eebc999r0b4ef8ab6d6bb9bd380a11",
"a0ee-bc99------4ef8-bb6d-6bb9-bd38-0a11",
- "{a0eebc99-bb6d6bb9-bd380a11}"].each do |invalid_uuid|
+ "{a0eebc99-bb6d6bb9-bd380a11}",
+ "{a0eebc99-9c0b4ef8-bb6d6bb9-bd380a11",
+ "a0eebc99-9c0b4ef8-bb6d6bb9-bd380a11}"].each do |invalid_uuid|
uuid = UUIDType.new guid: invalid_uuid
assert_nil uuid.guid
end
@@ -146,7 +178,7 @@ class PostgresqlUUIDTest < ActiveRecord::PostgreSQLTestCase
duplicate = klass.new(guid: record.guid)
assert record.guid.present? # Ensure we actually are testing a UUID
- assert_not duplicate.valid?
+ assert_not_predicate duplicate, :valid?
end
end
@@ -178,7 +210,7 @@ class PostgresqlUUIDGenerationTest < ActiveRecord::PostgreSQLTestCase
t.uuid "other_uuid_2", default: "my_uuid_generator()"
end
- connection.create_table("pg_uuids_3", id: :uuid) do |t|
+ connection.create_table("pg_uuids_3", id: :uuid, **uuid_default) do |t|
t.string "name"
end
end
@@ -190,68 +222,66 @@ class PostgresqlUUIDGenerationTest < ActiveRecord::PostgreSQLTestCase
connection.execute "DROP FUNCTION IF EXISTS my_uuid_generator();"
end
- if ActiveRecord::Base.connection.supports_extensions?
- def test_id_is_uuid
- assert_equal :uuid, UUID.columns_hash["id"].type
- assert UUID.primary_key
- end
+ def test_id_is_uuid
+ assert_equal :uuid, UUID.columns_hash["id"].type
+ assert UUID.primary_key
+ end
- def test_id_has_a_default
- u = UUID.create
- assert_not_nil u.id
- end
+ def test_id_has_a_default
+ u = UUID.create
+ assert_not_nil u.id
+ end
- def test_auto_create_uuid
- u = UUID.create
- u.reload
- assert_not_nil u.other_uuid
- end
+ def test_auto_create_uuid
+ u = UUID.create
+ u.reload
+ assert_not_nil u.other_uuid
+ end
- def test_pk_and_sequence_for_uuid_primary_key
- pk, seq = connection.pk_and_sequence_for("pg_uuids")
- assert_equal "id", pk
- assert_nil seq
- end
+ def test_pk_and_sequence_for_uuid_primary_key
+ pk, seq = connection.pk_and_sequence_for("pg_uuids")
+ assert_equal "id", pk
+ assert_nil seq
+ end
- def test_schema_dumper_for_uuid_primary_key
- schema = dump_table_schema "pg_uuids"
- assert_match(/\bcreate_table "pg_uuids", id: :uuid, default: -> { "uuid_generate_v1\(\)" }/, schema)
- assert_match(/t\.uuid "other_uuid", default: -> { "uuid_generate_v4\(\)" }/, schema)
- end
+ def test_schema_dumper_for_uuid_primary_key
+ schema = dump_table_schema "pg_uuids"
+ assert_match(/\bcreate_table "pg_uuids", id: :uuid, default: -> { "uuid_generate_v1\(\)" }/, schema)
+ assert_match(/t\.uuid "other_uuid", default: -> { "uuid_generate_v4\(\)" }/, schema)
+ end
+
+ def test_schema_dumper_for_uuid_primary_key_with_custom_default
+ schema = dump_table_schema "pg_uuids_2"
+ assert_match(/\bcreate_table "pg_uuids_2", id: :uuid, default: -> { "my_uuid_generator\(\)" }/, schema)
+ assert_match(/t\.uuid "other_uuid_2", default: -> { "my_uuid_generator\(\)" }/, schema)
+ end
- def test_schema_dumper_for_uuid_primary_key_with_custom_default
- schema = dump_table_schema "pg_uuids_2"
- assert_match(/\bcreate_table "pg_uuids_2", id: :uuid, default: -> { "my_uuid_generator\(\)" }/, schema)
- assert_match(/t\.uuid "other_uuid_2", default: -> { "my_uuid_generator\(\)" }/, schema)
+ def test_schema_dumper_for_uuid_primary_key_default
+ schema = dump_table_schema "pg_uuids_3"
+ if connection.supports_pgcrypto_uuid?
+ assert_match(/\bcreate_table "pg_uuids_3", id: :uuid, default: -> { "gen_random_uuid\(\)" }/, schema)
+ else
+ assert_match(/\bcreate_table "pg_uuids_3", id: :uuid, default: -> { "uuid_generate_v4\(\)" }/, schema)
end
+ end
+
+ def test_schema_dumper_for_uuid_primary_key_default_in_legacy_migration
+ @verbose_was = ActiveRecord::Migration.verbose
+ ActiveRecord::Migration.verbose = false
- def test_schema_dumper_for_uuid_primary_key_default
- schema = dump_table_schema "pg_uuids_3"
- if connection.supports_pgcrypto_uuid?
- assert_match(/\bcreate_table "pg_uuids_3", id: :uuid, default: -> { "gen_random_uuid\(\)" }/, schema)
- else
- assert_match(/\bcreate_table "pg_uuids_3", id: :uuid, default: -> { "uuid_generate_v4\(\)" }/, schema)
+ migration = Class.new(ActiveRecord::Migration[5.0]) do
+ def version; 101 end
+ def migrate(x)
+ create_table("pg_uuids_4", id: :uuid)
end
- end
+ end.new
+ ActiveRecord::Migrator.new(:up, [migration]).migrate
- def test_schema_dumper_for_uuid_primary_key_default_in_legacy_migration
- @verbose_was = ActiveRecord::Migration.verbose
- ActiveRecord::Migration.verbose = false
-
- migration = Class.new(ActiveRecord::Migration[5.0]) do
- def version; 101 end
- def migrate(x)
- create_table("pg_uuids_4", id: :uuid)
- end
- end.new
- ActiveRecord::Migrator.new(:up, [migration]).migrate
-
- schema = dump_table_schema "pg_uuids_4"
- assert_match(/\bcreate_table "pg_uuids_4", id: :uuid, default: -> { "uuid_generate_v4\(\)" }/, schema)
- ensure
- drop_table "pg_uuids_4"
- ActiveRecord::Migration.verbose = @verbose_was
- end
+ schema = dump_table_schema "pg_uuids_4"
+ assert_match(/\bcreate_table "pg_uuids_4", id: :uuid, default: -> { "uuid_generate_v4\(\)" }/, schema)
+ ensure
+ drop_table "pg_uuids_4"
+ ActiveRecord::Migration.verbose = @verbose_was
end
end
@@ -270,38 +300,36 @@ class PostgresqlUUIDTestNilDefault < ActiveRecord::PostgreSQLTestCase
drop_table "pg_uuids"
end
- if ActiveRecord::Base.connection.supports_extensions?
- def test_id_allows_default_override_via_nil
- col_desc = connection.execute("SELECT pg_get_expr(d.adbin, d.adrelid) as default
- FROM pg_attribute a
- LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum
- WHERE a.attname='id' AND a.attrelid = 'pg_uuids'::regclass").first
- assert_nil col_desc["default"]
- end
+ def test_id_allows_default_override_via_nil
+ col_desc = connection.execute("SELECT pg_get_expr(d.adbin, d.adrelid) as default
+ FROM pg_attribute a
+ LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum
+ WHERE a.attname='id' AND a.attrelid = 'pg_uuids'::regclass").first
+ assert_nil col_desc["default"]
+ end
- def test_schema_dumper_for_uuid_primary_key_with_default_override_via_nil
- schema = dump_table_schema "pg_uuids"
- assert_match(/\bcreate_table "pg_uuids", id: :uuid, default: nil/, schema)
- end
+ def test_schema_dumper_for_uuid_primary_key_with_default_override_via_nil
+ schema = dump_table_schema "pg_uuids"
+ assert_match(/\bcreate_table "pg_uuids", id: :uuid, default: nil/, schema)
+ end
- def test_schema_dumper_for_uuid_primary_key_with_default_nil_in_legacy_migration
- @verbose_was = ActiveRecord::Migration.verbose
- ActiveRecord::Migration.verbose = false
-
- migration = Class.new(ActiveRecord::Migration[5.0]) do
- def version; 101 end
- def migrate(x)
- create_table("pg_uuids_4", id: :uuid, default: nil)
- end
- end.new
- ActiveRecord::Migrator.new(:up, [migration]).migrate
-
- schema = dump_table_schema "pg_uuids_4"
- assert_match(/\bcreate_table "pg_uuids_4", id: :uuid, default: nil/, schema)
- ensure
- drop_table "pg_uuids_4"
- ActiveRecord::Migration.verbose = @verbose_was
- end
+ def test_schema_dumper_for_uuid_primary_key_with_default_nil_in_legacy_migration
+ @verbose_was = ActiveRecord::Migration.verbose
+ ActiveRecord::Migration.verbose = false
+
+ migration = Class.new(ActiveRecord::Migration[5.0]) do
+ def version; 101 end
+ def migrate(x)
+ create_table("pg_uuids_4", id: :uuid, default: nil)
+ end
+ end.new
+ ActiveRecord::Migrator.new(:up, [migration]).migrate
+
+ schema = dump_table_schema "pg_uuids_4"
+ assert_match(/\bcreate_table "pg_uuids_4", id: :uuid, default: nil/, schema)
+ ensure
+ drop_table "pg_uuids_4"
+ ActiveRecord::Migration.verbose = @verbose_was
end
end
@@ -320,10 +348,10 @@ class PostgresqlUUIDTestInverseOf < ActiveRecord::PostgreSQLTestCase
setup do
connection.transaction do
- connection.create_table("pg_uuid_posts", id: :uuid) do |t|
+ connection.create_table("pg_uuid_posts", id: :uuid, **uuid_default) do |t|
t.string "title"
end
- connection.create_table("pg_uuid_comments", id: :uuid) do |t|
+ connection.create_table("pg_uuid_comments", id: :uuid, **uuid_default) do |t|
t.references :uuid_post, type: :uuid
t.string "content"
end
@@ -335,23 +363,21 @@ class PostgresqlUUIDTestInverseOf < ActiveRecord::PostgreSQLTestCase
drop_table "pg_uuid_posts"
end
- if ActiveRecord::Base.connection.supports_extensions?
- def test_collection_association_with_uuid
- post = UuidPost.create!
- comment = post.uuid_comments.create!
- assert post.uuid_comments.find(comment.id)
- end
+ def test_collection_association_with_uuid
+ post = UuidPost.create!
+ comment = post.uuid_comments.create!
+ assert post.uuid_comments.find(comment.id)
+ end
- def test_find_with_uuid
- UuidPost.create!
- assert_raise ActiveRecord::RecordNotFound do
- UuidPost.find(123456)
- end
+ def test_find_with_uuid
+ UuidPost.create!
+ assert_raise ActiveRecord::RecordNotFound do
+ UuidPost.find(123456)
end
+ end
- def test_find_by_with_uuid
- UuidPost.create!
- assert_nil UuidPost.find_by(id: 789)
- end
+ def test_find_by_with_uuid
+ UuidPost.create!
+ assert_nil UuidPost.find_by(id: 789)
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/xml_test.rb b/activerecord/test/cases/adapters/postgresql/xml_test.rb
index 826b384fb3..71ead6f7f3 100644
--- a/activerecord/test/cases/adapters/postgresql/xml_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/xml_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "support/schema_dumping_helper"
diff --git a/activerecord/test/cases/adapters/sqlite3/collation_test.rb b/activerecord/test/cases/adapters/sqlite3/collation_test.rb
index 28e8f12c18..76c8f7d8dd 100644
--- a/activerecord/test/cases/adapters/sqlite3/collation_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/collation_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "support/schema_dumping_helper"
@@ -47,7 +49,7 @@ class SQLite3CollationTest < ActiveRecord::SQLite3TestCase
test "schema dump includes collation" do
output = dump_table_schema("collation_table_sqlite3")
- assert_match %r{t.string\s+"string_nocase",\s+collation: "NOCASE"$}, output
- assert_match %r{t.text\s+"text_rtrim",\s+collation: "RTRIM"$}, output
+ assert_match %r{t\.string\s+"string_nocase",\s+collation: "NOCASE"$}, output
+ assert_match %r{t\.text\s+"text_rtrim",\s+collation: "RTRIM"$}, output
end
end
diff --git a/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb b/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb
index e1cfd703e8..ffb1d6afce 100644
--- a/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
class CopyTableTest < ActiveRecord::SQLite3TestCase
diff --git a/activerecord/test/cases/adapters/sqlite3/explain_test.rb b/activerecord/test/cases/adapters/sqlite3/explain_test.rb
index 128acb79cf..b6d2ccdb53 100644
--- a/activerecord/test/cases/adapters/sqlite3/explain_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/explain_test.rb
@@ -1,21 +1,23 @@
+# frozen_string_literal: true
+
require "cases/helper"
-require "models/developer"
-require "models/computer"
+require "models/author"
+require "models/post"
class SQLite3ExplainTest < ActiveRecord::SQLite3TestCase
- fixtures :developers
+ fixtures :authors
def test_explain_for_one_query
- explain = Developer.where(id: 1).explain
- assert_match %r(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = (?:\? \[\["id", 1\]\]|1)), explain
- assert_match(/(SEARCH )?TABLE developers USING (INTEGER )?PRIMARY KEY/, explain)
+ explain = Author.where(id: 1).explain
+ assert_match %r(EXPLAIN for: SELECT "authors"\.\* FROM "authors" WHERE "authors"\."id" = (?:\? \[\["id", 1\]\]|1)), explain
+ assert_match(/(SEARCH )?TABLE authors USING (INTEGER )?PRIMARY KEY/, explain)
end
def test_explain_with_eager_loading
- explain = Developer.where(id: 1).includes(:audit_logs).explain
- assert_match %r(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = (?:\? \[\["id", 1\]\]|1)), explain
- assert_match(/(SEARCH )?TABLE developers USING (INTEGER )?PRIMARY KEY/, explain)
- assert_match %(EXPLAIN for: SELECT "audit_logs".* FROM "audit_logs" WHERE "audit_logs"."developer_id" = 1), explain
- assert_match(/(SCAN )?TABLE audit_logs/, explain)
+ explain = Author.where(id: 1).includes(:posts).explain
+ assert_match %r(EXPLAIN for: SELECT "authors"\.\* FROM "authors" WHERE "authors"\."id" = (?:\? \[\["id", 1\]\]|1)), explain
+ assert_match(/(SEARCH )?TABLE authors USING (INTEGER )?PRIMARY KEY/, explain)
+ assert_match %r(EXPLAIN for: SELECT "posts"\.\* FROM "posts" WHERE "posts"\."author_id" = (?:\? \[\["author_id", 1\]\]|1)), explain
+ assert_match(/(SCAN )?TABLE posts/, explain)
end
end
diff --git a/activerecord/test/cases/adapters/sqlite3/json_test.rb b/activerecord/test/cases/adapters/sqlite3/json_test.rb
new file mode 100644
index 0000000000..6f247fcd22
--- /dev/null
+++ b/activerecord/test/cases/adapters/sqlite3/json_test.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+require "cases/helper"
+require "cases/json_shared_test_cases"
+
+class SQLite3JSONTest < ActiveRecord::SQLite3TestCase
+ include JSONSharedTestCases
+
+ def setup
+ super
+ @connection.create_table("json_data_type") do |t|
+ t.json "payload", default: {}
+ t.json "settings"
+ end
+ end
+
+ def test_default
+ @connection.add_column "json_data_type", "permissions", column_type, default: { "users": "read", "posts": ["read", "write"] }
+ klass.reset_column_information
+
+ assert_equal({ "users" => "read", "posts" => ["read", "write"] }, klass.column_defaults["permissions"])
+ assert_equal({ "users" => "read", "posts" => ["read", "write"] }, klass.new.permissions)
+ end
+
+ private
+ def column_type
+ :json
+ end
+end
diff --git a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
index aefbb309e6..40b58e86bf 100644
--- a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "bigdecimal"
require "securerandom"
@@ -5,6 +7,11 @@ require "securerandom"
class SQLite3QuotingTest < ActiveRecord::SQLite3TestCase
def setup
@conn = ActiveRecord::Base.connection
+ @initial_represent_boolean_as_integer = ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer
+ end
+
+ def teardown
+ ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer = @initial_represent_boolean_as_integer
end
def test_type_cast_binary_encoding_without_logger
@@ -15,15 +22,23 @@ class SQLite3QuotingTest < ActiveRecord::SQLite3TestCase
end
def test_type_cast_true
+ ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer = false
assert_equal "t", @conn.type_cast(true)
+
+ ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer = true
+ assert_equal 1, @conn.type_cast(true)
end
def test_type_cast_false
+ ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer = false
assert_equal "f", @conn.type_cast(false)
+
+ ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer = true
+ assert_equal 0, @conn.type_cast(false)
end
def test_type_cast_bigdecimal
- bd = BigDecimal.new "10.0"
+ bd = BigDecimal "10.0"
assert_equal bd.to_f, @conn.type_cast(bd)
end
@@ -40,4 +55,37 @@ class SQLite3QuotingTest < ActiveRecord::SQLite3TestCase
assert_equal "'2000-01-01 12:30:00.999999'", @conn.quote(type.serialize(value))
end
+
+ def test_quoted_time_normalizes_date_qualified_time
+ value = ::Time.utc(2018, 3, 11, 12, 30, 0, 999999)
+ type = ActiveRecord::Type::Time.new
+
+ assert_equal "'2000-01-01 12:30:00.999999'", @conn.quote(type.serialize(value))
+ end
+
+ def test_quoted_time_dst_utc
+ with_env_tz "America/New_York" do
+ with_timezone_config default: :utc do
+ t = Time.new(2000, 7, 1, 0, 0, 0, "+04:30")
+
+ expected = t.change(year: 2000, month: 1, day: 1)
+ expected = expected.getutc.to_s(:db).sub(/\A\d\d\d\d-\d\d-\d\d /, "2000-01-01 ")
+
+ assert_equal expected, @conn.quoted_time(t)
+ end
+ end
+ end
+
+ def test_quoted_time_dst_local
+ with_env_tz "America/New_York" do
+ with_timezone_config default: :local do
+ t = Time.new(2000, 7, 1, 0, 0, 0, "+04:30")
+
+ expected = t.change(year: 2000, month: 1, day: 1)
+ expected = expected.getlocal.to_s(:db).sub(/\A\d\d\d\d-\d\d-\d\d /, "2000-01-01 ")
+
+ assert_equal expected, @conn.quoted_time(t)
+ end
+ end
+ end
end
diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
index a6afb7816b..d1d4d545a3 100644
--- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/owner"
require "tempfile"
@@ -49,22 +51,6 @@ module ActiveRecord
end
end
- def test_valid_column
- with_example_table do
- column = @conn.columns("ex").find { |col| col.name == "id" }
- assert @conn.valid_type?(column.type)
- end
- end
-
- # sqlite3 databases should be able to support any type and not just the
- # ones mentioned in the native_database_types.
- #
- # Therefore test_invalid column should always return true even if the
- # type is not valid.
- def test_invalid_column
- assert @conn.valid_type?(:foobar)
- end
-
def test_column_types
owner = Owner.create!(name: "hello".encode("ascii-8bit"))
owner.reload
@@ -75,7 +61,7 @@ module ActiveRecord
WHERE #{Owner.primary_key} = #{owner.id}
esql
- assert(!result.rows.first.include?("blob"), "should not store blobs")
+ assert_not(result.rows.first.include?("blob"), "should not store blobs")
ensure
owner.delete
end
@@ -181,7 +167,7 @@ module ActiveRecord
data binary
)
eosql
- str = "\x80".force_encoding("ASCII-8BIT")
+ str = "\x80".dup.force_encoding("ASCII-8BIT")
binary = DualEncoding.new name: "いただきます!", data: str
binary.save!
assert_equal str, binary.data
@@ -190,7 +176,7 @@ module ActiveRecord
end
def test_type_cast_should_not_mutate_encoding
- name = "hello".force_encoding(Encoding::ASCII_8BIT)
+ name = "hello".dup.force_encoding(Encoding::ASCII_8BIT)
Owner.create(name: name)
assert_equal Encoding::ASCII_8BIT, name.encoding
ensure
@@ -276,27 +262,17 @@ module ActiveRecord
def test_tables_logs_name
sql = <<-SQL
- SELECT name FROM sqlite_master
- WHERE type = 'table' AND name <> 'sqlite_sequence'
+ SELECT name FROM sqlite_master WHERE name <> 'sqlite_sequence' AND type IN ('table')
SQL
assert_logged [[sql.squish, "SCHEMA", []]] do
@conn.tables
end
end
- def test_indexes_logs_name
- with_example_table do
- assert_logged [["PRAGMA index_list(\"ex\")", "SCHEMA", []]] do
- assert_deprecated { @conn.indexes("ex", "hello") }
- end
- end
- end
-
def test_table_exists_logs_name
with_example_table do
sql = <<-SQL
- SELECT name FROM sqlite_master
- WHERE type = 'table' AND name <> 'sqlite_sequence' AND name = 'ex'
+ SELECT name FROM sqlite_master WHERE name <> 'sqlite_sequence' AND name = 'ex' AND type IN ('table')
SQL
assert_logged [[sql.squish, "SCHEMA", []]] do
assert @conn.table_exists?("ex")
@@ -384,16 +360,126 @@ module ActiveRecord
end
end
+ class Barcode < ActiveRecord::Base
+ self.primary_key = "code"
+ end
+
+ def test_copy_table_with_existing_records_have_custom_primary_key
+ connection = Barcode.connection
+ connection.create_table(:barcodes, primary_key: "code", id: :string, limit: 42, force: true) do |t|
+ t.text :other_attr
+ end
+ code = "214fe0c2-dd47-46df-b53b-66090b3c1d40"
+ Barcode.create!(code: code, other_attr: "xxx")
+
+ connection.remove_column("barcodes", "other_attr")
+
+ assert_equal code, Barcode.first.id
+ ensure
+ Barcode.reset_column_information
+ end
+
+ def test_copy_table_with_composite_primary_keys
+ connection = Barcode.connection
+ connection.create_table(:barcodes, primary_key: ["region", "code"], force: true) do |t|
+ t.string :region
+ t.string :code
+ t.text :other_attr
+ end
+ region = "US"
+ code = "214fe0c2-dd47-46df-b53b-66090b3c1d40"
+ Barcode.create!(region: region, code: code, other_attr: "xxx")
+
+ connection.remove_column("barcodes", "other_attr")
+
+ assert_equal ["region", "code"], connection.primary_keys("barcodes")
+
+ barcode = Barcode.first
+ assert_equal region, barcode.region
+ assert_equal code, barcode.code
+ ensure
+ Barcode.reset_column_information
+ end
+
+ def test_custom_primary_key_in_create_table
+ connection = Barcode.connection
+ connection.create_table :barcodes, id: false, force: true do |t|
+ t.primary_key :id, :string
+ end
+
+ assert_equal "id", connection.primary_key("barcodes")
+
+ custom_pk = Barcode.columns_hash["id"]
+
+ assert_equal :string, custom_pk.type
+ assert_not custom_pk.null
+ ensure
+ Barcode.reset_column_information
+ end
+
+ def test_custom_primary_key_in_change_table
+ connection = Barcode.connection
+ connection.create_table :barcodes, id: false, force: true do |t|
+ t.integer :dummy
+ end
+ connection.change_table :barcodes do |t|
+ t.primary_key :id, :string
+ end
+
+ assert_equal "id", connection.primary_key("barcodes")
+
+ custom_pk = Barcode.columns_hash["id"]
+
+ assert_equal :string, custom_pk.type
+ assert_not custom_pk.null
+ ensure
+ Barcode.reset_column_information
+ end
+
+ def test_add_column_with_custom_primary_key
+ connection = Barcode.connection
+ connection.create_table :barcodes, id: false, force: true do |t|
+ t.integer :dummy
+ end
+ connection.add_column :barcodes, :id, :string, primary_key: true
+
+ assert_equal "id", connection.primary_key("barcodes")
+
+ custom_pk = Barcode.columns_hash["id"]
+
+ assert_equal :string, custom_pk.type
+ assert_not custom_pk.null
+ ensure
+ Barcode.reset_column_information
+ end
+
+ def test_remove_column_preserves_partial_indexes
+ connection = Barcode.connection
+ connection.create_table :barcodes, force: true do |t|
+ t.string :code
+ t.string :region
+ t.boolean :bool_attr
+
+ t.index :code, unique: true, where: :bool_attr, name: "partial"
+ end
+ connection.remove_column :barcodes, :region
+
+ index = connection.indexes("barcodes").find { |idx| idx.name == "partial" }
+ assert_equal "bool_attr", index.where
+ ensure
+ Barcode.reset_column_information
+ end
+
def test_supports_extensions
assert_not @conn.supports_extensions?, "does not support extensions"
end
def test_respond_to_enable_extension
- assert @conn.respond_to?(:enable_extension)
+ assert_respond_to @conn, :enable_extension
end
def test_respond_to_disable_extension
- assert @conn.respond_to?(:disable_extension)
+ assert_respond_to @conn, :disable_extension
end
def test_statement_closed
@@ -414,6 +500,43 @@ module ActiveRecord
end
end
+ def test_deprecate_valid_alter_table_type
+ assert_deprecated { @conn.valid_alter_table_type?(:string) }
+ end
+
+ def test_db_is_not_readonly_when_readonly_option_is_false
+ conn = Base.sqlite3_connection database: ":memory:",
+ adapter: "sqlite3",
+ readonly: false
+
+ assert_not_predicate conn.raw_connection, :readonly?
+ end
+
+ def test_db_is_not_readonly_when_readonly_option_is_unspecified
+ conn = Base.sqlite3_connection database: ":memory:",
+ adapter: "sqlite3"
+
+ assert_not_predicate conn.raw_connection, :readonly?
+ end
+
+ def test_db_is_readonly_when_readonly_option_is_true
+ conn = Base.sqlite3_connection database: ":memory:",
+ adapter: "sqlite3",
+ readonly: true
+
+ assert_predicate conn.raw_connection, :readonly?
+ end
+
+ def test_writes_are_not_permitted_to_readonly_databases
+ conn = Base.sqlite3_connection database: ":memory:",
+ adapter: "sqlite3",
+ readonly: true
+
+ assert_raises(ActiveRecord::StatementInvalid, /SQLite3::ReadOnlyException/) do
+ conn.execute("CREATE TABLE test(id integer)")
+ end
+ end
+
private
def assert_logged(logs)
diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_create_folder_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_create_folder_test.rb
index b1b4463bf1..d70486605f 100644
--- a/activerecord/test/cases/adapters/sqlite3/sqlite3_create_folder_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_create_folder_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/owner"
diff --git a/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb b/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb
index 37ff973397..61002435a4 100644
--- a/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
class SQLite3StatementPoolTest < ActiveRecord::SQLite3TestCase
@@ -8,7 +10,7 @@ class SQLite3StatementPoolTest < ActiveRecord::SQLite3TestCase
assert_equal "bar", cache["foo"]
pid = fork {
- lookup = cache["foo"];
+ lookup = cache["foo"]
exit!(!lookup)
}
diff --git a/activerecord/test/cases/aggregations_test.rb b/activerecord/test/cases/aggregations_test.rb
index f8136fde72..fbdf2ada4b 100644
--- a/activerecord/test/cases/aggregations_test.rb
+++ b/activerecord/test/cases/aggregations_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/customer"
@@ -25,7 +27,7 @@ class AggregationsTest < ActiveRecord::TestCase
def test_immutable_value_objects
customers(:david).balance = Money.new(100)
- assert_raise(RuntimeError) { customers(:david).balance.instance_eval { @amount = 20 } }
+ assert_raise(frozen_error_class) { customers(:david).balance.instance_eval { @amount = 20 } }
end
def test_inferred_mapping
diff --git a/activerecord/test/cases/ar_schema_test.rb b/activerecord/test/cases/ar_schema_test.rb
index 5b608d8e83..f05dcac7dd 100644
--- a/activerecord/test/cases/ar_schema_test.rb
+++ b/activerecord/test/cases/ar_schema_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
class ActiveRecordSchemaTest < ActiveRecord::TestCase
@@ -46,7 +48,7 @@ class ActiveRecordSchemaTest < ActiveRecord::TestCase
assert_nothing_raised { @connection.select_all "SELECT * FROM fruits" }
assert_nothing_raised { @connection.select_all "SELECT * FROM schema_migrations" }
- assert_equal 7, ActiveRecord::Migrator::current_version
+ assert_equal 7, @connection.migration_context.current_version
end
def test_schema_define_w_table_name_prefix
@@ -62,7 +64,7 @@ class ActiveRecordSchemaTest < ActiveRecord::TestCase
t.column :flavor, :string
end
end
- assert_equal 7, ActiveRecord::Migrator::current_version
+ assert_equal 7, @connection.migration_context.current_version
ensure
ActiveRecord::Base.table_name_prefix = old_table_name_prefix
ActiveRecord::SchemaMigration.table_name = table_name
@@ -114,8 +116,8 @@ class ActiveRecordSchemaTest < ActiveRecord::TestCase
end
end
- assert !@connection.columns(:has_timestamps).find { |c| c.name == "created_at" }.null
- assert !@connection.columns(:has_timestamps).find { |c| c.name == "updated_at" }.null
+ assert_not @connection.columns(:has_timestamps).find { |c| c.name == "created_at" }.null
+ assert_not @connection.columns(:has_timestamps).find { |c| c.name == "updated_at" }.null
end
def test_timestamps_without_null_set_null_to_false_on_change_table
@@ -127,8 +129,8 @@ class ActiveRecordSchemaTest < ActiveRecord::TestCase
end
end
- assert !@connection.columns(:has_timestamps).find { |c| c.name == "created_at" }.null
- assert !@connection.columns(:has_timestamps).find { |c| c.name == "updated_at" }.null
+ assert_not @connection.columns(:has_timestamps).find { |c| c.name == "created_at" }.null
+ assert_not @connection.columns(:has_timestamps).find { |c| c.name == "updated_at" }.null
end
def test_timestamps_without_null_set_null_to_false_on_add_timestamps
@@ -137,7 +139,7 @@ class ActiveRecordSchemaTest < ActiveRecord::TestCase
add_timestamps :has_timestamps, default: Time.now
end
- assert !@connection.columns(:has_timestamps).find { |c| c.name == "created_at" }.null
- assert !@connection.columns(:has_timestamps).find { |c| c.name == "updated_at" }.null
+ assert_not @connection.columns(:has_timestamps).find { |c| c.name == "created_at" }.null
+ assert_not @connection.columns(:has_timestamps).find { |c| c.name == "updated_at" }.null
end
end
diff --git a/activerecord/test/cases/arel/attributes/attribute_test.rb b/activerecord/test/cases/arel/attributes/attribute_test.rb
new file mode 100644
index 0000000000..671e273543
--- /dev/null
+++ b/activerecord/test/cases/arel/attributes/attribute_test.rb
@@ -0,0 +1,1015 @@
+# frozen_string_literal: true
+
+require_relative "../helper"
+require "ostruct"
+
+module Arel
+ module Attributes
+ class AttributeTest < Arel::Spec
+ describe "#not_eq" do
+ it "should create a NotEqual node" do
+ relation = Table.new(:users)
+ relation[:id].not_eq(10).must_be_kind_of Nodes::NotEqual
+ end
+
+ it "should generate != in sql" do
+ relation = Table.new(:users)
+ mgr = relation.project relation[:id]
+ mgr.where relation[:id].not_eq(10)
+ mgr.to_sql.must_be_like %{
+ SELECT "users"."id" FROM "users" WHERE "users"."id" != 10
+ }
+ end
+
+ it "should handle nil" do
+ relation = Table.new(:users)
+ mgr = relation.project relation[:id]
+ mgr.where relation[:id].not_eq(nil)
+ mgr.to_sql.must_be_like %{
+ SELECT "users"."id" FROM "users" WHERE "users"."id" IS NOT NULL
+ }
+ end
+ end
+
+ describe "#not_eq_any" do
+ it "should create a Grouping node" do
+ relation = Table.new(:users)
+ relation[:id].not_eq_any([1, 2]).must_be_kind_of Nodes::Grouping
+ end
+
+ it "should generate ORs in sql" do
+ relation = Table.new(:users)
+ mgr = relation.project relation[:id]
+ mgr.where relation[:id].not_eq_any([1, 2])
+ mgr.to_sql.must_be_like %{
+ SELECT "users"."id" FROM "users" WHERE ("users"."id" != 1 OR "users"."id" != 2)
+ }
+ end
+ end
+
+ describe "#not_eq_all" do
+ it "should create a Grouping node" do
+ relation = Table.new(:users)
+ relation[:id].not_eq_all([1, 2]).must_be_kind_of Nodes::Grouping
+ end
+
+ it "should generate ANDs in sql" do
+ relation = Table.new(:users)
+ mgr = relation.project relation[:id]
+ mgr.where relation[:id].not_eq_all([1, 2])
+ mgr.to_sql.must_be_like %{
+ SELECT "users"."id" FROM "users" WHERE ("users"."id" != 1 AND "users"."id" != 2)
+ }
+ end
+ end
+
+ describe "#gt" do
+ it "should create a GreaterThan node" do
+ relation = Table.new(:users)
+ relation[:id].gt(10).must_be_kind_of Nodes::GreaterThan
+ end
+
+ it "should generate > in sql" do
+ relation = Table.new(:users)
+ mgr = relation.project relation[:id]
+ mgr.where relation[:id].gt(10)
+ mgr.to_sql.must_be_like %{
+ SELECT "users"."id" FROM "users" WHERE "users"."id" > 10
+ }
+ end
+
+ it "should handle comparing with a subquery" do
+ users = Table.new(:users)
+
+ avg = users.project(users[:karma].average)
+ mgr = users.project(Arel.star).where(users[:karma].gt(avg))
+
+ mgr.to_sql.must_be_like %{
+ SELECT * FROM "users" WHERE "users"."karma" > (SELECT AVG("users"."karma") FROM "users")
+ }
+ end
+
+ it "should accept various data types." do
+ relation = Table.new(:users)
+ mgr = relation.project relation[:id]
+ mgr.where relation[:name].gt("fake_name")
+ mgr.to_sql.must_match %{"users"."name" > 'fake_name'}
+
+ current_time = ::Time.now
+ mgr.where relation[:created_at].gt(current_time)
+ mgr.to_sql.must_match %{"users"."created_at" > '#{current_time}'}
+ end
+ end
+
+ describe "#gt_any" do
+ it "should create a Grouping node" do
+ relation = Table.new(:users)
+ relation[:id].gt_any([1, 2]).must_be_kind_of Nodes::Grouping
+ end
+
+ it "should generate ORs in sql" do
+ relation = Table.new(:users)
+ mgr = relation.project relation[:id]
+ mgr.where relation[:id].gt_any([1, 2])
+ mgr.to_sql.must_be_like %{
+ SELECT "users"."id" FROM "users" WHERE ("users"."id" > 1 OR "users"."id" > 2)
+ }
+ end
+ end
+
+ describe "#gt_all" do
+ it "should create a Grouping node" do
+ relation = Table.new(:users)
+ relation[:id].gt_all([1, 2]).must_be_kind_of Nodes::Grouping
+ end
+
+ it "should generate ANDs in sql" do
+ relation = Table.new(:users)
+ mgr = relation.project relation[:id]
+ mgr.where relation[:id].gt_all([1, 2])
+ mgr.to_sql.must_be_like %{
+ SELECT "users"."id" FROM "users" WHERE ("users"."id" > 1 AND "users"."id" > 2)
+ }
+ end
+ end
+
+ describe "#gteq" do
+ it "should create a GreaterThanOrEqual node" do
+ relation = Table.new(:users)
+ relation[:id].gteq(10).must_be_kind_of Nodes::GreaterThanOrEqual
+ end
+
+ it "should generate >= in sql" do
+ relation = Table.new(:users)
+ mgr = relation.project relation[:id]
+ mgr.where relation[:id].gteq(10)
+ mgr.to_sql.must_be_like %{
+ SELECT "users"."id" FROM "users" WHERE "users"."id" >= 10
+ }
+ end
+
+ it "should accept various data types." do
+ relation = Table.new(:users)
+ mgr = relation.project relation[:id]
+ mgr.where relation[:name].gteq("fake_name")
+ mgr.to_sql.must_match %{"users"."name" >= 'fake_name'}
+
+ current_time = ::Time.now
+ mgr.where relation[:created_at].gteq(current_time)
+ mgr.to_sql.must_match %{"users"."created_at" >= '#{current_time}'}
+ end
+ end
+
+ describe "#gteq_any" do
+ it "should create a Grouping node" do
+ relation = Table.new(:users)
+ relation[:id].gteq_any([1, 2]).must_be_kind_of Nodes::Grouping
+ end
+
+ it "should generate ORs in sql" do
+ relation = Table.new(:users)
+ mgr = relation.project relation[:id]
+ mgr.where relation[:id].gteq_any([1, 2])
+ mgr.to_sql.must_be_like %{
+ SELECT "users"."id" FROM "users" WHERE ("users"."id" >= 1 OR "users"."id" >= 2)
+ }
+ end
+ end
+
+ describe "#gteq_all" do
+ it "should create a Grouping node" do
+ relation = Table.new(:users)
+ relation[:id].gteq_all([1, 2]).must_be_kind_of Nodes::Grouping
+ end
+
+ it "should generate ANDs in sql" do
+ relation = Table.new(:users)
+ mgr = relation.project relation[:id]
+ mgr.where relation[:id].gteq_all([1, 2])
+ mgr.to_sql.must_be_like %{
+ SELECT "users"."id" FROM "users" WHERE ("users"."id" >= 1 AND "users"."id" >= 2)
+ }
+ end
+ end
+
+ describe "#lt" do
+ it "should create a LessThan node" do
+ relation = Table.new(:users)
+ relation[:id].lt(10).must_be_kind_of Nodes::LessThan
+ end
+
+ it "should generate < in sql" do
+ relation = Table.new(:users)
+ mgr = relation.project relation[:id]
+ mgr.where relation[:id].lt(10)
+ mgr.to_sql.must_be_like %{
+ SELECT "users"."id" FROM "users" WHERE "users"."id" < 10
+ }
+ end
+
+ it "should accept various data types." do
+ relation = Table.new(:users)
+ mgr = relation.project relation[:id]
+ mgr.where relation[:name].lt("fake_name")
+ mgr.to_sql.must_match %{"users"."name" < 'fake_name'}
+
+ current_time = ::Time.now
+ mgr.where relation[:created_at].lt(current_time)
+ mgr.to_sql.must_match %{"users"."created_at" < '#{current_time}'}
+ end
+ end
+
+ describe "#lt_any" do
+ it "should create a Grouping node" do
+ relation = Table.new(:users)
+ relation[:id].lt_any([1, 2]).must_be_kind_of Nodes::Grouping
+ end
+
+ it "should generate ORs in sql" do
+ relation = Table.new(:users)
+ mgr = relation.project relation[:id]
+ mgr.where relation[:id].lt_any([1, 2])
+ mgr.to_sql.must_be_like %{
+ SELECT "users"."id" FROM "users" WHERE ("users"."id" < 1 OR "users"."id" < 2)
+ }
+ end
+ end
+
+ describe "#lt_all" do
+ it "should create a Grouping node" do
+ relation = Table.new(:users)
+ relation[:id].lt_all([1, 2]).must_be_kind_of Nodes::Grouping
+ end
+
+ it "should generate ANDs in sql" do
+ relation = Table.new(:users)
+ mgr = relation.project relation[:id]
+ mgr.where relation[:id].lt_all([1, 2])
+ mgr.to_sql.must_be_like %{
+ SELECT "users"."id" FROM "users" WHERE ("users"."id" < 1 AND "users"."id" < 2)
+ }
+ end
+ end
+
+ describe "#lteq" do
+ it "should create a LessThanOrEqual node" do
+ relation = Table.new(:users)
+ relation[:id].lteq(10).must_be_kind_of Nodes::LessThanOrEqual
+ end
+
+ it "should generate <= in sql" do
+ relation = Table.new(:users)
+ mgr = relation.project relation[:id]
+ mgr.where relation[:id].lteq(10)
+ mgr.to_sql.must_be_like %{
+ SELECT "users"."id" FROM "users" WHERE "users"."id" <= 10
+ }
+ end
+
+ it "should accept various data types." do
+ relation = Table.new(:users)
+ mgr = relation.project relation[:id]
+ mgr.where relation[:name].lteq("fake_name")
+ mgr.to_sql.must_match %{"users"."name" <= 'fake_name'}
+
+ current_time = ::Time.now
+ mgr.where relation[:created_at].lteq(current_time)
+ mgr.to_sql.must_match %{"users"."created_at" <= '#{current_time}'}
+ end
+ end
+
+ describe "#lteq_any" do
+ it "should create a Grouping node" do
+ relation = Table.new(:users)
+ relation[:id].lteq_any([1, 2]).must_be_kind_of Nodes::Grouping
+ end
+
+ it "should generate ORs in sql" do
+ relation = Table.new(:users)
+ mgr = relation.project relation[:id]
+ mgr.where relation[:id].lteq_any([1, 2])
+ mgr.to_sql.must_be_like %{
+ SELECT "users"."id" FROM "users" WHERE ("users"."id" <= 1 OR "users"."id" <= 2)
+ }
+ end
+ end
+
+ describe "#lteq_all" do
+ it "should create a Grouping node" do
+ relation = Table.new(:users)
+ relation[:id].lteq_all([1, 2]).must_be_kind_of Nodes::Grouping
+ end
+
+ it "should generate ANDs in sql" do
+ relation = Table.new(:users)
+ mgr = relation.project relation[:id]
+ mgr.where relation[:id].lteq_all([1, 2])
+ mgr.to_sql.must_be_like %{
+ SELECT "users"."id" FROM "users" WHERE ("users"."id" <= 1 AND "users"."id" <= 2)
+ }
+ end
+ end
+
+ describe "#average" do
+ it "should create a AVG node" do
+ relation = Table.new(:users)
+ relation[:id].average.must_be_kind_of Nodes::Avg
+ end
+
+ it "should generate the proper SQL" do
+ relation = Table.new(:users)
+ mgr = relation.project relation[:id].average
+ mgr.to_sql.must_be_like %{
+ SELECT AVG("users"."id")
+ FROM "users"
+ }
+ end
+ end
+
+ describe "#maximum" do
+ it "should create a MAX node" do
+ relation = Table.new(:users)
+ relation[:id].maximum.must_be_kind_of Nodes::Max
+ end
+
+ it "should generate proper SQL" do
+ relation = Table.new(:users)
+ mgr = relation.project relation[:id].maximum
+ mgr.to_sql.must_be_like %{
+ SELECT MAX("users"."id")
+ FROM "users"
+ }
+ end
+ end
+
+ describe "#minimum" do
+ it "should create a Min node" do
+ relation = Table.new(:users)
+ relation[:id].minimum.must_be_kind_of Nodes::Min
+ end
+
+ it "should generate proper SQL" do
+ relation = Table.new(:users)
+ mgr = relation.project relation[:id].minimum
+ mgr.to_sql.must_be_like %{
+ SELECT MIN("users"."id")
+ FROM "users"
+ }
+ end
+ end
+
+ describe "#sum" do
+ it "should create a SUM node" do
+ relation = Table.new(:users)
+ relation[:id].sum.must_be_kind_of Nodes::Sum
+ end
+
+ it "should generate the proper SQL" do
+ relation = Table.new(:users)
+ mgr = relation.project relation[:id].sum
+ mgr.to_sql.must_be_like %{
+ SELECT SUM("users"."id")
+ FROM "users"
+ }
+ end
+ end
+
+ describe "#count" do
+ it "should return a count node" do
+ relation = Table.new(:users)
+ relation[:id].count.must_be_kind_of Nodes::Count
+ end
+
+ it "should take a distinct param" do
+ relation = Table.new(:users)
+ count = relation[:id].count(nil)
+ count.must_be_kind_of Nodes::Count
+ count.distinct.must_be_nil
+ end
+ end
+
+ describe "#eq" do
+ it "should return an equality node" do
+ attribute = Attribute.new nil, nil
+ equality = attribute.eq 1
+ equality.left.must_equal attribute
+ equality.right.val.must_equal 1
+ equality.must_be_kind_of Nodes::Equality
+ end
+
+ it "should generate = in sql" do
+ relation = Table.new(:users)
+ mgr = relation.project relation[:id]
+ mgr.where relation[:id].eq(10)
+ mgr.to_sql.must_be_like %{
+ SELECT "users"."id" FROM "users" WHERE "users"."id" = 10
+ }
+ end
+
+ it "should handle nil" do
+ relation = Table.new(:users)
+ mgr = relation.project relation[:id]
+ mgr.where relation[:id].eq(nil)
+ mgr.to_sql.must_be_like %{
+ SELECT "users"."id" FROM "users" WHERE "users"."id" IS NULL
+ }
+ end
+ end
+
+ describe "#eq_any" do
+ it "should create a Grouping node" do
+ relation = Table.new(:users)
+ relation[:id].eq_any([1, 2]).must_be_kind_of Nodes::Grouping
+ end
+
+ it "should generate ORs in sql" do
+ relation = Table.new(:users)
+ mgr = relation.project relation[:id]
+ mgr.where relation[:id].eq_any([1, 2])
+ mgr.to_sql.must_be_like %{
+ SELECT "users"."id" FROM "users" WHERE ("users"."id" = 1 OR "users"."id" = 2)
+ }
+ end
+
+ it "should not eat input" do
+ relation = Table.new(:users)
+ mgr = relation.project relation[:id]
+ values = [1, 2]
+ mgr.where relation[:id].eq_any(values)
+ values.must_equal [1, 2]
+ end
+ end
+
+ describe "#eq_all" do
+ it "should create a Grouping node" do
+ relation = Table.new(:users)
+ relation[:id].eq_all([1, 2]).must_be_kind_of Nodes::Grouping
+ end
+
+ it "should generate ANDs in sql" do
+ relation = Table.new(:users)
+ mgr = relation.project relation[:id]
+ mgr.where relation[:id].eq_all([1, 2])
+ mgr.to_sql.must_be_like %{
+ SELECT "users"."id" FROM "users" WHERE ("users"."id" = 1 AND "users"."id" = 2)
+ }
+ end
+
+ it "should not eat input" do
+ relation = Table.new(:users)
+ mgr = relation.project relation[:id]
+ values = [1, 2]
+ mgr.where relation[:id].eq_all(values)
+ values.must_equal [1, 2]
+ end
+ end
+
+ describe "#matches" do
+ it "should create a Matches node" do
+ relation = Table.new(:users)
+ relation[:name].matches("%bacon%").must_be_kind_of Nodes::Matches
+ end
+
+ it "should generate LIKE in sql" do
+ relation = Table.new(:users)
+ mgr = relation.project relation[:id]
+ mgr.where relation[:name].matches("%bacon%")
+ mgr.to_sql.must_be_like %{
+ SELECT "users"."id" FROM "users" WHERE "users"."name" LIKE '%bacon%'
+ }
+ end
+ end
+
+ describe "#matches_any" do
+ it "should create a Grouping node" do
+ relation = Table.new(:users)
+ relation[:name].matches_any(["%chunky%", "%bacon%"]).must_be_kind_of Nodes::Grouping
+ end
+
+ it "should generate ORs in sql" do
+ relation = Table.new(:users)
+ mgr = relation.project relation[:id]
+ mgr.where relation[:name].matches_any(["%chunky%", "%bacon%"])
+ mgr.to_sql.must_be_like %{
+ SELECT "users"."id" FROM "users" WHERE ("users"."name" LIKE '%chunky%' OR "users"."name" LIKE '%bacon%')
+ }
+ end
+ end
+
+ describe "#matches_all" do
+ it "should create a Grouping node" do
+ relation = Table.new(:users)
+ relation[:name].matches_all(["%chunky%", "%bacon%"]).must_be_kind_of Nodes::Grouping
+ end
+
+ it "should generate ANDs in sql" do
+ relation = Table.new(:users)
+ mgr = relation.project relation[:id]
+ mgr.where relation[:name].matches_all(["%chunky%", "%bacon%"])
+ mgr.to_sql.must_be_like %{
+ SELECT "users"."id" FROM "users" WHERE ("users"."name" LIKE '%chunky%' AND "users"."name" LIKE '%bacon%')
+ }
+ end
+ end
+
+ describe "#does_not_match" do
+ it "should create a DoesNotMatch node" do
+ relation = Table.new(:users)
+ relation[:name].does_not_match("%bacon%").must_be_kind_of Nodes::DoesNotMatch
+ end
+
+ it "should generate NOT LIKE in sql" do
+ relation = Table.new(:users)
+ mgr = relation.project relation[:id]
+ mgr.where relation[:name].does_not_match("%bacon%")
+ mgr.to_sql.must_be_like %{
+ SELECT "users"."id" FROM "users" WHERE "users"."name" NOT LIKE '%bacon%'
+ }
+ end
+ end
+
+ describe "#does_not_match_any" do
+ it "should create a Grouping node" do
+ relation = Table.new(:users)
+ relation[:name].does_not_match_any(["%chunky%", "%bacon%"]).must_be_kind_of Nodes::Grouping
+ end
+
+ it "should generate ORs in sql" do
+ relation = Table.new(:users)
+ mgr = relation.project relation[:id]
+ mgr.where relation[:name].does_not_match_any(["%chunky%", "%bacon%"])
+ mgr.to_sql.must_be_like %{
+ SELECT "users"."id" FROM "users" WHERE ("users"."name" NOT LIKE '%chunky%' OR "users"."name" NOT LIKE '%bacon%')
+ }
+ end
+ end
+
+ describe "#does_not_match_all" do
+ it "should create a Grouping node" do
+ relation = Table.new(:users)
+ relation[:name].does_not_match_all(["%chunky%", "%bacon%"]).must_be_kind_of Nodes::Grouping
+ end
+
+ it "should generate ANDs in sql" do
+ relation = Table.new(:users)
+ mgr = relation.project relation[:id]
+ mgr.where relation[:name].does_not_match_all(["%chunky%", "%bacon%"])
+ mgr.to_sql.must_be_like %{
+ SELECT "users"."id" FROM "users" WHERE ("users"."name" NOT LIKE '%chunky%' AND "users"."name" NOT LIKE '%bacon%')
+ }
+ end
+ end
+
+ describe "with a range" do
+ it "can be constructed with a standard range" do
+ attribute = Attribute.new nil, nil
+ node = attribute.between(1..3)
+
+ node.must_equal Nodes::Between.new(
+ attribute,
+ Nodes::And.new([
+ Nodes::Casted.new(1, attribute),
+ Nodes::Casted.new(3, attribute)
+ ])
+ )
+ end
+
+ it "can be constructed with a range starting from -Infinity" do
+ attribute = Attribute.new nil, nil
+ node = attribute.between(-::Float::INFINITY..3)
+
+ node.must_equal Nodes::LessThanOrEqual.new(
+ attribute,
+ Nodes::Casted.new(3, attribute)
+ )
+ end
+
+ it "can be constructed with a quoted range starting from -Infinity" do
+ attribute = Attribute.new nil, nil
+ node = attribute.between(quoted_range(-::Float::INFINITY, 3, false))
+
+ node.must_equal Nodes::LessThanOrEqual.new(
+ attribute,
+ Nodes::Quoted.new(3)
+ )
+ end
+
+ it "can be constructed with an exclusive range starting from -Infinity" do
+ attribute = Attribute.new nil, nil
+ node = attribute.between(-::Float::INFINITY...3)
+
+ node.must_equal Nodes::LessThan.new(
+ attribute,
+ Nodes::Casted.new(3, attribute)
+ )
+ end
+
+ it "can be constructed with a quoted exclusive range starting from -Infinity" do
+ attribute = Attribute.new nil, nil
+ node = attribute.between(quoted_range(-::Float::INFINITY, 3, true))
+
+ node.must_equal Nodes::LessThan.new(
+ attribute,
+ Nodes::Quoted.new(3)
+ )
+ end
+
+ it "can be constructed with an infinite range" do
+ attribute = Attribute.new nil, nil
+ node = attribute.between(-::Float::INFINITY..::Float::INFINITY)
+
+ node.must_equal Nodes::NotIn.new(attribute, [])
+ end
+
+ it "can be constructed with a quoted infinite range" do
+ attribute = Attribute.new nil, nil
+ node = attribute.between(quoted_range(-::Float::INFINITY, ::Float::INFINITY, false))
+
+ node.must_equal Nodes::NotIn.new(attribute, [])
+ end
+
+
+ it "can be constructed with a range ending at Infinity" do
+ attribute = Attribute.new nil, nil
+ node = attribute.between(0..::Float::INFINITY)
+
+ node.must_equal Nodes::GreaterThanOrEqual.new(
+ attribute,
+ Nodes::Casted.new(0, attribute)
+ )
+ end
+
+ it "can be constructed with a quoted range ending at Infinity" do
+ attribute = Attribute.new nil, nil
+ node = attribute.between(quoted_range(0, ::Float::INFINITY, false))
+
+ node.must_equal Nodes::GreaterThanOrEqual.new(
+ attribute,
+ Nodes::Quoted.new(0)
+ )
+ end
+
+ it "can be constructed with an exclusive range" do
+ attribute = Attribute.new nil, nil
+ node = attribute.between(0...3)
+
+ node.must_equal Nodes::And.new([
+ Nodes::GreaterThanOrEqual.new(
+ attribute,
+ Nodes::Casted.new(0, attribute)
+ ),
+ Nodes::LessThan.new(
+ attribute,
+ Nodes::Casted.new(3, attribute)
+ )
+ ])
+ end
+
+ def quoted_range(begin_val, end_val, exclude)
+ OpenStruct.new(
+ begin: Nodes::Quoted.new(begin_val),
+ end: Nodes::Quoted.new(end_val),
+ exclude_end?: exclude,
+ )
+ end
+ end
+
+ describe "#in" do
+ it "can be constructed with a subquery" do
+ relation = Table.new(:users)
+ mgr = relation.project relation[:id]
+ mgr.where relation[:name].does_not_match_all(["%chunky%", "%bacon%"])
+ attribute = Attribute.new nil, nil
+
+ node = attribute.in(mgr)
+
+ node.must_equal Nodes::In.new(attribute, mgr.ast)
+ end
+
+ it "can be constructed with a list" do
+ attribute = Attribute.new nil, nil
+ node = attribute.in([1, 2, 3])
+
+ node.must_equal Nodes::In.new(
+ attribute,
+ [
+ Nodes::Casted.new(1, attribute),
+ Nodes::Casted.new(2, attribute),
+ Nodes::Casted.new(3, attribute),
+ ]
+ )
+ end
+
+ it "can be constructed with a random object" do
+ attribute = Attribute.new nil, nil
+ random_object = Object.new
+ node = attribute.in(random_object)
+
+ node.must_equal Nodes::In.new(
+ attribute,
+ Nodes::Casted.new(random_object, attribute)
+ )
+ end
+
+ it "should generate IN in sql" do
+ relation = Table.new(:users)
+ mgr = relation.project relation[:id]
+ mgr.where relation[:id].in([1, 2, 3])
+ mgr.to_sql.must_be_like %{
+ SELECT "users"."id" FROM "users" WHERE "users"."id" IN (1, 2, 3)
+ }
+ end
+ end
+
+ describe "#in_any" do
+ it "should create a Grouping node" do
+ relation = Table.new(:users)
+ relation[:id].in_any([1, 2]).must_be_kind_of Nodes::Grouping
+ end
+
+ it "should generate ORs in sql" do
+ relation = Table.new(:users)
+ mgr = relation.project relation[:id]
+ mgr.where relation[:id].in_any([[1, 2], [3, 4]])
+ mgr.to_sql.must_be_like %{
+ SELECT "users"."id" FROM "users" WHERE ("users"."id" IN (1, 2) OR "users"."id" IN (3, 4))
+ }
+ end
+ end
+
+ describe "#in_all" do
+ it "should create a Grouping node" do
+ relation = Table.new(:users)
+ relation[:id].in_all([1, 2]).must_be_kind_of Nodes::Grouping
+ end
+
+ it "should generate ANDs in sql" do
+ relation = Table.new(:users)
+ mgr = relation.project relation[:id]
+ mgr.where relation[:id].in_all([[1, 2], [3, 4]])
+ mgr.to_sql.must_be_like %{
+ SELECT "users"."id" FROM "users" WHERE ("users"."id" IN (1, 2) AND "users"."id" IN (3, 4))
+ }
+ end
+ end
+
+ describe "with a range" do
+ it "can be constructed with a standard range" do
+ attribute = Attribute.new nil, nil
+ node = attribute.not_between(1..3)
+
+ node.must_equal Nodes::Grouping.new(Nodes::Or.new(
+ Nodes::LessThan.new(
+ attribute,
+ Nodes::Casted.new(1, attribute)
+ ),
+ Nodes::GreaterThan.new(
+ attribute,
+ Nodes::Casted.new(3, attribute)
+ )
+ ))
+ end
+
+ it "can be constructed with a range starting from -Infinity" do
+ attribute = Attribute.new nil, nil
+ node = attribute.not_between(-::Float::INFINITY..3)
+
+ node.must_equal Nodes::GreaterThan.new(
+ attribute,
+ Nodes::Casted.new(3, attribute)
+ )
+ end
+
+ it "can be constructed with an exclusive range starting from -Infinity" do
+ attribute = Attribute.new nil, nil
+ node = attribute.not_between(-::Float::INFINITY...3)
+
+ node.must_equal Nodes::GreaterThanOrEqual.new(
+ attribute,
+ Nodes::Casted.new(3, attribute)
+ )
+ end
+
+ it "can be constructed with an infinite range" do
+ attribute = Attribute.new nil, nil
+ node = attribute.not_between(-::Float::INFINITY..::Float::INFINITY)
+
+ node.must_equal Nodes::In.new(attribute, [])
+ end
+
+ it "can be constructed with a range ending at Infinity" do
+ attribute = Attribute.new nil, nil
+ node = attribute.not_between(0..::Float::INFINITY)
+
+ node.must_equal Nodes::LessThan.new(
+ attribute,
+ Nodes::Casted.new(0, attribute)
+ )
+ end
+
+ it "can be constructed with an exclusive range" do
+ attribute = Attribute.new nil, nil
+ node = attribute.not_between(0...3)
+
+ node.must_equal Nodes::Grouping.new(Nodes::Or.new(
+ Nodes::LessThan.new(
+ attribute,
+ Nodes::Casted.new(0, attribute)
+ ),
+ Nodes::GreaterThanOrEqual.new(
+ attribute,
+ Nodes::Casted.new(3, attribute)
+ )
+ ))
+ end
+ end
+
+ describe "#not_in" do
+ it "can be constructed with a subquery" do
+ relation = Table.new(:users)
+ mgr = relation.project relation[:id]
+ mgr.where relation[:name].does_not_match_all(["%chunky%", "%bacon%"])
+ attribute = Attribute.new nil, nil
+
+ node = attribute.not_in(mgr)
+
+ node.must_equal Nodes::NotIn.new(attribute, mgr.ast)
+ end
+
+ it "can be constructed with a Union" do
+ relation = Table.new(:users)
+ mgr1 = relation.project(relation[:id])
+ mgr2 = relation.project(relation[:id])
+
+ union = mgr1.union(mgr2)
+ node = relation[:id].in(union)
+ node.to_sql.must_be_like %{
+ "users"."id" IN (( SELECT "users"."id" FROM "users" UNION SELECT "users"."id" FROM "users" ))
+ }
+ end
+
+ it "can be constructed with a list" do
+ attribute = Attribute.new nil, nil
+ node = attribute.not_in([1, 2, 3])
+
+ node.must_equal Nodes::NotIn.new(
+ attribute,
+ [
+ Nodes::Casted.new(1, attribute),
+ Nodes::Casted.new(2, attribute),
+ Nodes::Casted.new(3, attribute),
+ ]
+ )
+ end
+
+ it "can be constructed with a random object" do
+ attribute = Attribute.new nil, nil
+ random_object = Object.new
+ node = attribute.not_in(random_object)
+
+ node.must_equal Nodes::NotIn.new(
+ attribute,
+ Nodes::Casted.new(random_object, attribute)
+ )
+ end
+
+ it "should generate NOT IN in sql" do
+ relation = Table.new(:users)
+ mgr = relation.project relation[:id]
+ mgr.where relation[:id].not_in([1, 2, 3])
+ mgr.to_sql.must_be_like %{
+ SELECT "users"."id" FROM "users" WHERE "users"."id" NOT IN (1, 2, 3)
+ }
+ end
+ end
+
+ describe "#not_in_any" do
+ it "should create a Grouping node" do
+ relation = Table.new(:users)
+ relation[:id].not_in_any([1, 2]).must_be_kind_of Nodes::Grouping
+ end
+
+ it "should generate ORs in sql" do
+ relation = Table.new(:users)
+ mgr = relation.project relation[:id]
+ mgr.where relation[:id].not_in_any([[1, 2], [3, 4]])
+ mgr.to_sql.must_be_like %{
+ SELECT "users"."id" FROM "users" WHERE ("users"."id" NOT IN (1, 2) OR "users"."id" NOT IN (3, 4))
+ }
+ end
+ end
+
+ describe "#not_in_all" do
+ it "should create a Grouping node" do
+ relation = Table.new(:users)
+ relation[:id].not_in_all([1, 2]).must_be_kind_of Nodes::Grouping
+ end
+
+ it "should generate ANDs in sql" do
+ relation = Table.new(:users)
+ mgr = relation.project relation[:id]
+ mgr.where relation[:id].not_in_all([[1, 2], [3, 4]])
+ mgr.to_sql.must_be_like %{
+ SELECT "users"."id" FROM "users" WHERE ("users"."id" NOT IN (1, 2) AND "users"."id" NOT IN (3, 4))
+ }
+ end
+ end
+
+ describe "#eq_all" do
+ it "should create a Grouping node" do
+ relation = Table.new(:users)
+ relation[:id].eq_all([1, 2]).must_be_kind_of Nodes::Grouping
+ end
+
+ it "should generate ANDs in sql" do
+ relation = Table.new(:users)
+ mgr = relation.project relation[:id]
+ mgr.where relation[:id].eq_all([1, 2])
+ mgr.to_sql.must_be_like %{
+ SELECT "users"."id" FROM "users" WHERE ("users"."id" = 1 AND "users"."id" = 2)
+ }
+ end
+ end
+
+ describe "#asc" do
+ it "should create an Ascending node" do
+ relation = Table.new(:users)
+ relation[:id].asc.must_be_kind_of Nodes::Ascending
+ end
+
+ it "should generate ASC in sql" do
+ relation = Table.new(:users)
+ mgr = relation.project relation[:id]
+ mgr.order relation[:id].asc
+ mgr.to_sql.must_be_like %{
+ SELECT "users"."id" FROM "users" ORDER BY "users"."id" ASC
+ }
+ end
+ end
+
+ describe "#desc" do
+ it "should create a Descending node" do
+ relation = Table.new(:users)
+ relation[:id].desc.must_be_kind_of Nodes::Descending
+ end
+
+ it "should generate DESC in sql" do
+ relation = Table.new(:users)
+ mgr = relation.project relation[:id]
+ mgr.order relation[:id].desc
+ mgr.to_sql.must_be_like %{
+ SELECT "users"."id" FROM "users" ORDER BY "users"."id" DESC
+ }
+ end
+ end
+
+ describe "equality" do
+ describe "#to_sql" do
+ it "should produce sql" do
+ table = Table.new :users
+ condition = table["id"].eq 1
+ condition.to_sql.must_equal '"users"."id" = 1'
+ end
+ end
+ end
+
+ describe "type casting" do
+ it "does not type cast by default" do
+ table = Table.new(:foo)
+ condition = table["id"].eq("1")
+
+ assert_not table.able_to_type_cast?
+ condition.to_sql.must_equal %("foo"."id" = '1')
+ end
+
+ it "type casts when given an explicit caster" do
+ fake_caster = Object.new
+ def fake_caster.type_cast_for_database(attr_name, value)
+ if attr_name == "id"
+ value.to_i
+ else
+ value
+ end
+ end
+ table = Table.new(:foo, type_caster: fake_caster)
+ condition = table["id"].eq("1").and(table["other_id"].eq("2"))
+
+ assert table.able_to_type_cast?
+ condition.to_sql.must_equal %("foo"."id" = 1 AND "foo"."other_id" = '2')
+ end
+
+ it "does not type cast SqlLiteral nodes" do
+ fake_caster = Object.new
+ def fake_caster.type_cast_for_database(attr_name, value)
+ value.to_i
+ end
+ table = Table.new(:foo, type_caster: fake_caster)
+ condition = table["id"].eq(Arel.sql("(select 1)"))
+
+ assert table.able_to_type_cast?
+ condition.to_sql.must_equal %("foo"."id" = (select 1))
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/arel/attributes/math_test.rb b/activerecord/test/cases/arel/attributes/math_test.rb
new file mode 100644
index 0000000000..41eea217c0
--- /dev/null
+++ b/activerecord/test/cases/arel/attributes/math_test.rb
@@ -0,0 +1,83 @@
+# frozen_string_literal: true
+
+require_relative "../helper"
+
+module Arel
+ module Attributes
+ class MathTest < Arel::Spec
+ %i[* /].each do |math_operator|
+ it "average should be compatible with #{math_operator}" do
+ table = Arel::Table.new :users
+ (table[:id].average.public_send(math_operator, 2)).to_sql.must_be_like %{
+ AVG("users"."id") #{math_operator} 2
+ }
+ end
+
+ it "count should be compatible with #{math_operator}" do
+ table = Arel::Table.new :users
+ (table[:id].count.public_send(math_operator, 2)).to_sql.must_be_like %{
+ COUNT("users"."id") #{math_operator} 2
+ }
+ end
+
+ it "maximum should be compatible with #{math_operator}" do
+ table = Arel::Table.new :users
+ (table[:id].maximum.public_send(math_operator, 2)).to_sql.must_be_like %{
+ MAX("users"."id") #{math_operator} 2
+ }
+ end
+
+ it "minimum should be compatible with #{math_operator}" do
+ table = Arel::Table.new :users
+ (table[:id].minimum.public_send(math_operator, 2)).to_sql.must_be_like %{
+ MIN("users"."id") #{math_operator} 2
+ }
+ end
+
+ it "attribute node should be compatible with #{math_operator}" do
+ table = Arel::Table.new :users
+ (table[:id].public_send(math_operator, 2)).to_sql.must_be_like %{
+ "users"."id" #{math_operator} 2
+ }
+ end
+ end
+
+ %i[+ - & | ^ << >>].each do |math_operator|
+ it "average should be compatible with #{math_operator}" do
+ table = Arel::Table.new :users
+ (table[:id].average.public_send(math_operator, 2)).to_sql.must_be_like %{
+ (AVG("users"."id") #{math_operator} 2)
+ }
+ end
+
+ it "count should be compatible with #{math_operator}" do
+ table = Arel::Table.new :users
+ (table[:id].count.public_send(math_operator, 2)).to_sql.must_be_like %{
+ (COUNT("users"."id") #{math_operator} 2)
+ }
+ end
+
+ it "maximum should be compatible with #{math_operator}" do
+ table = Arel::Table.new :users
+ (table[:id].maximum.public_send(math_operator, 2)).to_sql.must_be_like %{
+ (MAX("users"."id") #{math_operator} 2)
+ }
+ end
+
+ it "minimum should be compatible with #{math_operator}" do
+ table = Arel::Table.new :users
+ (table[:id].minimum.public_send(math_operator, 2)).to_sql.must_be_like %{
+ (MIN("users"."id") #{math_operator} 2)
+ }
+ end
+
+ it "attribute node should be compatible with #{math_operator}" do
+ table = Arel::Table.new :users
+ (table[:id].public_send(math_operator, 2)).to_sql.must_be_like %{
+ ("users"."id" #{math_operator} 2)
+ }
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/arel/attributes_test.rb b/activerecord/test/cases/arel/attributes_test.rb
new file mode 100644
index 0000000000..b00af4bd29
--- /dev/null
+++ b/activerecord/test/cases/arel/attributes_test.rb
@@ -0,0 +1,68 @@
+# frozen_string_literal: true
+
+require_relative "helper"
+
+module Arel
+ describe "Attributes" do
+ it "responds to lower" do
+ relation = Table.new(:users)
+ attribute = relation[:foo]
+ node = attribute.lower
+ assert_equal "LOWER", node.name
+ assert_equal [attribute], node.expressions
+ end
+
+ describe "equality" do
+ it "is equal with equal ivars" do
+ array = [Attribute.new("foo", "bar"), Attribute.new("foo", "bar")]
+ assert_equal 1, array.uniq.size
+ end
+
+ it "is not equal with different ivars" do
+ array = [Attribute.new("foo", "bar"), Attribute.new("foo", "baz")]
+ assert_equal 2, array.uniq.size
+ end
+ end
+
+ describe "for" do
+ it "deals with unknown column types" do
+ column = Struct.new(:type).new :crazy
+ Attributes.for(column).must_equal Attributes::Undefined
+ end
+
+ it "returns the correct constant for strings" do
+ [:string, :text, :binary].each do |type|
+ column = Struct.new(:type).new type
+ Attributes.for(column).must_equal Attributes::String
+ end
+ end
+
+ it "returns the correct constant for ints" do
+ column = Struct.new(:type).new :integer
+ Attributes.for(column).must_equal Attributes::Integer
+ end
+
+ it "returns the correct constant for floats" do
+ column = Struct.new(:type).new :float
+ Attributes.for(column).must_equal Attributes::Float
+ end
+
+ it "returns the correct constant for decimals" do
+ column = Struct.new(:type).new :decimal
+ Attributes.for(column).must_equal Attributes::Decimal
+ end
+
+ it "returns the correct constant for boolean" do
+ column = Struct.new(:type).new :boolean
+ Attributes.for(column).must_equal Attributes::Boolean
+ end
+
+ it "returns the correct constant for time" do
+ [:date, :datetime, :timestamp, :time].each do |type|
+ column = Struct.new(:type).new type
+ Attributes.for(column).must_equal Attributes::Time
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/arel/collectors/bind_test.rb b/activerecord/test/cases/arel/collectors/bind_test.rb
new file mode 100644
index 0000000000..ffa9b15f66
--- /dev/null
+++ b/activerecord/test/cases/arel/collectors/bind_test.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+require_relative "../helper"
+require "arel/collectors/bind"
+
+module Arel
+ module Collectors
+ class TestBind < Arel::Test
+ def setup
+ @conn = FakeRecord::Base.new
+ @visitor = Visitors::ToSql.new @conn.connection
+ super
+ end
+
+ def collect(node)
+ @visitor.accept(node, Collectors::Bind.new)
+ end
+
+ def compile(node)
+ collect(node).value
+ end
+
+ def ast_with_binds(bvs)
+ table = Table.new(:users)
+ manager = Arel::SelectManager.new table
+ manager.where(table[:age].eq(Nodes::BindParam.new(bvs.shift)))
+ manager.where(table[:name].eq(Nodes::BindParam.new(bvs.shift)))
+ manager.ast
+ end
+
+ def test_compile_gathers_all_bind_params
+ binds = compile(ast_with_binds(["hello", "world"]))
+ assert_equal ["hello", "world"], binds
+
+ binds = compile(ast_with_binds(["hello2", "world3"]))
+ assert_equal ["hello2", "world3"], binds
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/arel/collectors/composite_test.rb b/activerecord/test/cases/arel/collectors/composite_test.rb
new file mode 100644
index 0000000000..545637496f
--- /dev/null
+++ b/activerecord/test/cases/arel/collectors/composite_test.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+require_relative "../helper"
+
+require "arel/collectors/bind"
+require "arel/collectors/composite"
+
+module Arel
+ module Collectors
+ class TestComposite < Arel::Test
+ def setup
+ @conn = FakeRecord::Base.new
+ @visitor = Visitors::ToSql.new @conn.connection
+ super
+ end
+
+ def collect(node)
+ sql_collector = Collectors::SQLString.new
+ bind_collector = Collectors::Bind.new
+ collector = Collectors::Composite.new(sql_collector, bind_collector)
+ @visitor.accept(node, collector)
+ end
+
+ def compile(node)
+ collect(node).value
+ end
+
+ def ast_with_binds(bvs)
+ table = Table.new(:users)
+ manager = Arel::SelectManager.new table
+ manager.where(table[:age].eq(Nodes::BindParam.new(bvs.shift)))
+ manager.where(table[:name].eq(Nodes::BindParam.new(bvs.shift)))
+ manager.ast
+ end
+
+ def test_composite_collector_performs_multiple_collections_at_once
+ sql, binds = compile(ast_with_binds(["hello", "world"]))
+ assert_equal 'SELECT FROM "users" WHERE "users"."age" = ? AND "users"."name" = ?', sql
+ assert_equal ["hello", "world"], binds
+
+ sql, binds = compile(ast_with_binds(["hello2", "world3"]))
+ assert_equal 'SELECT FROM "users" WHERE "users"."age" = ? AND "users"."name" = ?', sql
+ assert_equal ["hello2", "world3"], binds
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/arel/collectors/sql_string_test.rb b/activerecord/test/cases/arel/collectors/sql_string_test.rb
new file mode 100644
index 0000000000..380573494d
--- /dev/null
+++ b/activerecord/test/cases/arel/collectors/sql_string_test.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+require_relative "../helper"
+
+module Arel
+ module Collectors
+ class TestSqlString < Arel::Test
+ def setup
+ @conn = FakeRecord::Base.new
+ @visitor = Visitors::ToSql.new @conn.connection
+ super
+ end
+
+ def collect(node)
+ @visitor.accept(node, Collectors::SQLString.new)
+ end
+
+ def compile(node)
+ collect(node).value
+ end
+
+ def ast_with_binds(bv)
+ table = Table.new(:users)
+ manager = Arel::SelectManager.new table
+ manager.where(table[:age].eq(bv))
+ manager.where(table[:name].eq(bv))
+ manager.ast
+ end
+
+ def test_compile
+ bv = Nodes::BindParam.new(1)
+ collector = collect ast_with_binds bv
+
+ sql = collector.compile ["hello", "world"]
+ assert_equal 'SELECT FROM "users" WHERE "users"."age" = ? AND "users"."name" = ?', sql
+ end
+
+ def test_returned_sql_uses_utf8_encoding
+ bv = Nodes::BindParam.new(1)
+ collector = collect ast_with_binds bv
+
+ sql = collector.compile ["hello", "world"]
+ assert_equal sql.encoding, Encoding::UTF_8
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/arel/collectors/substitute_bind_collector_test.rb b/activerecord/test/cases/arel/collectors/substitute_bind_collector_test.rb
new file mode 100644
index 0000000000..255c8e79e9
--- /dev/null
+++ b/activerecord/test/cases/arel/collectors/substitute_bind_collector_test.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+require_relative "../helper"
+require "arel/collectors/substitute_binds"
+require "arel/collectors/sql_string"
+
+module Arel
+ module Collectors
+ class TestSubstituteBindCollector < Arel::Test
+ def setup
+ @conn = FakeRecord::Base.new
+ @visitor = Visitors::ToSql.new @conn.connection
+ super
+ end
+
+ def ast_with_binds
+ table = Table.new(:users)
+ manager = Arel::SelectManager.new table
+ manager.where(table[:age].eq(Nodes::BindParam.new("hello")))
+ manager.where(table[:name].eq(Nodes::BindParam.new("world")))
+ manager.ast
+ end
+
+ def compile(node, quoter)
+ collector = Collectors::SubstituteBinds.new(quoter, Collectors::SQLString.new)
+ @visitor.accept(node, collector).value
+ end
+
+ def test_compile
+ quoter = Object.new
+ def quoter.quote(val)
+ val.to_s
+ end
+ sql = compile(ast_with_binds, quoter)
+ assert_equal 'SELECT FROM "users" WHERE "users"."age" = hello AND "users"."name" = world', sql
+ end
+
+ def test_quoting_is_delegated_to_quoter
+ quoter = Object.new
+ def quoter.quote(val)
+ val.inspect
+ end
+ sql = compile(ast_with_binds, quoter)
+ assert_equal 'SELECT FROM "users" WHERE "users"."age" = "hello" AND "users"."name" = "world"', sql
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/arel/crud_test.rb b/activerecord/test/cases/arel/crud_test.rb
new file mode 100644
index 0000000000..f3cdd8927f
--- /dev/null
+++ b/activerecord/test/cases/arel/crud_test.rb
@@ -0,0 +1,65 @@
+# frozen_string_literal: true
+
+require_relative "helper"
+
+module Arel
+ class FakeCrudder < SelectManager
+ class FakeEngine
+ attr_reader :calls, :connection_pool, :spec, :config
+
+ def initialize
+ @calls = []
+ @connection_pool = self
+ @spec = self
+ @config = { adapter: "sqlite3" }
+ end
+
+ def connection; self end
+
+ def method_missing(name, *args)
+ @calls << [name, args]
+ end
+ end
+
+ include Crud
+
+ attr_reader :engine
+ attr_accessor :ctx
+
+ def initialize(engine = FakeEngine.new)
+ super
+ end
+ end
+
+ describe "crud" do
+ describe "insert" do
+ it "should call insert on the connection" do
+ table = Table.new :users
+ fc = FakeCrudder.new
+ fc.from table
+ im = fc.compile_insert [[table[:id], "foo"]]
+ assert_instance_of Arel::InsertManager, im
+ end
+ end
+
+ describe "update" do
+ it "should call update on the connection" do
+ table = Table.new :users
+ fc = FakeCrudder.new
+ fc.from table
+ stmt = fc.compile_update [[table[:id], "foo"]], Arel::Attributes::Attribute.new(table, "id")
+ assert_instance_of Arel::UpdateManager, stmt
+ end
+ end
+
+ describe "delete" do
+ it "should call delete on the connection" do
+ table = Table.new :users
+ fc = FakeCrudder.new
+ fc.from table
+ stmt = fc.compile_delete
+ assert_instance_of Arel::DeleteManager, stmt
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/arel/delete_manager_test.rb b/activerecord/test/cases/arel/delete_manager_test.rb
new file mode 100644
index 0000000000..17a5271039
--- /dev/null
+++ b/activerecord/test/cases/arel/delete_manager_test.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+require_relative "helper"
+
+module Arel
+ class DeleteManagerTest < Arel::Spec
+ describe "new" do
+ it "takes an engine" do
+ Arel::DeleteManager.new
+ end
+ end
+
+ it "handles limit properly" do
+ table = Table.new(:users)
+ dm = Arel::DeleteManager.new
+ dm.take 10
+ dm.from table
+ assert_match(/LIMIT 10/, dm.to_sql)
+ end
+
+ describe "from" do
+ it "uses from" do
+ table = Table.new(:users)
+ dm = Arel::DeleteManager.new
+ dm.from table
+ dm.to_sql.must_be_like %{ DELETE FROM "users" }
+ end
+
+ it "chains" do
+ table = Table.new(:users)
+ dm = Arel::DeleteManager.new
+ dm.from(table).must_equal dm
+ end
+ end
+
+ describe "where" do
+ it "uses where values" do
+ table = Table.new(:users)
+ dm = Arel::DeleteManager.new
+ dm.from table
+ dm.where table[:id].eq(10)
+ dm.to_sql.must_be_like %{ DELETE FROM "users" WHERE "users"."id" = 10}
+ end
+
+ it "chains" do
+ table = Table.new(:users)
+ dm = Arel::DeleteManager.new
+ dm.where(table[:id].eq(10)).must_equal dm
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/arel/factory_methods_test.rb b/activerecord/test/cases/arel/factory_methods_test.rb
new file mode 100644
index 0000000000..26d2cdd08d
--- /dev/null
+++ b/activerecord/test/cases/arel/factory_methods_test.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+require_relative "helper"
+
+module Arel
+ module FactoryMethods
+ class TestFactoryMethods < Arel::Test
+ class Factory
+ include Arel::FactoryMethods
+ end
+
+ def setup
+ @factory = Factory.new
+ end
+
+ def test_create_join
+ join = @factory.create_join :one, :two
+ assert_kind_of Nodes::Join, join
+ assert_equal :two, join.right
+ end
+
+ def test_create_on
+ on = @factory.create_on :one
+ assert_instance_of Nodes::On, on
+ assert_equal :one, on.expr
+ end
+
+ def test_create_true
+ true_node = @factory.create_true
+ assert_instance_of Nodes::True, true_node
+ end
+
+ def test_create_false
+ false_node = @factory.create_false
+ assert_instance_of Nodes::False, false_node
+ end
+
+ def test_lower
+ lower = @factory.lower :one
+ assert_instance_of Nodes::NamedFunction, lower
+ assert_equal "LOWER", lower.name
+ assert_equal [:one], lower.expressions.map(&:expr)
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/arel/helper.rb b/activerecord/test/cases/arel/helper.rb
new file mode 100644
index 0000000000..f8ce658440
--- /dev/null
+++ b/activerecord/test/cases/arel/helper.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+require "active_support"
+require "minitest/autorun"
+require "arel"
+
+require_relative "support/fake_record"
+
+class Object
+ def must_be_like(other)
+ gsub(/\s+/, " ").strip.must_equal other.gsub(/\s+/, " ").strip
+ end
+end
+
+module Arel
+ class Test < ActiveSupport::TestCase
+ def setup
+ super
+ @arel_engine = Arel::Table.engine
+ Arel::Table.engine = FakeRecord::Base.new
+ end
+
+ def teardown
+ Arel::Table.engine = @arel_engine if defined? @arel_engine
+ super
+ end
+ end
+
+ class Spec < Minitest::Spec
+ before do
+ @arel_engine = Arel::Table.engine
+ Arel::Table.engine = FakeRecord::Base.new
+ end
+
+ after do
+ Arel::Table.engine = @arel_engine if defined? @arel_engine
+ end
+ include ActiveSupport::Testing::Assertions
+
+ # test/unit backwards compatibility methods
+ alias :assert_no_match :refute_match
+ alias :assert_not_equal :refute_equal
+ alias :assert_not_same :refute_same
+ end
+end
diff --git a/activerecord/test/cases/arel/insert_manager_test.rb b/activerecord/test/cases/arel/insert_manager_test.rb
new file mode 100644
index 0000000000..2376ad8d37
--- /dev/null
+++ b/activerecord/test/cases/arel/insert_manager_test.rb
@@ -0,0 +1,242 @@
+# frozen_string_literal: true
+
+require_relative "helper"
+
+module Arel
+ class InsertManagerTest < Arel::Spec
+ describe "new" do
+ it "takes an engine" do
+ Arel::InsertManager.new
+ end
+ end
+
+ describe "insert" do
+ it "can create a Values node" do
+ manager = Arel::InsertManager.new
+ values = manager.create_values %w{ a b }, %w{ c d }
+
+ assert_kind_of Arel::Nodes::Values, values
+ assert_equal %w{ a b }, values.left
+ assert_equal %w{ c d }, values.right
+ end
+
+ it "allows sql literals" do
+ manager = Arel::InsertManager.new
+ manager.into Table.new(:users)
+ manager.values = manager.create_values [Arel.sql("*")], %w{ a }
+ manager.to_sql.must_be_like %{
+ INSERT INTO \"users\" VALUES (*)
+ }
+ end
+
+ it "works with multiple values" do
+ table = Table.new(:users)
+ manager = Arel::InsertManager.new
+ manager.into table
+
+ manager.columns << table[:id]
+ manager.columns << table[:name]
+
+ manager.values = manager.create_values_list([
+ %w{1 david},
+ %w{2 kir},
+ ["3", Arel.sql("DEFAULT")],
+ ])
+
+ manager.to_sql.must_be_like %{
+ INSERT INTO \"users\" (\"id\", \"name\") VALUES ('1', 'david'), ('2', 'kir'), ('3', DEFAULT)
+ }
+ end
+
+ it "literals in multiple values are not escaped" do
+ table = Table.new(:users)
+ manager = Arel::InsertManager.new
+ manager.into table
+
+ manager.columns << table[:name]
+
+ manager.values = manager.create_values_list([
+ [Arel.sql("*")],
+ [Arel.sql("DEFAULT")],
+ ])
+
+ manager.to_sql.must_be_like %{
+ INSERT INTO \"users\" (\"name\") VALUES (*), (DEFAULT)
+ }
+ end
+
+ it "works with multiple single values" do
+ table = Table.new(:users)
+ manager = Arel::InsertManager.new
+ manager.into table
+
+ manager.columns << table[:name]
+
+ manager.values = manager.create_values_list([
+ %w{david},
+ %w{kir},
+ [Arel.sql("DEFAULT")],
+ ])
+
+ manager.to_sql.must_be_like %{
+ INSERT INTO \"users\" (\"name\") VALUES ('david'), ('kir'), (DEFAULT)
+ }
+ end
+
+ it "inserts false" do
+ table = Table.new(:users)
+ manager = Arel::InsertManager.new
+
+ manager.insert [[table[:bool], false]]
+ manager.to_sql.must_be_like %{
+ INSERT INTO "users" ("bool") VALUES ('f')
+ }
+ end
+
+ it "inserts null" do
+ table = Table.new(:users)
+ manager = Arel::InsertManager.new
+ manager.insert [[table[:id], nil]]
+ manager.to_sql.must_be_like %{
+ INSERT INTO "users" ("id") VALUES (NULL)
+ }
+ end
+
+ it "inserts time" do
+ table = Table.new(:users)
+ manager = Arel::InsertManager.new
+
+ time = Time.now
+ attribute = table[:created_at]
+
+ manager.insert [[attribute, time]]
+ manager.to_sql.must_be_like %{
+ INSERT INTO "users" ("created_at") VALUES (#{Table.engine.connection.quote time})
+ }
+ end
+
+ it "takes a list of lists" do
+ table = Table.new(:users)
+ manager = Arel::InsertManager.new
+ manager.into table
+ manager.insert [[table[:id], 1], [table[:name], "aaron"]]
+ manager.to_sql.must_be_like %{
+ INSERT INTO "users" ("id", "name") VALUES (1, 'aaron')
+ }
+ end
+
+ it "defaults the table" do
+ table = Table.new(:users)
+ manager = Arel::InsertManager.new
+ manager.insert [[table[:id], 1], [table[:name], "aaron"]]
+ manager.to_sql.must_be_like %{
+ INSERT INTO "users" ("id", "name") VALUES (1, 'aaron')
+ }
+ end
+
+ it "noop for empty list" do
+ table = Table.new(:users)
+ manager = Arel::InsertManager.new
+ manager.insert [[table[:id], 1]]
+ manager.insert []
+ manager.to_sql.must_be_like %{
+ INSERT INTO "users" ("id") VALUES (1)
+ }
+ end
+
+ it "is chainable" do
+ table = Table.new(:users)
+ manager = Arel::InsertManager.new
+ insert_result = manager.insert [[table[:id], 1]]
+ assert_equal manager, insert_result
+ end
+ end
+
+ describe "into" do
+ it "takes a Table and chains" do
+ manager = Arel::InsertManager.new
+ manager.into(Table.new(:users)).must_equal manager
+ end
+
+ it "converts to sql" do
+ table = Table.new :users
+ manager = Arel::InsertManager.new
+ manager.into table
+ manager.to_sql.must_be_like %{
+ INSERT INTO "users"
+ }
+ end
+ end
+
+ describe "columns" do
+ it "converts to sql" do
+ table = Table.new :users
+ manager = Arel::InsertManager.new
+ manager.into table
+ manager.columns << table[:id]
+ manager.to_sql.must_be_like %{
+ INSERT INTO "users" ("id")
+ }
+ end
+ end
+
+ describe "values" do
+ it "converts to sql" do
+ table = Table.new :users
+ manager = Arel::InsertManager.new
+ manager.into table
+
+ manager.values = Nodes::Values.new [1]
+ manager.to_sql.must_be_like %{
+ INSERT INTO "users" VALUES (1)
+ }
+ end
+
+ it "accepts sql literals" do
+ table = Table.new :users
+ manager = Arel::InsertManager.new
+ manager.into table
+
+ manager.values = Arel.sql("DEFAULT VALUES")
+ manager.to_sql.must_be_like %{
+ INSERT INTO "users" DEFAULT VALUES
+ }
+ end
+ end
+
+ describe "combo" do
+ it "combines columns and values list in order" do
+ table = Table.new :users
+ manager = Arel::InsertManager.new
+ manager.into table
+
+ manager.values = Nodes::Values.new [1, "aaron"]
+ manager.columns << table[:id]
+ manager.columns << table[:name]
+ manager.to_sql.must_be_like %{
+ INSERT INTO "users" ("id", "name") VALUES (1, 'aaron')
+ }
+ end
+ end
+
+ describe "select" do
+ it "accepts a select query in place of a VALUES clause" do
+ table = Table.new :users
+
+ manager = Arel::InsertManager.new
+ manager.into table
+
+ select = Arel::SelectManager.new
+ select.project Arel.sql("1")
+ select.project Arel.sql('"aaron"')
+
+ manager.select select
+ manager.columns << table[:id]
+ manager.columns << table[:name]
+ manager.to_sql.must_be_like %{
+ INSERT INTO "users" ("id", "name") (SELECT 1, "aaron")
+ }
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/arel/nodes/and_test.rb b/activerecord/test/cases/arel/nodes/and_test.rb
new file mode 100644
index 0000000000..eff54abd91
--- /dev/null
+++ b/activerecord/test/cases/arel/nodes/and_test.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+require_relative "../helper"
+
+module Arel
+ module Nodes
+ describe "And" do
+ describe "equality" do
+ it "is equal with equal ivars" do
+ array = [And.new(["foo", "bar"]), And.new(["foo", "bar"])]
+ assert_equal 1, array.uniq.size
+ end
+
+ it "is not equal with different ivars" do
+ array = [And.new(["foo", "bar"]), And.new(["foo", "baz"])]
+ assert_equal 2, array.uniq.size
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/arel/nodes/as_test.rb b/activerecord/test/cases/arel/nodes/as_test.rb
new file mode 100644
index 0000000000..1169ea11c9
--- /dev/null
+++ b/activerecord/test/cases/arel/nodes/as_test.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+require_relative "../helper"
+
+module Arel
+ module Nodes
+ describe "As" do
+ describe "#as" do
+ it "makes an AS node" do
+ attr = Table.new(:users)[:id]
+ as = attr.as(Arel.sql("foo"))
+ assert_equal attr, as.left
+ assert_equal "foo", as.right
+ end
+
+ it "converts right to SqlLiteral if a string" do
+ attr = Table.new(:users)[:id]
+ as = attr.as("foo")
+ assert_kind_of Arel::Nodes::SqlLiteral, as.right
+ end
+ end
+
+ describe "equality" do
+ it "is equal with equal ivars" do
+ array = [As.new("foo", "bar"), As.new("foo", "bar")]
+ assert_equal 1, array.uniq.size
+ end
+
+ it "is not equal with different ivars" do
+ array = [As.new("foo", "bar"), As.new("foo", "baz")]
+ assert_equal 2, array.uniq.size
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/arel/nodes/ascending_test.rb b/activerecord/test/cases/arel/nodes/ascending_test.rb
new file mode 100644
index 0000000000..4811e6ff5b
--- /dev/null
+++ b/activerecord/test/cases/arel/nodes/ascending_test.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+require_relative "../helper"
+
+module Arel
+ module Nodes
+ class TestAscending < Arel::Test
+ def test_construct
+ ascending = Ascending.new "zomg"
+ assert_equal "zomg", ascending.expr
+ end
+
+ def test_reverse
+ ascending = Ascending.new "zomg"
+ descending = ascending.reverse
+ assert_kind_of Descending, descending
+ assert_equal ascending.expr, descending.expr
+ end
+
+ def test_direction
+ ascending = Ascending.new "zomg"
+ assert_equal :asc, ascending.direction
+ end
+
+ def test_ascending?
+ ascending = Ascending.new "zomg"
+ assert ascending.ascending?
+ end
+
+ def test_descending?
+ ascending = Ascending.new "zomg"
+ assert_not ascending.descending?
+ end
+
+ def test_equality_with_same_ivars
+ array = [Ascending.new("zomg"), Ascending.new("zomg")]
+ assert_equal 1, array.uniq.size
+ end
+
+ def test_inequality_with_different_ivars
+ array = [Ascending.new("zomg"), Ascending.new("zomg!")]
+ assert_equal 2, array.uniq.size
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/arel/nodes/bin_test.rb b/activerecord/test/cases/arel/nodes/bin_test.rb
new file mode 100644
index 0000000000..ee2ec3cf2f
--- /dev/null
+++ b/activerecord/test/cases/arel/nodes/bin_test.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+require_relative "../helper"
+
+module Arel
+ module Nodes
+ class TestBin < Arel::Test
+ def test_new
+ assert Arel::Nodes::Bin.new("zomg")
+ end
+
+ def test_default_to_sql
+ viz = Arel::Visitors::ToSql.new Table.engine.connection_pool
+ node = Arel::Nodes::Bin.new(Arel.sql("zomg"))
+ assert_equal "zomg", viz.accept(node, Collectors::SQLString.new).value
+ end
+
+ def test_mysql_to_sql
+ viz = Arel::Visitors::MySQL.new Table.engine.connection_pool
+ node = Arel::Nodes::Bin.new(Arel.sql("zomg"))
+ assert_equal "BINARY zomg", viz.accept(node, Collectors::SQLString.new).value
+ end
+
+ def test_equality_with_same_ivars
+ array = [Bin.new("zomg"), Bin.new("zomg")]
+ assert_equal 1, array.uniq.size
+ end
+
+ def test_inequality_with_different_ivars
+ array = [Bin.new("zomg"), Bin.new("zomg!")]
+ assert_equal 2, array.uniq.size
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/arel/nodes/binary_test.rb b/activerecord/test/cases/arel/nodes/binary_test.rb
new file mode 100644
index 0000000000..d160e7cd9d
--- /dev/null
+++ b/activerecord/test/cases/arel/nodes/binary_test.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+require_relative "../helper"
+
+module Arel
+ module Nodes
+ class NodesTest < Arel::Spec
+ describe "Binary" do
+ describe "#hash" do
+ it "generates a hash based on its value" do
+ eq = Equality.new("foo", "bar")
+ eq2 = Equality.new("foo", "bar")
+ eq3 = Equality.new("bar", "baz")
+
+ assert_equal eq.hash, eq2.hash
+ assert_not_equal eq.hash, eq3.hash
+ end
+
+ it "generates a hash specific to its class" do
+ eq = Equality.new("foo", "bar")
+ neq = NotEqual.new("foo", "bar")
+
+ assert_not_equal eq.hash, neq.hash
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/arel/nodes/bind_param_test.rb b/activerecord/test/cases/arel/nodes/bind_param_test.rb
new file mode 100644
index 0000000000..37a362ece4
--- /dev/null
+++ b/activerecord/test/cases/arel/nodes/bind_param_test.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+require_relative "../helper"
+
+module Arel
+ module Nodes
+ describe "BindParam" do
+ it "is equal to other bind params with the same value" do
+ BindParam.new(1).must_equal(BindParam.new(1))
+ BindParam.new("foo").must_equal(BindParam.new("foo"))
+ end
+
+ it "is not equal to other nodes" do
+ BindParam.new(nil).wont_equal(Node.new)
+ end
+
+ it "is not equal to bind params with different values" do
+ BindParam.new(1).wont_equal(BindParam.new(2))
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/arel/nodes/case_test.rb b/activerecord/test/cases/arel/nodes/case_test.rb
new file mode 100644
index 0000000000..89861488df
--- /dev/null
+++ b/activerecord/test/cases/arel/nodes/case_test.rb
@@ -0,0 +1,86 @@
+# frozen_string_literal: true
+
+require_relative "../helper"
+
+module Arel
+ module Nodes
+ class NodesTest < Arel::Spec
+ describe "Case" do
+ describe "#initialize" do
+ it "sets case expression from first argument" do
+ node = Case.new "foo"
+
+ assert_equal "foo", node.case
+ end
+
+ it "sets default case from second argument" do
+ node = Case.new nil, "bar"
+
+ assert_equal "bar", node.default
+ end
+ end
+
+ describe "#clone" do
+ it "clones case, conditions and default" do
+ foo = Nodes.build_quoted "foo"
+
+ node = Case.new
+ node.case = foo
+ node.conditions = [When.new(foo, foo)]
+ node.default = foo
+
+ dolly = node.clone
+
+ assert_equal dolly.case, node.case
+ assert_not_same dolly.case, node.case
+
+ assert_equal dolly.conditions, node.conditions
+ assert_not_same dolly.conditions, node.conditions
+
+ assert_equal dolly.default, node.default
+ assert_not_same dolly.default, node.default
+ end
+ end
+
+ describe "equality" do
+ it "is equal with equal ivars" do
+ foo = Nodes.build_quoted "foo"
+ one = Nodes.build_quoted 1
+ zero = Nodes.build_quoted 0
+
+ case1 = Case.new foo
+ case1.conditions = [When.new(foo, one)]
+ case1.default = Else.new zero
+
+ case2 = Case.new foo
+ case2.conditions = [When.new(foo, one)]
+ case2.default = Else.new zero
+
+ array = [case1, case2]
+
+ assert_equal 1, array.uniq.size
+ end
+
+ it "is not equal with different ivars" do
+ foo = Nodes.build_quoted "foo"
+ bar = Nodes.build_quoted "bar"
+ one = Nodes.build_quoted 1
+ zero = Nodes.build_quoted 0
+
+ case1 = Case.new foo
+ case1.conditions = [When.new(foo, one)]
+ case1.default = Else.new zero
+
+ case2 = Case.new foo
+ case2.conditions = [When.new(bar, one)]
+ case2.default = Else.new zero
+
+ array = [case1, case2]
+
+ assert_equal 2, array.uniq.size
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/arel/nodes/casted_test.rb b/activerecord/test/cases/arel/nodes/casted_test.rb
new file mode 100644
index 0000000000..e27f58a4e2
--- /dev/null
+++ b/activerecord/test/cases/arel/nodes/casted_test.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+require_relative "../helper"
+
+module Arel
+ module Nodes
+ describe Casted do
+ describe "#hash" do
+ it "is equal when eql? returns true" do
+ one = Casted.new 1, 2
+ also_one = Casted.new 1, 2
+
+ assert_equal one.hash, also_one.hash
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/arel/nodes/count_test.rb b/activerecord/test/cases/arel/nodes/count_test.rb
new file mode 100644
index 0000000000..daabea6c4c
--- /dev/null
+++ b/activerecord/test/cases/arel/nodes/count_test.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+require_relative "../helper"
+
+class Arel::Nodes::CountTest < Arel::Spec
+ describe "as" do
+ it "should alias the count" do
+ table = Arel::Table.new :users
+ table[:id].count.as("foo").to_sql.must_be_like %{
+ COUNT("users"."id") AS foo
+ }
+ end
+ end
+
+ describe "eq" do
+ it "should compare the count" do
+ table = Arel::Table.new :users
+ table[:id].count.eq(2).to_sql.must_be_like %{
+ COUNT("users"."id") = 2
+ }
+ end
+ end
+
+ describe "equality" do
+ it "is equal with equal ivars" do
+ array = [Arel::Nodes::Count.new("foo"), Arel::Nodes::Count.new("foo")]
+ assert_equal 1, array.uniq.size
+ end
+
+ it "is not equal with different ivars" do
+ array = [Arel::Nodes::Count.new("foo"), Arel::Nodes::Count.new("foo!")]
+ assert_equal 2, array.uniq.size
+ end
+ end
+end
diff --git a/activerecord/test/cases/arel/nodes/delete_statement_test.rb b/activerecord/test/cases/arel/nodes/delete_statement_test.rb
new file mode 100644
index 0000000000..3f078063a4
--- /dev/null
+++ b/activerecord/test/cases/arel/nodes/delete_statement_test.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+require_relative "../helper"
+
+describe Arel::Nodes::DeleteStatement do
+ describe "#clone" do
+ it "clones wheres" do
+ statement = Arel::Nodes::DeleteStatement.new
+ statement.wheres = %w[a b c]
+
+ dolly = statement.clone
+ dolly.wheres.must_equal statement.wheres
+ dolly.wheres.wont_be_same_as statement.wheres
+ end
+ end
+
+ describe "equality" do
+ it "is equal with equal ivars" do
+ statement1 = Arel::Nodes::DeleteStatement.new
+ statement1.wheres = %w[a b c]
+ statement2 = Arel::Nodes::DeleteStatement.new
+ statement2.wheres = %w[a b c]
+ array = [statement1, statement2]
+ assert_equal 1, array.uniq.size
+ end
+
+ it "is not equal with different ivars" do
+ statement1 = Arel::Nodes::DeleteStatement.new
+ statement1.wheres = %w[a b c]
+ statement2 = Arel::Nodes::DeleteStatement.new
+ statement2.wheres = %w[1 2 3]
+ array = [statement1, statement2]
+ assert_equal 2, array.uniq.size
+ end
+ end
+end
diff --git a/activerecord/test/cases/arel/nodes/descending_test.rb b/activerecord/test/cases/arel/nodes/descending_test.rb
new file mode 100644
index 0000000000..5f1747e1da
--- /dev/null
+++ b/activerecord/test/cases/arel/nodes/descending_test.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+require_relative "../helper"
+
+module Arel
+ module Nodes
+ class TestDescending < Arel::Test
+ def test_construct
+ descending = Descending.new "zomg"
+ assert_equal "zomg", descending.expr
+ end
+
+ def test_reverse
+ descending = Descending.new "zomg"
+ ascending = descending.reverse
+ assert_kind_of Ascending, ascending
+ assert_equal descending.expr, ascending.expr
+ end
+
+ def test_direction
+ descending = Descending.new "zomg"
+ assert_equal :desc, descending.direction
+ end
+
+ def test_ascending?
+ descending = Descending.new "zomg"
+ assert_not descending.ascending?
+ end
+
+ def test_descending?
+ descending = Descending.new "zomg"
+ assert descending.descending?
+ end
+
+ def test_equality_with_same_ivars
+ array = [Descending.new("zomg"), Descending.new("zomg")]
+ assert_equal 1, array.uniq.size
+ end
+
+ def test_inequality_with_different_ivars
+ array = [Descending.new("zomg"), Descending.new("zomg!")]
+ assert_equal 2, array.uniq.size
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/arel/nodes/distinct_test.rb b/activerecord/test/cases/arel/nodes/distinct_test.rb
new file mode 100644
index 0000000000..de5f0ee588
--- /dev/null
+++ b/activerecord/test/cases/arel/nodes/distinct_test.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+require_relative "../helper"
+
+module Arel
+ module Nodes
+ describe "Distinct" do
+ describe "equality" do
+ it "is equal to other distinct nodes" do
+ array = [Distinct.new, Distinct.new]
+ assert_equal 1, array.uniq.size
+ end
+
+ it "is not equal with other nodes" do
+ array = [Distinct.new, Node.new]
+ assert_equal 2, array.uniq.size
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/arel/nodes/equality_test.rb b/activerecord/test/cases/arel/nodes/equality_test.rb
new file mode 100644
index 0000000000..e173720e86
--- /dev/null
+++ b/activerecord/test/cases/arel/nodes/equality_test.rb
@@ -0,0 +1,86 @@
+# frozen_string_literal: true
+
+require_relative "../helper"
+
+module Arel
+ module Nodes
+ describe "equality" do
+ # FIXME: backwards compat
+ describe "backwards compat" do
+ describe "operator" do
+ it "returns :==" do
+ attr = Table.new(:users)[:id]
+ left = attr.eq(10)
+ left.operator.must_equal :==
+ end
+ end
+
+ describe "operand1" do
+ it "should equal left" do
+ attr = Table.new(:users)[:id]
+ left = attr.eq(10)
+ left.left.must_equal left.operand1
+ end
+ end
+
+ describe "operand2" do
+ it "should equal right" do
+ attr = Table.new(:users)[:id]
+ left = attr.eq(10)
+ left.right.must_equal left.operand2
+ end
+ end
+
+ describe "to_sql" do
+ it "takes an engine" do
+ engine = FakeRecord::Base.new
+ engine.connection.extend Module.new {
+ attr_accessor :quote_count
+ def quote(*args) @quote_count += 1; super; end
+ def quote_column_name(*args) @quote_count += 1; super; end
+ def quote_table_name(*args) @quote_count += 1; super; end
+ }
+ engine.connection.quote_count = 0
+
+ attr = Table.new(:users)[:id]
+ test = attr.eq(10)
+ test.to_sql engine
+ engine.connection.quote_count.must_equal 3
+ end
+ end
+ end
+
+ describe "or" do
+ it "makes an OR node" do
+ attr = Table.new(:users)[:id]
+ left = attr.eq(10)
+ right = attr.eq(11)
+ node = left.or right
+ node.expr.left.must_equal left
+ node.expr.right.must_equal right
+ end
+ end
+
+ describe "and" do
+ it "makes and AND node" do
+ attr = Table.new(:users)[:id]
+ left = attr.eq(10)
+ right = attr.eq(11)
+ node = left.and right
+ node.left.must_equal left
+ node.right.must_equal right
+ end
+ end
+
+ it "is equal with equal ivars" do
+ array = [Equality.new("foo", "bar"), Equality.new("foo", "bar")]
+ assert_equal 1, array.uniq.size
+ end
+
+ it "is not equal with different ivars" do
+ array = [Equality.new("foo", "bar"), Equality.new("foo", "baz")]
+ assert_equal 2, array.uniq.size
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/arel/nodes/extract_test.rb b/activerecord/test/cases/arel/nodes/extract_test.rb
new file mode 100644
index 0000000000..8fc1e04d67
--- /dev/null
+++ b/activerecord/test/cases/arel/nodes/extract_test.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+require_relative "../helper"
+
+class Arel::Nodes::ExtractTest < Arel::Spec
+ it "should extract field" do
+ table = Arel::Table.new :users
+ table[:timestamp].extract("date").to_sql.must_be_like %{
+ EXTRACT(DATE FROM "users"."timestamp")
+ }
+ end
+
+ describe "as" do
+ it "should alias the extract" do
+ table = Arel::Table.new :users
+ table[:timestamp].extract("date").as("foo").to_sql.must_be_like %{
+ EXTRACT(DATE FROM "users"."timestamp") AS foo
+ }
+ end
+
+ it "should not mutate the extract" do
+ table = Arel::Table.new :users
+ extract = table[:timestamp].extract("date")
+ before = extract.dup
+ extract.as("foo")
+ assert_equal extract, before
+ end
+ end
+
+ describe "equality" do
+ it "is equal with equal ivars" do
+ table = Arel::Table.new :users
+ array = [table[:attr].extract("foo"), table[:attr].extract("foo")]
+ assert_equal 1, array.uniq.size
+ end
+
+ it "is not equal with different ivars" do
+ table = Arel::Table.new :users
+ array = [table[:attr].extract("foo"), table[:attr].extract("bar")]
+ assert_equal 2, array.uniq.size
+ end
+ end
+end
diff --git a/activerecord/test/cases/arel/nodes/false_test.rb b/activerecord/test/cases/arel/nodes/false_test.rb
new file mode 100644
index 0000000000..4ecf8e332e
--- /dev/null
+++ b/activerecord/test/cases/arel/nodes/false_test.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+require_relative "../helper"
+
+module Arel
+ module Nodes
+ describe "False" do
+ describe "equality" do
+ it "is equal to other false nodes" do
+ array = [False.new, False.new]
+ assert_equal 1, array.uniq.size
+ end
+
+ it "is not equal with other nodes" do
+ array = [False.new, Node.new]
+ assert_equal 2, array.uniq.size
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/arel/nodes/grouping_test.rb b/activerecord/test/cases/arel/nodes/grouping_test.rb
new file mode 100644
index 0000000000..03d5c142d5
--- /dev/null
+++ b/activerecord/test/cases/arel/nodes/grouping_test.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+require_relative "../helper"
+
+module Arel
+ module Nodes
+ class GroupingTest < Arel::Spec
+ it "should create Equality nodes" do
+ grouping = Grouping.new(Nodes.build_quoted("foo"))
+ grouping.eq("foo").to_sql.must_be_like "('foo') = 'foo'"
+ end
+
+ describe "equality" do
+ it "is equal with equal ivars" do
+ array = [Grouping.new("foo"), Grouping.new("foo")]
+ assert_equal 1, array.uniq.size
+ end
+
+ it "is not equal with different ivars" do
+ array = [Grouping.new("foo"), Grouping.new("bar")]
+ assert_equal 2, array.uniq.size
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/arel/nodes/infix_operation_test.rb b/activerecord/test/cases/arel/nodes/infix_operation_test.rb
new file mode 100644
index 0000000000..dcf2200c12
--- /dev/null
+++ b/activerecord/test/cases/arel/nodes/infix_operation_test.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+require_relative "../helper"
+
+module Arel
+ module Nodes
+ class TestInfixOperation < Arel::Test
+ def test_construct
+ operation = InfixOperation.new :+, 1, 2
+ assert_equal :+, operation.operator
+ assert_equal 1, operation.left
+ assert_equal 2, operation.right
+ end
+
+ def test_operation_alias
+ operation = InfixOperation.new :+, 1, 2
+ aliaz = operation.as("zomg")
+ assert_kind_of As, aliaz
+ assert_equal operation, aliaz.left
+ assert_equal "zomg", aliaz.right
+ end
+
+ def test_operation_ordering
+ operation = InfixOperation.new :+, 1, 2
+ ordering = operation.desc
+ assert_kind_of Descending, ordering
+ assert_equal operation, ordering.expr
+ assert ordering.descending?
+ end
+
+ def test_equality_with_same_ivars
+ array = [InfixOperation.new(:+, 1, 2), InfixOperation.new(:+, 1, 2)]
+ assert_equal 1, array.uniq.size
+ end
+
+ def test_inequality_with_different_ivars
+ array = [InfixOperation.new(:+, 1, 2), InfixOperation.new(:+, 1, 3)]
+ assert_equal 2, array.uniq.size
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/arel/nodes/insert_statement_test.rb b/activerecord/test/cases/arel/nodes/insert_statement_test.rb
new file mode 100644
index 0000000000..252a0d0d0b
--- /dev/null
+++ b/activerecord/test/cases/arel/nodes/insert_statement_test.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+require_relative "../helper"
+
+describe Arel::Nodes::InsertStatement do
+ describe "#clone" do
+ it "clones columns and values" do
+ statement = Arel::Nodes::InsertStatement.new
+ statement.columns = %w[a b c]
+ statement.values = %w[x y z]
+
+ dolly = statement.clone
+ dolly.columns.must_equal statement.columns
+ dolly.values.must_equal statement.values
+
+ dolly.columns.wont_be_same_as statement.columns
+ dolly.values.wont_be_same_as statement.values
+ end
+ end
+
+ describe "equality" do
+ it "is equal with equal ivars" do
+ statement1 = Arel::Nodes::InsertStatement.new
+ statement1.columns = %w[a b c]
+ statement1.values = %w[x y z]
+ statement2 = Arel::Nodes::InsertStatement.new
+ statement2.columns = %w[a b c]
+ statement2.values = %w[x y z]
+ array = [statement1, statement2]
+ assert_equal 1, array.uniq.size
+ end
+
+ it "is not equal with different ivars" do
+ statement1 = Arel::Nodes::InsertStatement.new
+ statement1.columns = %w[a b c]
+ statement1.values = %w[x y z]
+ statement2 = Arel::Nodes::InsertStatement.new
+ statement2.columns = %w[a b c]
+ statement2.values = %w[1 2 3]
+ array = [statement1, statement2]
+ assert_equal 2, array.uniq.size
+ end
+ end
+end
diff --git a/activerecord/test/cases/arel/nodes/named_function_test.rb b/activerecord/test/cases/arel/nodes/named_function_test.rb
new file mode 100644
index 0000000000..dbd7ae43be
--- /dev/null
+++ b/activerecord/test/cases/arel/nodes/named_function_test.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+require_relative "../helper"
+
+module Arel
+ module Nodes
+ class TestNamedFunction < Arel::Test
+ def test_construct
+ function = NamedFunction.new "omg", "zomg"
+ assert_equal "omg", function.name
+ assert_equal "zomg", function.expressions
+ end
+
+ def test_function_alias
+ function = NamedFunction.new "omg", "zomg"
+ function = function.as("wth")
+ assert_equal "omg", function.name
+ assert_equal "zomg", function.expressions
+ assert_kind_of SqlLiteral, function.alias
+ assert_equal "wth", function.alias
+ end
+
+ def test_construct_with_alias
+ function = NamedFunction.new "omg", "zomg", "wth"
+ assert_equal "omg", function.name
+ assert_equal "zomg", function.expressions
+ assert_kind_of SqlLiteral, function.alias
+ assert_equal "wth", function.alias
+ end
+
+ def test_equality_with_same_ivars
+ array = [
+ NamedFunction.new("omg", "zomg", "wth"),
+ NamedFunction.new("omg", "zomg", "wth")
+ ]
+ assert_equal 1, array.uniq.size
+ end
+
+ def test_inequality_with_different_ivars
+ array = [
+ NamedFunction.new("omg", "zomg", "wth"),
+ NamedFunction.new("zomg", "zomg", "wth")
+ ]
+ assert_equal 2, array.uniq.size
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/arel/nodes/node_test.rb b/activerecord/test/cases/arel/nodes/node_test.rb
new file mode 100644
index 0000000000..f4f07ef2c5
--- /dev/null
+++ b/activerecord/test/cases/arel/nodes/node_test.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+require_relative "../helper"
+
+module Arel
+ class TestNode < Arel::Test
+ def test_includes_factory_methods
+ assert Node.new.respond_to?(:create_join)
+ end
+
+ def test_all_nodes_are_nodes
+ Nodes.constants.map { |k|
+ Nodes.const_get(k)
+ }.grep(Class).each do |klass|
+ next if Nodes::SqlLiteral == klass
+ next if Nodes::BindParam == klass
+ next if klass.name =~ /^Arel::Nodes::(?:Test|.*Test$)/
+ assert klass.ancestors.include?(Nodes::Node), klass.name
+ end
+ end
+
+ def test_each
+ list = []
+ node = Nodes::Node.new
+ node.each { |n| list << n }
+ assert_equal [node], list
+ end
+
+ def test_generator
+ list = []
+ node = Nodes::Node.new
+ node.each.each { |n| list << n }
+ assert_equal [node], list
+ end
+
+ def test_enumerable
+ node = Nodes::Node.new
+ assert_kind_of Enumerable, node
+ end
+ end
+end
diff --git a/activerecord/test/cases/arel/nodes/not_test.rb b/activerecord/test/cases/arel/nodes/not_test.rb
new file mode 100644
index 0000000000..481e678700
--- /dev/null
+++ b/activerecord/test/cases/arel/nodes/not_test.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require_relative "../helper"
+
+module Arel
+ module Nodes
+ describe "not" do
+ describe "#not" do
+ it "makes a NOT node" do
+ attr = Table.new(:users)[:id]
+ expr = attr.eq(10)
+ node = expr.not
+ node.must_be_kind_of Not
+ node.expr.must_equal expr
+ end
+ end
+
+ describe "equality" do
+ it "is equal with equal ivars" do
+ array = [Not.new("foo"), Not.new("foo")]
+ assert_equal 1, array.uniq.size
+ end
+
+ it "is not equal with different ivars" do
+ array = [Not.new("foo"), Not.new("baz")]
+ assert_equal 2, array.uniq.size
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/arel/nodes/or_test.rb b/activerecord/test/cases/arel/nodes/or_test.rb
new file mode 100644
index 0000000000..93f826740d
--- /dev/null
+++ b/activerecord/test/cases/arel/nodes/or_test.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+require_relative "../helper"
+
+module Arel
+ module Nodes
+ describe "or" do
+ describe "#or" do
+ it "makes an OR node" do
+ attr = Table.new(:users)[:id]
+ left = attr.eq(10)
+ right = attr.eq(11)
+ node = left.or right
+ node.expr.left.must_equal left
+ node.expr.right.must_equal right
+
+ oror = node.or(right)
+ oror.expr.left.must_equal node
+ oror.expr.right.must_equal right
+ end
+ end
+
+ describe "equality" do
+ it "is equal with equal ivars" do
+ array = [Or.new("foo", "bar"), Or.new("foo", "bar")]
+ assert_equal 1, array.uniq.size
+ end
+
+ it "is not equal with different ivars" do
+ array = [Or.new("foo", "bar"), Or.new("foo", "baz")]
+ assert_equal 2, array.uniq.size
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/arel/nodes/over_test.rb b/activerecord/test/cases/arel/nodes/over_test.rb
new file mode 100644
index 0000000000..981ec2e34b
--- /dev/null
+++ b/activerecord/test/cases/arel/nodes/over_test.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+require_relative "../helper"
+
+class Arel::Nodes::OverTest < Arel::Spec
+ describe "as" do
+ it "should alias the expression" do
+ table = Arel::Table.new :users
+ table[:id].count.over.as("foo").to_sql.must_be_like %{
+ COUNT("users"."id") OVER () AS foo
+ }
+ end
+ end
+
+ describe "with literal" do
+ it "should reference the window definition by name" do
+ table = Arel::Table.new :users
+ table[:id].count.over("foo").to_sql.must_be_like %{
+ COUNT("users"."id") OVER "foo"
+ }
+ end
+ end
+
+ describe "with SQL literal" do
+ it "should reference the window definition by name" do
+ table = Arel::Table.new :users
+ table[:id].count.over(Arel.sql("foo")).to_sql.must_be_like %{
+ COUNT("users"."id") OVER foo
+ }
+ end
+ end
+
+ describe "with no expression" do
+ it "should use empty definition" do
+ table = Arel::Table.new :users
+ table[:id].count.over.to_sql.must_be_like %{
+ COUNT("users"."id") OVER ()
+ }
+ end
+ end
+
+ describe "with expression" do
+ it "should use definition in sub-expression" do
+ table = Arel::Table.new :users
+ window = Arel::Nodes::Window.new.order(table["foo"])
+ table[:id].count.over(window).to_sql.must_be_like %{
+ COUNT("users"."id") OVER (ORDER BY \"users\".\"foo\")
+ }
+ end
+ end
+
+ describe "equality" do
+ it "is equal with equal ivars" do
+ array = [
+ Arel::Nodes::Over.new("foo", "bar"),
+ Arel::Nodes::Over.new("foo", "bar")
+ ]
+ assert_equal 1, array.uniq.size
+ end
+
+ it "is not equal with different ivars" do
+ array = [
+ Arel::Nodes::Over.new("foo", "bar"),
+ Arel::Nodes::Over.new("foo", "baz")
+ ]
+ assert_equal 2, array.uniq.size
+ end
+ end
+end
diff --git a/activerecord/test/cases/arel/nodes/select_core_test.rb b/activerecord/test/cases/arel/nodes/select_core_test.rb
new file mode 100644
index 0000000000..0b698205ff
--- /dev/null
+++ b/activerecord/test/cases/arel/nodes/select_core_test.rb
@@ -0,0 +1,71 @@
+# frozen_string_literal: true
+
+require_relative "../helper"
+
+module Arel
+ module Nodes
+ class TestSelectCore < Arel::Test
+ def test_clone
+ core = Arel::Nodes::SelectCore.new
+ core.froms = %w[a b c]
+ core.projections = %w[d e f]
+ core.wheres = %w[g h i]
+
+ dolly = core.clone
+
+ assert_equal core.froms, dolly.froms
+ assert_equal core.projections, dolly.projections
+ assert_equal core.wheres, dolly.wheres
+
+ assert_not_same core.froms, dolly.froms
+ assert_not_same core.projections, dolly.projections
+ assert_not_same core.wheres, dolly.wheres
+ end
+
+ def test_set_quantifier
+ core = Arel::Nodes::SelectCore.new
+ core.set_quantifier = Arel::Nodes::Distinct.new
+ viz = Arel::Visitors::ToSql.new Table.engine.connection_pool
+ assert_match "DISTINCT", viz.accept(core, Collectors::SQLString.new).value
+ end
+
+ def test_equality_with_same_ivars
+ core1 = SelectCore.new
+ core1.froms = %w[a b c]
+ core1.projections = %w[d e f]
+ core1.wheres = %w[g h i]
+ core1.groups = %w[j k l]
+ core1.windows = %w[m n o]
+ core1.havings = %w[p q r]
+ core2 = SelectCore.new
+ core2.froms = %w[a b c]
+ core2.projections = %w[d e f]
+ core2.wheres = %w[g h i]
+ core2.groups = %w[j k l]
+ core2.windows = %w[m n o]
+ core2.havings = %w[p q r]
+ array = [core1, core2]
+ assert_equal 1, array.uniq.size
+ end
+
+ def test_inequality_with_different_ivars
+ core1 = SelectCore.new
+ core1.froms = %w[a b c]
+ core1.projections = %w[d e f]
+ core1.wheres = %w[g h i]
+ core1.groups = %w[j k l]
+ core1.windows = %w[m n o]
+ core1.havings = %w[p q r]
+ core2 = SelectCore.new
+ core2.froms = %w[a b c]
+ core2.projections = %w[d e f]
+ core2.wheres = %w[g h i]
+ core2.groups = %w[j k l]
+ core2.windows = %w[m n o]
+ core2.havings = %w[l o l]
+ array = [core1, core2]
+ assert_equal 2, array.uniq.size
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/arel/nodes/select_statement_test.rb b/activerecord/test/cases/arel/nodes/select_statement_test.rb
new file mode 100644
index 0000000000..a91605de3e
--- /dev/null
+++ b/activerecord/test/cases/arel/nodes/select_statement_test.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+require_relative "../helper"
+
+describe Arel::Nodes::SelectStatement do
+ describe "#clone" do
+ it "clones cores" do
+ statement = Arel::Nodes::SelectStatement.new %w[a b c]
+
+ dolly = statement.clone
+ dolly.cores.must_equal statement.cores
+ dolly.cores.wont_be_same_as statement.cores
+ end
+ end
+
+ describe "equality" do
+ it "is equal with equal ivars" do
+ statement1 = Arel::Nodes::SelectStatement.new %w[a b c]
+ statement1.offset = 1
+ statement1.limit = 2
+ statement1.lock = false
+ statement1.orders = %w[x y z]
+ statement1.with = "zomg"
+ statement2 = Arel::Nodes::SelectStatement.new %w[a b c]
+ statement2.offset = 1
+ statement2.limit = 2
+ statement2.lock = false
+ statement2.orders = %w[x y z]
+ statement2.with = "zomg"
+ array = [statement1, statement2]
+ assert_equal 1, array.uniq.size
+ end
+
+ it "is not equal with different ivars" do
+ statement1 = Arel::Nodes::SelectStatement.new %w[a b c]
+ statement1.offset = 1
+ statement1.limit = 2
+ statement1.lock = false
+ statement1.orders = %w[x y z]
+ statement1.with = "zomg"
+ statement2 = Arel::Nodes::SelectStatement.new %w[a b c]
+ statement2.offset = 1
+ statement2.limit = 2
+ statement2.lock = false
+ statement2.orders = %w[x y z]
+ statement2.with = "wth"
+ array = [statement1, statement2]
+ assert_equal 2, array.uniq.size
+ end
+ end
+end
diff --git a/activerecord/test/cases/arel/nodes/sql_literal_test.rb b/activerecord/test/cases/arel/nodes/sql_literal_test.rb
new file mode 100644
index 0000000000..3b95fed1f4
--- /dev/null
+++ b/activerecord/test/cases/arel/nodes/sql_literal_test.rb
@@ -0,0 +1,75 @@
+# frozen_string_literal: true
+
+require_relative "../helper"
+require "yaml"
+
+module Arel
+ module Nodes
+ class SqlLiteralTest < Arel::Spec
+ before do
+ @visitor = Visitors::ToSql.new Table.engine.connection
+ end
+
+ def compile(node)
+ @visitor.accept(node, Collectors::SQLString.new).value
+ end
+
+ describe "sql" do
+ it "makes a sql literal node" do
+ sql = Arel.sql "foo"
+ sql.must_be_kind_of Arel::Nodes::SqlLiteral
+ end
+ end
+
+ describe "count" do
+ it "makes a count node" do
+ node = SqlLiteral.new("*").count
+ compile(node).must_be_like %{ COUNT(*) }
+ end
+
+ it "makes a distinct node" do
+ node = SqlLiteral.new("*").count true
+ compile(node).must_be_like %{ COUNT(DISTINCT *) }
+ end
+ end
+
+ describe "equality" do
+ it "makes an equality node" do
+ node = SqlLiteral.new("foo").eq(1)
+ compile(node).must_be_like %{ foo = 1 }
+ end
+
+ it "is equal with equal contents" do
+ array = [SqlLiteral.new("foo"), SqlLiteral.new("foo")]
+ assert_equal 1, array.uniq.size
+ end
+
+ it "is not equal with different contents" do
+ array = [SqlLiteral.new("foo"), SqlLiteral.new("bar")]
+ assert_equal 2, array.uniq.size
+ end
+ end
+
+ describe 'grouped "or" equality' do
+ it "makes a grouping node with an or node" do
+ node = SqlLiteral.new("foo").eq_any([1, 2])
+ compile(node).must_be_like %{ (foo = 1 OR foo = 2) }
+ end
+ end
+
+ describe 'grouped "and" equality' do
+ it "makes a grouping node with an and node" do
+ node = SqlLiteral.new("foo").eq_all([1, 2])
+ compile(node).must_be_like %{ (foo = 1 AND foo = 2) }
+ end
+ end
+
+ describe "serialization" do
+ it "serializes into YAML" do
+ yaml_literal = SqlLiteral.new("foo").to_yaml
+ assert_equal("foo", YAML.load(yaml_literal))
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/arel/nodes/sum_test.rb b/activerecord/test/cases/arel/nodes/sum_test.rb
new file mode 100644
index 0000000000..5015964951
--- /dev/null
+++ b/activerecord/test/cases/arel/nodes/sum_test.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+require_relative "../helper"
+
+class Arel::Nodes::SumTest < Arel::Spec
+ describe "as" do
+ it "should alias the sum" do
+ table = Arel::Table.new :users
+ table[:id].sum.as("foo").to_sql.must_be_like %{
+ SUM("users"."id") AS foo
+ }
+ end
+ end
+
+ describe "equality" do
+ it "is equal with equal ivars" do
+ array = [Arel::Nodes::Sum.new("foo"), Arel::Nodes::Sum.new("foo")]
+ assert_equal 1, array.uniq.size
+ end
+
+ it "is not equal with different ivars" do
+ array = [Arel::Nodes::Sum.new("foo"), Arel::Nodes::Sum.new("foo!")]
+ assert_equal 2, array.uniq.size
+ end
+ end
+
+ describe "order" do
+ it "should order the sum" do
+ table = Arel::Table.new :users
+ table[:id].sum.desc.to_sql.must_be_like %{
+ SUM("users"."id") DESC
+ }
+ end
+ end
+end
diff --git a/activerecord/test/cases/arel/nodes/table_alias_test.rb b/activerecord/test/cases/arel/nodes/table_alias_test.rb
new file mode 100644
index 0000000000..c661b6771e
--- /dev/null
+++ b/activerecord/test/cases/arel/nodes/table_alias_test.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+require_relative "../helper"
+
+module Arel
+ module Nodes
+ describe "table alias" do
+ describe "equality" do
+ it "is equal with equal ivars" do
+ relation1 = Table.new(:users)
+ node1 = TableAlias.new relation1, :foo
+ relation2 = Table.new(:users)
+ node2 = TableAlias.new relation2, :foo
+ array = [node1, node2]
+ assert_equal 1, array.uniq.size
+ end
+
+ it "is not equal with different ivars" do
+ relation1 = Table.new(:users)
+ node1 = TableAlias.new relation1, :foo
+ relation2 = Table.new(:users)
+ node2 = TableAlias.new relation2, :bar
+ array = [node1, node2]
+ assert_equal 2, array.uniq.size
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/arel/nodes/true_test.rb b/activerecord/test/cases/arel/nodes/true_test.rb
new file mode 100644
index 0000000000..1e85fe7d48
--- /dev/null
+++ b/activerecord/test/cases/arel/nodes/true_test.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+require_relative "../helper"
+
+module Arel
+ module Nodes
+ describe "True" do
+ describe "equality" do
+ it "is equal to other true nodes" do
+ array = [True.new, True.new]
+ assert_equal 1, array.uniq.size
+ end
+
+ it "is not equal with other nodes" do
+ array = [True.new, Node.new]
+ assert_equal 2, array.uniq.size
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/arel/nodes/unary_operation_test.rb b/activerecord/test/cases/arel/nodes/unary_operation_test.rb
new file mode 100644
index 0000000000..f0dd0c625c
--- /dev/null
+++ b/activerecord/test/cases/arel/nodes/unary_operation_test.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+require_relative "../helper"
+
+module Arel
+ module Nodes
+ class TestUnaryOperation < Arel::Test
+ def test_construct
+ operation = UnaryOperation.new :-, 1
+ assert_equal :-, operation.operator
+ assert_equal 1, operation.expr
+ end
+
+ def test_operation_alias
+ operation = UnaryOperation.new :-, 1
+ aliaz = operation.as("zomg")
+ assert_kind_of As, aliaz
+ assert_equal operation, aliaz.left
+ assert_equal "zomg", aliaz.right
+ end
+
+ def test_operation_ordering
+ operation = UnaryOperation.new :-, 1
+ ordering = operation.desc
+ assert_kind_of Descending, ordering
+ assert_equal operation, ordering.expr
+ assert ordering.descending?
+ end
+
+ def test_equality_with_same_ivars
+ array = [UnaryOperation.new(:-, 1), UnaryOperation.new(:-, 1)]
+ assert_equal 1, array.uniq.size
+ end
+
+ def test_inequality_with_different_ivars
+ array = [UnaryOperation.new(:-, 1), UnaryOperation.new(:-, 2)]
+ assert_equal 2, array.uniq.size
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/arel/nodes/update_statement_test.rb b/activerecord/test/cases/arel/nodes/update_statement_test.rb
new file mode 100644
index 0000000000..a83ce32f68
--- /dev/null
+++ b/activerecord/test/cases/arel/nodes/update_statement_test.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+
+require_relative "../helper"
+
+describe Arel::Nodes::UpdateStatement do
+ describe "#clone" do
+ it "clones wheres and values" do
+ statement = Arel::Nodes::UpdateStatement.new
+ statement.wheres = %w[a b c]
+ statement.values = %w[x y z]
+
+ dolly = statement.clone
+ dolly.wheres.must_equal statement.wheres
+ dolly.wheres.wont_be_same_as statement.wheres
+
+ dolly.values.must_equal statement.values
+ dolly.values.wont_be_same_as statement.values
+ end
+ end
+
+ describe "equality" do
+ it "is equal with equal ivars" do
+ statement1 = Arel::Nodes::UpdateStatement.new
+ statement1.relation = "zomg"
+ statement1.wheres = 2
+ statement1.values = false
+ statement1.orders = %w[x y z]
+ statement1.limit = 42
+ statement1.key = "zomg"
+ statement2 = Arel::Nodes::UpdateStatement.new
+ statement2.relation = "zomg"
+ statement2.wheres = 2
+ statement2.values = false
+ statement2.orders = %w[x y z]
+ statement2.limit = 42
+ statement2.key = "zomg"
+ array = [statement1, statement2]
+ assert_equal 1, array.uniq.size
+ end
+
+ it "is not equal with different ivars" do
+ statement1 = Arel::Nodes::UpdateStatement.new
+ statement1.relation = "zomg"
+ statement1.wheres = 2
+ statement1.values = false
+ statement1.orders = %w[x y z]
+ statement1.limit = 42
+ statement1.key = "zomg"
+ statement2 = Arel::Nodes::UpdateStatement.new
+ statement2.relation = "zomg"
+ statement2.wheres = 2
+ statement2.values = false
+ statement2.orders = %w[x y z]
+ statement2.limit = 42
+ statement2.key = "wth"
+ array = [statement1, statement2]
+ assert_equal 2, array.uniq.size
+ end
+ end
+end
diff --git a/activerecord/test/cases/arel/nodes/window_test.rb b/activerecord/test/cases/arel/nodes/window_test.rb
new file mode 100644
index 0000000000..729b0556a4
--- /dev/null
+++ b/activerecord/test/cases/arel/nodes/window_test.rb
@@ -0,0 +1,81 @@
+# frozen_string_literal: true
+
+require_relative "../helper"
+
+module Arel
+ module Nodes
+ describe "Window" do
+ describe "equality" do
+ it "is equal with equal ivars" do
+ window1 = Window.new
+ window1.orders = [1, 2]
+ window1.partitions = [1]
+ window1.frame 3
+ window2 = Window.new
+ window2.orders = [1, 2]
+ window2.partitions = [1]
+ window2.frame 3
+ array = [window1, window2]
+ assert_equal 1, array.uniq.size
+ end
+
+ it "is not equal with different ivars" do
+ window1 = Window.new
+ window1.orders = [1, 2]
+ window1.partitions = [1]
+ window1.frame 3
+ window2 = Window.new
+ window2.orders = [1, 2]
+ window1.partitions = [1]
+ window2.frame 4
+ array = [window1, window2]
+ assert_equal 2, array.uniq.size
+ end
+ end
+ end
+
+ describe "NamedWindow" do
+ describe "equality" do
+ it "is equal with equal ivars" do
+ window1 = NamedWindow.new "foo"
+ window1.orders = [1, 2]
+ window1.partitions = [1]
+ window1.frame 3
+ window2 = NamedWindow.new "foo"
+ window2.orders = [1, 2]
+ window2.partitions = [1]
+ window2.frame 3
+ array = [window1, window2]
+ assert_equal 1, array.uniq.size
+ end
+
+ it "is not equal with different ivars" do
+ window1 = NamedWindow.new "foo"
+ window1.orders = [1, 2]
+ window1.partitions = [1]
+ window1.frame 3
+ window2 = NamedWindow.new "bar"
+ window2.orders = [1, 2]
+ window2.partitions = [1]
+ window2.frame 3
+ array = [window1, window2]
+ assert_equal 2, array.uniq.size
+ end
+ end
+ end
+
+ describe "CurrentRow" do
+ describe "equality" do
+ it "is equal to other current row nodes" do
+ array = [CurrentRow.new, CurrentRow.new]
+ assert_equal 1, array.uniq.size
+ end
+
+ it "is not equal with other nodes" do
+ array = [CurrentRow.new, Node.new]
+ assert_equal 2, array.uniq.size
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/arel/nodes_test.rb b/activerecord/test/cases/arel/nodes_test.rb
new file mode 100644
index 0000000000..9021de0d20
--- /dev/null
+++ b/activerecord/test/cases/arel/nodes_test.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+require_relative "helper"
+
+module Arel
+ module Nodes
+ class TestNodes < Arel::Test
+ def test_every_arel_nodes_have_hash_eql_eqeq_from_same_class
+ # #descendants code from activesupport
+ node_descendants = []
+ ObjectSpace.each_object(Arel::Nodes::Node.singleton_class) do |k|
+ next if k.respond_to?(:singleton_class?) && k.singleton_class?
+ node_descendants.unshift k unless k == self
+ end
+ node_descendants.delete(Arel::Nodes::Node)
+ node_descendants.delete(Arel::Nodes::NodeExpression)
+
+ bad_node_descendants = node_descendants.reject do |subnode|
+ eqeq_owner = subnode.instance_method(:==).owner
+ eql_owner = subnode.instance_method(:eql?).owner
+ hash_owner = subnode.instance_method(:hash).owner
+
+ eqeq_owner < Arel::Nodes::Node &&
+ eqeq_owner == eql_owner &&
+ eqeq_owner == hash_owner
+ end
+
+ problem_msg = "Some subclasses of Arel::Nodes::Node do not have a" \
+ " #== or #eql? or #hash defined from the same class as the others"
+ assert_empty bad_node_descendants, problem_msg
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/arel/select_manager_test.rb b/activerecord/test/cases/arel/select_manager_test.rb
new file mode 100644
index 0000000000..c17487ae88
--- /dev/null
+++ b/activerecord/test/cases/arel/select_manager_test.rb
@@ -0,0 +1,1225 @@
+# frozen_string_literal: true
+
+require_relative "helper"
+
+module Arel
+ class SelectManagerTest < Arel::Spec
+ def test_join_sources
+ manager = Arel::SelectManager.new
+ manager.join_sources << Arel::Nodes::StringJoin.new(Nodes.build_quoted("foo"))
+ assert_equal "SELECT FROM 'foo'", manager.to_sql
+ end
+
+ describe "backwards compatibility" do
+ describe "project" do
+ it "accepts symbols as sql literals" do
+ table = Table.new :users
+ manager = Arel::SelectManager.new
+ manager.project :id
+ manager.from table
+ manager.to_sql.must_be_like %{
+ SELECT id FROM "users"
+ }
+ end
+ end
+
+ describe "order" do
+ it "accepts symbols" do
+ table = Table.new :users
+ manager = Arel::SelectManager.new
+ manager.project Nodes::SqlLiteral.new "*"
+ manager.from table
+ manager.order :foo
+ manager.to_sql.must_be_like %{ SELECT * FROM "users" ORDER BY foo }
+ end
+ end
+
+ describe "group" do
+ it "takes a symbol" do
+ table = Table.new :users
+ manager = Arel::SelectManager.new
+ manager.from table
+ manager.group :foo
+ manager.to_sql.must_be_like %{ SELECT FROM "users" GROUP BY foo }
+ end
+ end
+
+ describe "as" do
+ it "makes an AS node by grouping the AST" do
+ manager = Arel::SelectManager.new
+ as = manager.as(Arel.sql("foo"))
+ assert_kind_of Arel::Nodes::Grouping, as.left
+ assert_equal manager.ast, as.left.expr
+ assert_equal "foo", as.right
+ end
+
+ it "converts right to SqlLiteral if a string" do
+ manager = Arel::SelectManager.new
+ as = manager.as("foo")
+ assert_kind_of Arel::Nodes::SqlLiteral, as.right
+ end
+
+ it "can make a subselect" do
+ manager = Arel::SelectManager.new
+ manager.project Arel.star
+ manager.from Arel.sql("zomg")
+ as = manager.as(Arel.sql("foo"))
+
+ manager = Arel::SelectManager.new
+ manager.project Arel.sql("name")
+ manager.from as
+ manager.to_sql.must_be_like "SELECT name FROM (SELECT * FROM zomg) foo"
+ end
+ end
+
+ describe "from" do
+ it "ignores strings when table of same name exists" do
+ table = Table.new :users
+ manager = Arel::SelectManager.new
+
+ manager.from table
+ manager.from "users"
+ manager.project table["id"]
+ manager.to_sql.must_be_like 'SELECT "users"."id" FROM users'
+ end
+
+ it "should support any ast" do
+ table = Table.new :users
+ manager1 = Arel::SelectManager.new
+
+ manager2 = Arel::SelectManager.new
+ manager2.project(Arel.sql("*"))
+ manager2.from table
+
+ manager1.project Arel.sql("lol")
+ as = manager2.as Arel.sql("omg")
+ manager1.from(as)
+
+ manager1.to_sql.must_be_like %{
+ SELECT lol FROM (SELECT * FROM "users") omg
+ }
+ end
+ end
+
+ describe "having" do
+ it "converts strings to SQLLiterals" do
+ table = Table.new :users
+ mgr = table.from
+ mgr.having Arel.sql("foo")
+ mgr.to_sql.must_be_like %{ SELECT FROM "users" HAVING foo }
+ end
+
+ it "can have multiple items specified separately" do
+ table = Table.new :users
+ mgr = table.from
+ mgr.having Arel.sql("foo")
+ mgr.having Arel.sql("bar")
+ mgr.to_sql.must_be_like %{ SELECT FROM "users" HAVING foo AND bar }
+ end
+
+ it "can receive any node" do
+ table = Table.new :users
+ mgr = table.from
+ mgr.having Arel::Nodes::And.new([Arel.sql("foo"), Arel.sql("bar")])
+ mgr.to_sql.must_be_like %{ SELECT FROM "users" HAVING foo AND bar }
+ end
+ end
+
+ describe "on" do
+ it "converts to sqlliterals" do
+ table = Table.new :users
+ right = table.alias
+ mgr = table.from
+ mgr.join(right).on("omg")
+ mgr.to_sql.must_be_like %{ SELECT FROM "users" INNER JOIN "users" "users_2" ON omg }
+ end
+
+ it "converts to sqlliterals with multiple items" do
+ table = Table.new :users
+ right = table.alias
+ mgr = table.from
+ mgr.join(right).on("omg", "123")
+ mgr.to_sql.must_be_like %{ SELECT FROM "users" INNER JOIN "users" "users_2" ON omg AND 123 }
+ end
+ end
+ end
+
+ describe "clone" do
+ it "creates new cores" do
+ table = Table.new :users, as: "foo"
+ mgr = table.from
+ m2 = mgr.clone
+ m2.project "foo"
+ mgr.to_sql.wont_equal m2.to_sql
+ end
+
+ it "makes updates to the correct copy" do
+ table = Table.new :users, as: "foo"
+ mgr = table.from
+ m2 = mgr.clone
+ m3 = m2.clone
+ m2.project "foo"
+ mgr.to_sql.wont_equal m2.to_sql
+ m3.to_sql.must_equal mgr.to_sql
+ end
+ end
+
+ describe "initialize" do
+ it "uses alias in sql" do
+ table = Table.new :users, as: "foo"
+ mgr = table.from
+ mgr.skip 10
+ mgr.to_sql.must_be_like %{ SELECT FROM "users" "foo" OFFSET 10 }
+ end
+ end
+
+ describe "skip" do
+ it "should add an offset" do
+ table = Table.new :users
+ mgr = table.from
+ mgr.skip 10
+ mgr.to_sql.must_be_like %{ SELECT FROM "users" OFFSET 10 }
+ end
+
+ it "should chain" do
+ table = Table.new :users
+ mgr = table.from
+ mgr.skip(10).to_sql.must_be_like %{ SELECT FROM "users" OFFSET 10 }
+ end
+ end
+
+ describe "offset" do
+ it "should add an offset" do
+ table = Table.new :users
+ mgr = table.from
+ mgr.offset = 10
+ mgr.to_sql.must_be_like %{ SELECT FROM "users" OFFSET 10 }
+ end
+
+ it "should remove an offset" do
+ table = Table.new :users
+ mgr = table.from
+ mgr.offset = 10
+ mgr.to_sql.must_be_like %{ SELECT FROM "users" OFFSET 10 }
+
+ mgr.offset = nil
+ mgr.to_sql.must_be_like %{ SELECT FROM "users" }
+ end
+
+ it "should return the offset" do
+ table = Table.new :users
+ mgr = table.from
+ mgr.offset = 10
+ assert_equal 10, mgr.offset
+ end
+ end
+
+ describe "exists" do
+ it "should create an exists clause" do
+ table = Table.new(:users)
+ manager = Arel::SelectManager.new table
+ manager.project Nodes::SqlLiteral.new "*"
+ m2 = Arel::SelectManager.new
+ m2.project manager.exists
+ m2.to_sql.must_be_like %{ SELECT EXISTS (#{manager.to_sql}) }
+ end
+
+ it "can be aliased" do
+ table = Table.new(:users)
+ manager = Arel::SelectManager.new table
+ manager.project Nodes::SqlLiteral.new "*"
+ m2 = Arel::SelectManager.new
+ m2.project manager.exists.as("foo")
+ m2.to_sql.must_be_like %{ SELECT EXISTS (#{manager.to_sql}) AS foo }
+ end
+ end
+
+ describe "union" do
+ before do
+ table = Table.new :users
+ @m1 = Arel::SelectManager.new table
+ @m1.project Arel.star
+ @m1.where(table[:age].lt(18))
+
+ @m2 = Arel::SelectManager.new table
+ @m2.project Arel.star
+ @m2.where(table[:age].gt(99))
+ end
+
+ it "should union two managers" do
+ # FIXME should this union "managers" or "statements" ?
+ # FIXME this probably shouldn't return a node
+ node = @m1.union @m2
+
+ # maybe FIXME: decide when wrapper parens are needed
+ node.to_sql.must_be_like %{
+ ( SELECT * FROM "users" WHERE "users"."age" < 18 UNION SELECT * FROM "users" WHERE "users"."age" > 99 )
+ }
+ end
+
+ it "should union all" do
+ node = @m1.union :all, @m2
+
+ node.to_sql.must_be_like %{
+ ( SELECT * FROM "users" WHERE "users"."age" < 18 UNION ALL SELECT * FROM "users" WHERE "users"."age" > 99 )
+ }
+ end
+ end
+
+ describe "intersect" do
+ before do
+ table = Table.new :users
+ @m1 = Arel::SelectManager.new table
+ @m1.project Arel.star
+ @m1.where(table[:age].gt(18))
+
+ @m2 = Arel::SelectManager.new table
+ @m2.project Arel.star
+ @m2.where(table[:age].lt(99))
+ end
+
+ it "should intersect two managers" do
+ # FIXME should this intersect "managers" or "statements" ?
+ # FIXME this probably shouldn't return a node
+ node = @m1.intersect @m2
+
+ # maybe FIXME: decide when wrapper parens are needed
+ node.to_sql.must_be_like %{
+ ( SELECT * FROM "users" WHERE "users"."age" > 18 INTERSECT SELECT * FROM "users" WHERE "users"."age" < 99 )
+ }
+ end
+ end
+
+ describe "except" do
+ before do
+ table = Table.new :users
+ @m1 = Arel::SelectManager.new table
+ @m1.project Arel.star
+ @m1.where(table[:age].between(18..60))
+
+ @m2 = Arel::SelectManager.new table
+ @m2.project Arel.star
+ @m2.where(table[:age].between(40..99))
+ end
+
+ it "should except two managers" do
+ # FIXME should this except "managers" or "statements" ?
+ # FIXME this probably shouldn't return a node
+ node = @m1.except @m2
+
+ # maybe FIXME: decide when wrapper parens are needed
+ node.to_sql.must_be_like %{
+ ( SELECT * FROM "users" WHERE "users"."age" BETWEEN 18 AND 60 EXCEPT SELECT * FROM "users" WHERE "users"."age" BETWEEN 40 AND 99 )
+ }
+ end
+ end
+
+ describe "with" do
+ it "should support basic WITH" do
+ users = Table.new(:users)
+ users_top = Table.new(:users_top)
+ comments = Table.new(:comments)
+
+ top = users.project(users[:id]).where(users[:karma].gt(100))
+ users_as = Arel::Nodes::As.new(users_top, top)
+ select_manager = comments.project(Arel.star).with(users_as)
+ .where(comments[:author_id].in(users_top.project(users_top[:id])))
+
+ select_manager.to_sql.must_be_like %{
+ WITH "users_top" AS (SELECT "users"."id" FROM "users" WHERE "users"."karma" > 100) SELECT * FROM "comments" WHERE "comments"."author_id" IN (SELECT "users_top"."id" FROM "users_top")
+ }
+ end
+
+ it "should support WITH RECURSIVE" do
+ comments = Table.new(:comments)
+ comments_id = comments[:id]
+ comments_parent_id = comments[:parent_id]
+
+ replies = Table.new(:replies)
+ replies_id = replies[:id]
+
+ recursive_term = Arel::SelectManager.new
+ recursive_term.from(comments).project(comments_id, comments_parent_id).where(comments_id.eq 42)
+
+ non_recursive_term = Arel::SelectManager.new
+ non_recursive_term.from(comments).project(comments_id, comments_parent_id).join(replies).on(comments_parent_id.eq replies_id)
+
+ union = recursive_term.union(non_recursive_term)
+
+ as_statement = Arel::Nodes::As.new replies, union
+
+ manager = Arel::SelectManager.new
+ manager.with(:recursive, as_statement).from(replies).project(Arel.star)
+
+ sql = manager.to_sql
+ sql.must_be_like %{
+ WITH RECURSIVE "replies" AS (
+ SELECT "comments"."id", "comments"."parent_id" FROM "comments" WHERE "comments"."id" = 42
+ UNION
+ SELECT "comments"."id", "comments"."parent_id" FROM "comments" INNER JOIN "replies" ON "comments"."parent_id" = "replies"."id"
+ )
+ SELECT * FROM "replies"
+ }
+ end
+ end
+
+ describe "ast" do
+ it "should return the ast" do
+ table = Table.new :users
+ mgr = table.from
+ assert mgr.ast
+ end
+
+ it "should allow orders to work when the ast is grepped" do
+ table = Table.new :users
+ mgr = table.from
+ mgr.project Arel.sql "*"
+ mgr.from table
+ mgr.orders << Arel::Nodes::Ascending.new(Arel.sql("foo"))
+ mgr.ast.grep(Arel::Nodes::OuterJoin)
+ mgr.to_sql.must_be_like %{ SELECT * FROM "users" ORDER BY foo ASC }
+ end
+ end
+
+ describe "taken" do
+ it "should return limit" do
+ manager = Arel::SelectManager.new
+ manager.take 10
+ manager.taken.must_equal 10
+ end
+ end
+
+ describe "lock" do
+ # This should fail on other databases
+ it "adds a lock node" do
+ table = Table.new :users
+ mgr = table.from
+ mgr.lock.to_sql.must_be_like %{ SELECT FROM "users" FOR UPDATE }
+ end
+ end
+
+ describe "orders" do
+ it "returns order clauses" do
+ table = Table.new :users
+ manager = Arel::SelectManager.new
+ order = table[:id]
+ manager.order table[:id]
+ manager.orders.must_equal [order]
+ end
+ end
+
+ describe "order" do
+ it "generates order clauses" do
+ table = Table.new :users
+ manager = Arel::SelectManager.new
+ manager.project Nodes::SqlLiteral.new "*"
+ manager.from table
+ manager.order table[:id]
+ manager.to_sql.must_be_like %{
+ SELECT * FROM "users" ORDER BY "users"."id"
+ }
+ end
+
+ # FIXME: I would like to deprecate this
+ it "takes *args" do
+ table = Table.new :users
+ manager = Arel::SelectManager.new
+ manager.project Nodes::SqlLiteral.new "*"
+ manager.from table
+ manager.order table[:id], table[:name]
+ manager.to_sql.must_be_like %{
+ SELECT * FROM "users" ORDER BY "users"."id", "users"."name"
+ }
+ end
+
+ it "chains" do
+ table = Table.new :users
+ manager = Arel::SelectManager.new
+ manager.order(table[:id]).must_equal manager
+ end
+
+ it "has order attributes" do
+ table = Table.new :users
+ manager = Arel::SelectManager.new
+ manager.project Nodes::SqlLiteral.new "*"
+ manager.from table
+ manager.order table[:id].desc
+ manager.to_sql.must_be_like %{
+ SELECT * FROM "users" ORDER BY "users"."id" DESC
+ }
+ end
+ end
+
+ describe "on" do
+ it "takes two params" do
+ left = Table.new :users
+ right = left.alias
+ predicate = left[:id].eq(right[:id])
+ manager = Arel::SelectManager.new
+
+ manager.from left
+ manager.join(right).on(predicate, predicate)
+ manager.to_sql.must_be_like %{
+ SELECT FROM "users"
+ INNER JOIN "users" "users_2"
+ ON "users"."id" = "users_2"."id" AND
+ "users"."id" = "users_2"."id"
+ }
+ end
+
+ it "takes three params" do
+ left = Table.new :users
+ right = left.alias
+ predicate = left[:id].eq(right[:id])
+ manager = Arel::SelectManager.new
+
+ manager.from left
+ manager.join(right).on(
+ predicate,
+ predicate,
+ left[:name].eq(right[:name])
+ )
+ manager.to_sql.must_be_like %{
+ SELECT FROM "users"
+ INNER JOIN "users" "users_2"
+ ON "users"."id" = "users_2"."id" AND
+ "users"."id" = "users_2"."id" AND
+ "users"."name" = "users_2"."name"
+ }
+ end
+ end
+
+ it "should hand back froms" do
+ relation = Arel::SelectManager.new
+ assert_equal [], relation.froms
+ end
+
+ it "should create and nodes" do
+ relation = Arel::SelectManager.new
+ children = ["foo", "bar", "baz"]
+ clause = relation.create_and children
+ assert_kind_of Arel::Nodes::And, clause
+ assert_equal children, clause.children
+ end
+
+ it "should create insert managers" do
+ relation = Arel::SelectManager.new
+ insert = relation.create_insert
+ assert_kind_of Arel::InsertManager, insert
+ end
+
+ it "should create join nodes" do
+ relation = Arel::SelectManager.new
+ join = relation.create_join "foo", "bar"
+ assert_kind_of Arel::Nodes::InnerJoin, join
+ assert_equal "foo", join.left
+ assert_equal "bar", join.right
+ end
+
+ it "should create join nodes with a full outer join klass" do
+ relation = Arel::SelectManager.new
+ join = relation.create_join "foo", "bar", Arel::Nodes::FullOuterJoin
+ assert_kind_of Arel::Nodes::FullOuterJoin, join
+ assert_equal "foo", join.left
+ assert_equal "bar", join.right
+ end
+
+ it "should create join nodes with a outer join klass" do
+ relation = Arel::SelectManager.new
+ join = relation.create_join "foo", "bar", Arel::Nodes::OuterJoin
+ assert_kind_of Arel::Nodes::OuterJoin, join
+ assert_equal "foo", join.left
+ assert_equal "bar", join.right
+ end
+
+ it "should create join nodes with a right outer join klass" do
+ relation = Arel::SelectManager.new
+ join = relation.create_join "foo", "bar", Arel::Nodes::RightOuterJoin
+ assert_kind_of Arel::Nodes::RightOuterJoin, join
+ assert_equal "foo", join.left
+ assert_equal "bar", join.right
+ end
+
+ describe "join" do
+ it "responds to join" do
+ left = Table.new :users
+ right = left.alias
+ predicate = left[:id].eq(right[:id])
+ manager = Arel::SelectManager.new
+
+ manager.from left
+ manager.join(right).on(predicate)
+ manager.to_sql.must_be_like %{
+ SELECT FROM "users"
+ INNER JOIN "users" "users_2"
+ ON "users"."id" = "users_2"."id"
+ }
+ end
+
+ it "takes a class" do
+ left = Table.new :users
+ right = left.alias
+ predicate = left[:id].eq(right[:id])
+ manager = Arel::SelectManager.new
+
+ manager.from left
+ manager.join(right, Nodes::OuterJoin).on(predicate)
+ manager.to_sql.must_be_like %{
+ SELECT FROM "users"
+ LEFT OUTER JOIN "users" "users_2"
+ ON "users"."id" = "users_2"."id"
+ }
+ end
+
+ it "takes the full outer join class" do
+ left = Table.new :users
+ right = left.alias
+ predicate = left[:id].eq(right[:id])
+ manager = Arel::SelectManager.new
+
+ manager.from left
+ manager.join(right, Nodes::FullOuterJoin).on(predicate)
+ manager.to_sql.must_be_like %{
+ SELECT FROM "users"
+ FULL OUTER JOIN "users" "users_2"
+ ON "users"."id" = "users_2"."id"
+ }
+ end
+
+ it "takes the right outer join class" do
+ left = Table.new :users
+ right = left.alias
+ predicate = left[:id].eq(right[:id])
+ manager = Arel::SelectManager.new
+
+ manager.from left
+ manager.join(right, Nodes::RightOuterJoin).on(predicate)
+ manager.to_sql.must_be_like %{
+ SELECT FROM "users"
+ RIGHT OUTER JOIN "users" "users_2"
+ ON "users"."id" = "users_2"."id"
+ }
+ end
+
+ it "noops on nil" do
+ manager = Arel::SelectManager.new
+ manager.join(nil).must_equal manager
+ end
+
+ it "raises EmptyJoinError on empty" do
+ left = Table.new :users
+ manager = Arel::SelectManager.new
+
+ manager.from left
+ assert_raises(EmptyJoinError) do
+ manager.join("")
+ end
+ end
+ end
+
+ describe "outer join" do
+ it "responds to join" do
+ left = Table.new :users
+ right = left.alias
+ predicate = left[:id].eq(right[:id])
+ manager = Arel::SelectManager.new
+
+ manager.from left
+ manager.outer_join(right).on(predicate)
+ manager.to_sql.must_be_like %{
+ SELECT FROM "users"
+ LEFT OUTER JOIN "users" "users_2"
+ ON "users"."id" = "users_2"."id"
+ }
+ end
+
+ it "noops on nil" do
+ manager = Arel::SelectManager.new
+ manager.outer_join(nil).must_equal manager
+ end
+ end
+
+ describe "joins" do
+ it "returns inner join sql" do
+ table = Table.new :users
+ aliaz = table.alias
+ manager = Arel::SelectManager.new
+ manager.from Nodes::InnerJoin.new(aliaz, table[:id].eq(aliaz[:id]))
+ assert_match 'INNER JOIN "users" "users_2" "users"."id" = "users_2"."id"',
+ manager.to_sql
+ end
+
+ it "returns outer join sql" do
+ table = Table.new :users
+ aliaz = table.alias
+ manager = Arel::SelectManager.new
+ manager.from Nodes::OuterJoin.new(aliaz, table[:id].eq(aliaz[:id]))
+ assert_match 'LEFT OUTER JOIN "users" "users_2" "users"."id" = "users_2"."id"',
+ manager.to_sql
+ end
+
+ it "can have a non-table alias as relation name" do
+ users = Table.new :users
+ comments = Table.new :comments
+
+ counts = comments.from.
+ group(comments[:user_id]).
+ project(
+ comments[:user_id].as("user_id"),
+ comments[:user_id].count.as("count")
+ ).as("counts")
+
+ joins = users.join(counts).on(counts[:user_id].eq(10))
+ joins.to_sql.must_be_like %{
+ SELECT FROM "users" INNER JOIN (SELECT "comments"."user_id" AS user_id, COUNT("comments"."user_id") AS count FROM "comments" GROUP BY "comments"."user_id") counts ON counts."user_id" = 10
+ }
+ end
+
+ it "joins itself" do
+ left = Table.new :users
+ right = left.alias
+ predicate = left[:id].eq(right[:id])
+
+ mgr = left.join(right)
+ mgr.project Nodes::SqlLiteral.new("*")
+ mgr.on(predicate).must_equal mgr
+
+ mgr.to_sql.must_be_like %{
+ SELECT * FROM "users"
+ INNER JOIN "users" "users_2"
+ ON "users"."id" = "users_2"."id"
+ }
+ end
+
+ it "returns string join sql" do
+ manager = Arel::SelectManager.new
+ manager.from Nodes::StringJoin.new(Nodes.build_quoted("hello"))
+ assert_match "'hello'", manager.to_sql
+ end
+ end
+
+ describe "group" do
+ it "takes an attribute" do
+ table = Table.new :users
+ manager = Arel::SelectManager.new
+ manager.from table
+ manager.group table[:id]
+ manager.to_sql.must_be_like %{
+ SELECT FROM "users" GROUP BY "users"."id"
+ }
+ end
+
+ it "chains" do
+ table = Table.new :users
+ manager = Arel::SelectManager.new
+ manager.group(table[:id]).must_equal manager
+ end
+
+ it "takes multiple args" do
+ table = Table.new :users
+ manager = Arel::SelectManager.new
+ manager.from table
+ manager.group table[:id], table[:name]
+ manager.to_sql.must_be_like %{
+ SELECT FROM "users" GROUP BY "users"."id", "users"."name"
+ }
+ end
+
+ # FIXME: backwards compat
+ it "makes strings literals" do
+ table = Table.new :users
+ manager = Arel::SelectManager.new
+ manager.from table
+ manager.group "foo"
+ manager.to_sql.must_be_like %{ SELECT FROM "users" GROUP BY foo }
+ end
+ end
+
+ describe "window definition" do
+ it "can be empty" do
+ table = Table.new :users
+ manager = Arel::SelectManager.new
+ manager.from table
+ manager.window("a_window")
+ manager.to_sql.must_be_like %{
+ SELECT FROM "users" WINDOW "a_window" AS ()
+ }
+ end
+
+ it "takes an order" do
+ table = Table.new :users
+ manager = Arel::SelectManager.new
+ manager.from table
+ manager.window("a_window").order(table["foo"].asc)
+ manager.to_sql.must_be_like %{
+ SELECT FROM "users" WINDOW "a_window" AS (ORDER BY "users"."foo" ASC)
+ }
+ end
+
+ it "takes an order with multiple columns" do
+ table = Table.new :users
+ manager = Arel::SelectManager.new
+ manager.from table
+ manager.window("a_window").order(table["foo"].asc, table["bar"].desc)
+ manager.to_sql.must_be_like %{
+ SELECT FROM "users" WINDOW "a_window" AS (ORDER BY "users"."foo" ASC, "users"."bar" DESC)
+ }
+ end
+
+ it "takes a partition" do
+ table = Table.new :users
+ manager = Arel::SelectManager.new
+ manager.from table
+ manager.window("a_window").partition(table["bar"])
+ manager.to_sql.must_be_like %{
+ SELECT FROM "users" WINDOW "a_window" AS (PARTITION BY "users"."bar")
+ }
+ end
+
+ it "takes a partition and an order" do
+ table = Table.new :users
+ manager = Arel::SelectManager.new
+ manager.from table
+ manager.window("a_window").partition(table["foo"]).order(table["foo"].asc)
+ manager.to_sql.must_be_like %{
+ SELECT FROM "users" WINDOW "a_window" AS (PARTITION BY "users"."foo"
+ ORDER BY "users"."foo" ASC)
+ }
+ end
+
+ it "takes a partition with multiple columns" do
+ table = Table.new :users
+ manager = Arel::SelectManager.new
+ manager.from table
+ manager.window("a_window").partition(table["bar"], table["baz"])
+ manager.to_sql.must_be_like %{
+ SELECT FROM "users" WINDOW "a_window" AS (PARTITION BY "users"."bar", "users"."baz")
+ }
+ end
+
+ it "takes a rows frame, unbounded preceding" do
+ table = Table.new :users
+ manager = Arel::SelectManager.new
+ manager.from table
+ manager.window("a_window").rows(Arel::Nodes::Preceding.new)
+ manager.to_sql.must_be_like %{
+ SELECT FROM "users" WINDOW "a_window" AS (ROWS UNBOUNDED PRECEDING)
+ }
+ end
+
+ it "takes a rows frame, bounded preceding" do
+ table = Table.new :users
+ manager = Arel::SelectManager.new
+ manager.from table
+ manager.window("a_window").rows(Arel::Nodes::Preceding.new(5))
+ manager.to_sql.must_be_like %{
+ SELECT FROM "users" WINDOW "a_window" AS (ROWS 5 PRECEDING)
+ }
+ end
+
+ it "takes a rows frame, unbounded following" do
+ table = Table.new :users
+ manager = Arel::SelectManager.new
+ manager.from table
+ manager.window("a_window").rows(Arel::Nodes::Following.new)
+ manager.to_sql.must_be_like %{
+ SELECT FROM "users" WINDOW "a_window" AS (ROWS UNBOUNDED FOLLOWING)
+ }
+ end
+
+ it "takes a rows frame, bounded following" do
+ table = Table.new :users
+ manager = Arel::SelectManager.new
+ manager.from table
+ manager.window("a_window").rows(Arel::Nodes::Following.new(5))
+ manager.to_sql.must_be_like %{
+ SELECT FROM "users" WINDOW "a_window" AS (ROWS 5 FOLLOWING)
+ }
+ end
+
+ it "takes a rows frame, current row" do
+ table = Table.new :users
+ manager = Arel::SelectManager.new
+ manager.from table
+ manager.window("a_window").rows(Arel::Nodes::CurrentRow.new)
+ manager.to_sql.must_be_like %{
+ SELECT FROM "users" WINDOW "a_window" AS (ROWS CURRENT ROW)
+ }
+ end
+
+ it "takes a rows frame, between two delimiters" do
+ table = Table.new :users
+ manager = Arel::SelectManager.new
+ manager.from table
+ window = manager.window("a_window")
+ window.frame(
+ Arel::Nodes::Between.new(
+ window.rows,
+ Nodes::And.new([
+ Arel::Nodes::Preceding.new,
+ Arel::Nodes::CurrentRow.new
+ ])))
+ manager.to_sql.must_be_like %{
+ SELECT FROM "users" WINDOW "a_window" AS (ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
+ }
+ end
+
+ it "takes a range frame, unbounded preceding" do
+ table = Table.new :users
+ manager = Arel::SelectManager.new
+ manager.from table
+ manager.window("a_window").range(Arel::Nodes::Preceding.new)
+ manager.to_sql.must_be_like %{
+ SELECT FROM "users" WINDOW "a_window" AS (RANGE UNBOUNDED PRECEDING)
+ }
+ end
+
+ it "takes a range frame, bounded preceding" do
+ table = Table.new :users
+ manager = Arel::SelectManager.new
+ manager.from table
+ manager.window("a_window").range(Arel::Nodes::Preceding.new(5))
+ manager.to_sql.must_be_like %{
+ SELECT FROM "users" WINDOW "a_window" AS (RANGE 5 PRECEDING)
+ }
+ end
+
+ it "takes a range frame, unbounded following" do
+ table = Table.new :users
+ manager = Arel::SelectManager.new
+ manager.from table
+ manager.window("a_window").range(Arel::Nodes::Following.new)
+ manager.to_sql.must_be_like %{
+ SELECT FROM "users" WINDOW "a_window" AS (RANGE UNBOUNDED FOLLOWING)
+ }
+ end
+
+ it "takes a range frame, bounded following" do
+ table = Table.new :users
+ manager = Arel::SelectManager.new
+ manager.from table
+ manager.window("a_window").range(Arel::Nodes::Following.new(5))
+ manager.to_sql.must_be_like %{
+ SELECT FROM "users" WINDOW "a_window" AS (RANGE 5 FOLLOWING)
+ }
+ end
+
+ it "takes a range frame, current row" do
+ table = Table.new :users
+ manager = Arel::SelectManager.new
+ manager.from table
+ manager.window("a_window").range(Arel::Nodes::CurrentRow.new)
+ manager.to_sql.must_be_like %{
+ SELECT FROM "users" WINDOW "a_window" AS (RANGE CURRENT ROW)
+ }
+ end
+
+ it "takes a range frame, between two delimiters" do
+ table = Table.new :users
+ manager = Arel::SelectManager.new
+ manager.from table
+ window = manager.window("a_window")
+ window.frame(
+ Arel::Nodes::Between.new(
+ window.range,
+ Nodes::And.new([
+ Arel::Nodes::Preceding.new,
+ Arel::Nodes::CurrentRow.new
+ ])))
+ manager.to_sql.must_be_like %{
+ SELECT FROM "users" WINDOW "a_window" AS (RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
+ }
+ end
+ end
+
+ describe "delete" do
+ it "copies from" do
+ table = Table.new :users
+ manager = Arel::SelectManager.new
+ manager.from table
+ stmt = manager.compile_delete
+
+ stmt.to_sql.must_be_like %{ DELETE FROM "users" }
+ end
+
+ it "copies where" do
+ table = Table.new :users
+ manager = Arel::SelectManager.new
+ manager.from table
+ manager.where table[:id].eq 10
+ stmt = manager.compile_delete
+
+ stmt.to_sql.must_be_like %{
+ DELETE FROM "users" WHERE "users"."id" = 10
+ }
+ end
+ end
+
+ describe "where_sql" do
+ it "gives me back the where sql" do
+ table = Table.new :users
+ manager = Arel::SelectManager.new
+ manager.from table
+ manager.where table[:id].eq 10
+ manager.where_sql.must_be_like %{ WHERE "users"."id" = 10 }
+ end
+
+ it "joins wheres with AND" do
+ table = Table.new :users
+ manager = Arel::SelectManager.new
+ manager.from table
+ manager.where table[:id].eq 10
+ manager.where table[:id].eq 11
+ manager.where_sql.must_be_like %{ WHERE "users"."id" = 10 AND "users"."id" = 11}
+ end
+
+ it "handles database specific statements" do
+ old_visitor = Table.engine.connection.visitor
+ Table.engine.connection.visitor = Visitors::PostgreSQL.new Table.engine.connection
+ table = Table.new :users
+ manager = Arel::SelectManager.new
+ manager.from table
+ manager.where table[:id].eq 10
+ manager.where table[:name].matches "foo%"
+ manager.where_sql.must_be_like %{ WHERE "users"."id" = 10 AND "users"."name" ILIKE 'foo%' }
+ Table.engine.connection.visitor = old_visitor
+ end
+
+ it "returns nil when there are no wheres" do
+ table = Table.new :users
+ manager = Arel::SelectManager.new
+ manager.from table
+ manager.where_sql.must_be_nil
+ end
+ end
+
+ describe "update" do
+ it "creates an update statement" do
+ table = Table.new :users
+ manager = Arel::SelectManager.new
+ manager.from table
+ stmt = manager.compile_update({ table[:id] => 1 }, Arel::Attributes::Attribute.new(table, "id"))
+
+ stmt.to_sql.must_be_like %{
+ UPDATE "users" SET "id" = 1
+ }
+ end
+
+ it "takes a string" do
+ table = Table.new :users
+ manager = Arel::SelectManager.new
+ manager.from table
+ stmt = manager.compile_update(Nodes::SqlLiteral.new("foo = bar"), Arel::Attributes::Attribute.new(table, "id"))
+
+ stmt.to_sql.must_be_like %{ UPDATE "users" SET foo = bar }
+ end
+
+ it "copies limits" do
+ table = Table.new :users
+ manager = Arel::SelectManager.new
+ manager.from table
+ manager.take 1
+ stmt = manager.compile_update(Nodes::SqlLiteral.new("foo = bar"), Arel::Attributes::Attribute.new(table, "id"))
+ stmt.key = table["id"]
+
+ stmt.to_sql.must_be_like %{
+ UPDATE "users" SET foo = bar
+ WHERE "users"."id" IN (SELECT "users"."id" FROM "users" LIMIT 1)
+ }
+ end
+
+ it "copies order" do
+ table = Table.new :users
+ manager = Arel::SelectManager.new
+ manager.from table
+ manager.order :foo
+ stmt = manager.compile_update(Nodes::SqlLiteral.new("foo = bar"), Arel::Attributes::Attribute.new(table, "id"))
+ stmt.key = table["id"]
+
+ stmt.to_sql.must_be_like %{
+ UPDATE "users" SET foo = bar
+ WHERE "users"."id" IN (SELECT "users"."id" FROM "users" ORDER BY foo)
+ }
+ end
+
+ it "copies where clauses" do
+ table = Table.new :users
+ manager = Arel::SelectManager.new
+ manager.where table[:id].eq 10
+ manager.from table
+ stmt = manager.compile_update({ table[:id] => 1 }, Arel::Attributes::Attribute.new(table, "id"))
+
+ stmt.to_sql.must_be_like %{
+ UPDATE "users" SET "id" = 1 WHERE "users"."id" = 10
+ }
+ end
+
+ it "copies where clauses when nesting is triggered" do
+ table = Table.new :users
+ manager = Arel::SelectManager.new
+ manager.where table[:foo].eq 10
+ manager.take 42
+ manager.from table
+ stmt = manager.compile_update({ table[:id] => 1 }, Arel::Attributes::Attribute.new(table, "id"))
+
+ stmt.to_sql.must_be_like %{
+ UPDATE "users" SET "id" = 1 WHERE "users"."id" IN (SELECT "users"."id" FROM "users" WHERE "users"."foo" = 10 LIMIT 42)
+ }
+ end
+ end
+
+ describe "project" do
+ it "takes sql literals" do
+ manager = Arel::SelectManager.new
+ manager.project Nodes::SqlLiteral.new "*"
+ manager.to_sql.must_be_like %{ SELECT * }
+ end
+
+ it "takes multiple args" do
+ manager = Arel::SelectManager.new
+ manager.project Nodes::SqlLiteral.new("foo"),
+ Nodes::SqlLiteral.new("bar")
+ manager.to_sql.must_be_like %{ SELECT foo, bar }
+ end
+
+ it "takes strings" do
+ manager = Arel::SelectManager.new
+ manager.project "*"
+ manager.to_sql.must_be_like %{ SELECT * }
+ end
+ end
+
+ describe "projections" do
+ it "reads projections" do
+ manager = Arel::SelectManager.new
+ manager.project Arel.sql("foo"), Arel.sql("bar")
+ manager.projections.must_equal [Arel.sql("foo"), Arel.sql("bar")]
+ end
+ end
+
+ describe "projections=" do
+ it "overwrites projections" do
+ manager = Arel::SelectManager.new
+ manager.project Arel.sql("foo")
+ manager.projections = [Arel.sql("bar")]
+ manager.to_sql.must_be_like %{ SELECT bar }
+ end
+ end
+
+ describe "take" do
+ it "knows take" do
+ table = Table.new :users
+ manager = Arel::SelectManager.new
+ manager.from(table).project(table["id"])
+ manager.where(table["id"].eq(1))
+ manager.take 1
+
+ manager.to_sql.must_be_like %{
+ SELECT "users"."id"
+ FROM "users"
+ WHERE "users"."id" = 1
+ LIMIT 1
+ }
+ end
+
+ it "chains" do
+ manager = Arel::SelectManager.new
+ manager.take(1).must_equal manager
+ end
+
+ it "removes LIMIT when nil is passed" do
+ manager = Arel::SelectManager.new
+ manager.limit = 10
+ assert_match("LIMIT", manager.to_sql)
+
+ manager.limit = nil
+ assert_no_match("LIMIT", manager.to_sql)
+ end
+ end
+
+ describe "where" do
+ it "knows where" do
+ table = Table.new :users
+ manager = Arel::SelectManager.new
+ manager.from(table).project(table["id"])
+ manager.where(table["id"].eq(1))
+ manager.to_sql.must_be_like %{
+ SELECT "users"."id"
+ FROM "users"
+ WHERE "users"."id" = 1
+ }
+ end
+
+ it "chains" do
+ table = Table.new :users
+ manager = Arel::SelectManager.new
+ manager.from(table)
+ manager.project(table["id"]).where(table["id"].eq 1).must_equal manager
+ end
+ end
+
+ describe "from" do
+ it "makes sql" do
+ table = Table.new :users
+ manager = Arel::SelectManager.new
+
+ manager.from table
+ manager.project table["id"]
+ manager.to_sql.must_be_like 'SELECT "users"."id" FROM "users"'
+ end
+
+ it "chains" do
+ table = Table.new :users
+ manager = Arel::SelectManager.new
+ manager.from(table).project(table["id"]).must_equal manager
+ manager.to_sql.must_be_like 'SELECT "users"."id" FROM "users"'
+ end
+ end
+
+ describe "source" do
+ it "returns the join source of the select core" do
+ manager = Arel::SelectManager.new
+ manager.source.must_equal manager.ast.cores.last.source
+ end
+ end
+
+ describe "distinct" do
+ it "sets the quantifier" do
+ manager = Arel::SelectManager.new
+
+ manager.distinct
+ manager.ast.cores.last.set_quantifier.class.must_equal Arel::Nodes::Distinct
+
+ manager.distinct(false)
+ manager.ast.cores.last.set_quantifier.must_be_nil
+ end
+
+ it "chains" do
+ manager = Arel::SelectManager.new
+ manager.distinct.must_equal manager
+ manager.distinct(false).must_equal manager
+ end
+ end
+
+ describe "distinct_on" do
+ it "sets the quantifier" do
+ manager = Arel::SelectManager.new
+ table = Table.new :users
+
+ manager.distinct_on(table["id"])
+ manager.ast.cores.last.set_quantifier.must_equal Arel::Nodes::DistinctOn.new(table["id"])
+
+ manager.distinct_on(false)
+ manager.ast.cores.last.set_quantifier.must_be_nil
+ end
+
+ it "chains" do
+ manager = Arel::SelectManager.new
+ table = Table.new :users
+
+ manager.distinct_on(table["id"]).must_equal manager
+ manager.distinct_on(false).must_equal manager
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/arel/support/fake_record.rb b/activerecord/test/cases/arel/support/fake_record.rb
new file mode 100644
index 0000000000..559ff5d4e6
--- /dev/null
+++ b/activerecord/test/cases/arel/support/fake_record.rb
@@ -0,0 +1,129 @@
+# frozen_string_literal: true
+
+require "date"
+module FakeRecord
+ class Column < Struct.new(:name, :type)
+ end
+
+ class Connection
+ attr_reader :tables
+ attr_accessor :visitor
+
+ def initialize(visitor = nil)
+ @tables = %w{ users photos developers products}
+ @columns = {
+ "users" => [
+ Column.new("id", :integer),
+ Column.new("name", :string),
+ Column.new("bool", :boolean),
+ Column.new("created_at", :date)
+ ],
+ "products" => [
+ Column.new("id", :integer),
+ Column.new("price", :decimal)
+ ]
+ }
+ @columns_hash = {
+ "users" => Hash[@columns["users"].map { |x| [x.name, x] }],
+ "products" => Hash[@columns["products"].map { |x| [x.name, x] }]
+ }
+ @primary_keys = {
+ "users" => "id",
+ "products" => "id"
+ }
+ @visitor = visitor
+ end
+
+ def columns_hash(table_name)
+ @columns_hash[table_name]
+ end
+
+ def primary_key(name)
+ @primary_keys[name.to_s]
+ end
+
+ def data_source_exists?(name)
+ @tables.include? name.to_s
+ end
+
+ def columns(name, message = nil)
+ @columns[name.to_s]
+ end
+
+ def quote_table_name(name)
+ "\"#{name}\""
+ end
+
+ def quote_column_name(name)
+ "\"#{name}\""
+ end
+
+ def schema_cache
+ self
+ end
+
+ def quote(thing)
+ case thing
+ when DateTime
+ "'#{thing.strftime("%Y-%m-%d %H:%M:%S")}'"
+ when Date
+ "'#{thing.strftime("%Y-%m-%d")}'"
+ when true
+ "'t'"
+ when false
+ "'f'"
+ when nil
+ "NULL"
+ when Numeric
+ thing
+ else
+ "'#{thing.to_s.gsub("'", "\\\\'")}'"
+ end
+ end
+ end
+
+ class ConnectionPool
+ class Spec < Struct.new(:config)
+ end
+
+ attr_reader :spec, :connection
+
+ def initialize
+ @spec = Spec.new(adapter: "america")
+ @connection = Connection.new
+ @connection.visitor = Arel::Visitors::ToSql.new(connection)
+ end
+
+ def with_connection
+ yield connection
+ end
+
+ def table_exists?(name)
+ connection.tables.include? name.to_s
+ end
+
+ def columns_hash
+ connection.columns_hash
+ end
+
+ def schema_cache
+ connection
+ end
+
+ def quote(thing)
+ connection.quote thing
+ end
+ end
+
+ class Base
+ attr_accessor :connection_pool
+
+ def initialize
+ @connection_pool = ConnectionPool.new
+ end
+
+ def connection
+ connection_pool.connection
+ end
+ end
+end
diff --git a/activerecord/test/cases/arel/table_test.rb b/activerecord/test/cases/arel/table_test.rb
new file mode 100644
index 0000000000..91b7a5a480
--- /dev/null
+++ b/activerecord/test/cases/arel/table_test.rb
@@ -0,0 +1,216 @@
+# frozen_string_literal: true
+
+require_relative "helper"
+
+module Arel
+ class TableTest < Arel::Spec
+ before do
+ @relation = Table.new(:users)
+ end
+
+ it "should create join nodes" do
+ join = @relation.create_string_join "foo"
+ assert_kind_of Arel::Nodes::StringJoin, join
+ assert_equal "foo", join.left
+ end
+
+ it "should create join nodes" do
+ join = @relation.create_join "foo", "bar"
+ assert_kind_of Arel::Nodes::InnerJoin, join
+ assert_equal "foo", join.left
+ assert_equal "bar", join.right
+ end
+
+ it "should create join nodes with a klass" do
+ join = @relation.create_join "foo", "bar", Arel::Nodes::FullOuterJoin
+ assert_kind_of Arel::Nodes::FullOuterJoin, join
+ assert_equal "foo", join.left
+ assert_equal "bar", join.right
+ end
+
+ it "should create join nodes with a klass" do
+ join = @relation.create_join "foo", "bar", Arel::Nodes::OuterJoin
+ assert_kind_of Arel::Nodes::OuterJoin, join
+ assert_equal "foo", join.left
+ assert_equal "bar", join.right
+ end
+
+ it "should create join nodes with a klass" do
+ join = @relation.create_join "foo", "bar", Arel::Nodes::RightOuterJoin
+ assert_kind_of Arel::Nodes::RightOuterJoin, join
+ assert_equal "foo", join.left
+ assert_equal "bar", join.right
+ end
+
+ it "should return an insert manager" do
+ im = @relation.compile_insert "VALUES(NULL)"
+ assert_kind_of Arel::InsertManager, im
+ im.into Table.new(:users)
+ assert_equal "INSERT INTO \"users\" VALUES(NULL)", im.to_sql
+ end
+
+ describe "skip" do
+ it "should add an offset" do
+ sm = @relation.skip 2
+ sm.to_sql.must_be_like "SELECT FROM \"users\" OFFSET 2"
+ end
+ end
+
+ describe "having" do
+ it "adds a having clause" do
+ mgr = @relation.having @relation[:id].eq(10)
+ mgr.to_sql.must_be_like %{
+ SELECT FROM "users" HAVING "users"."id" = 10
+ }
+ end
+ end
+
+ describe "backwards compat" do
+ describe "join" do
+ it "noops on nil" do
+ mgr = @relation.join nil
+
+ mgr.to_sql.must_be_like %{ SELECT FROM "users" }
+ end
+
+ it "raises EmptyJoinError on empty" do
+ assert_raises(EmptyJoinError) do
+ @relation.join ""
+ end
+ end
+
+ it "takes a second argument for join type" do
+ right = @relation.alias
+ predicate = @relation[:id].eq(right[:id])
+ mgr = @relation.join(right, Nodes::OuterJoin).on(predicate)
+
+ mgr.to_sql.must_be_like %{
+ SELECT FROM "users"
+ LEFT OUTER JOIN "users" "users_2"
+ ON "users"."id" = "users_2"."id"
+ }
+ end
+ end
+
+ describe "join" do
+ it "creates an outer join" do
+ right = @relation.alias
+ predicate = @relation[:id].eq(right[:id])
+ mgr = @relation.outer_join(right).on(predicate)
+
+ mgr.to_sql.must_be_like %{
+ SELECT FROM "users"
+ LEFT OUTER JOIN "users" "users_2"
+ ON "users"."id" = "users_2"."id"
+ }
+ end
+ end
+ end
+
+ describe "group" do
+ it "should create a group" do
+ manager = @relation.group @relation[:id]
+ manager.to_sql.must_be_like %{
+ SELECT FROM "users" GROUP BY "users"."id"
+ }
+ end
+ end
+
+ describe "alias" do
+ it "should create a node that proxies to a table" do
+ node = @relation.alias
+ node.name.must_equal "users_2"
+ node[:id].relation.must_equal node
+ end
+ end
+
+ describe "new" do
+ it "should accept a hash" do
+ rel = Table.new :users, as: "foo"
+ rel.table_alias.must_equal "foo"
+ end
+
+ it "ignores as if it equals name" do
+ rel = Table.new :users, as: "users"
+ rel.table_alias.must_be_nil
+ end
+ end
+
+ describe "order" do
+ it "should take an order" do
+ manager = @relation.order "foo"
+ manager.to_sql.must_be_like %{ SELECT FROM "users" ORDER BY foo }
+ end
+ end
+
+ describe "take" do
+ it "should add a limit" do
+ manager = @relation.take 1
+ manager.project Nodes::SqlLiteral.new "*"
+ manager.to_sql.must_be_like %{ SELECT * FROM "users" LIMIT 1 }
+ end
+ end
+
+ describe "project" do
+ it "can project" do
+ manager = @relation.project Nodes::SqlLiteral.new "*"
+ manager.to_sql.must_be_like %{ SELECT * FROM "users" }
+ end
+
+ it "takes multiple parameters" do
+ manager = @relation.project Nodes::SqlLiteral.new("*"), Nodes::SqlLiteral.new("*")
+ manager.to_sql.must_be_like %{ SELECT *, * FROM "users" }
+ end
+ end
+
+ describe "where" do
+ it "returns a tree manager" do
+ manager = @relation.where @relation[:id].eq 1
+ manager.project @relation[:id]
+ manager.must_be_kind_of TreeManager
+ manager.to_sql.must_be_like %{
+ SELECT "users"."id"
+ FROM "users"
+ WHERE "users"."id" = 1
+ }
+ end
+ end
+
+ it "should have a name" do
+ @relation.name.must_equal "users"
+ end
+
+ it "should have a table name" do
+ @relation.table_name.must_equal "users"
+ end
+
+ describe "[]" do
+ describe "when given a Symbol" do
+ it "manufactures an attribute if the symbol names an attribute within the relation" do
+ column = @relation[:id]
+ column.name.must_equal :id
+ end
+ end
+ end
+
+ describe "equality" do
+ it "is equal with equal ivars" do
+ relation1 = Table.new(:users)
+ relation1.table_alias = "zomg"
+ relation2 = Table.new(:users)
+ relation2.table_alias = "zomg"
+ array = [relation1, relation2]
+ assert_equal 1, array.uniq.size
+ end
+
+ it "is not equal with different ivars" do
+ relation1 = Table.new(:users)
+ relation1.table_alias = "zomg"
+ relation2 = Table.new(:users)
+ relation2.table_alias = "zomg2"
+ array = [relation1, relation2]
+ assert_equal 2, array.uniq.size
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/arel/update_manager_test.rb b/activerecord/test/cases/arel/update_manager_test.rb
new file mode 100644
index 0000000000..cc1b9ac5b3
--- /dev/null
+++ b/activerecord/test/cases/arel/update_manager_test.rb
@@ -0,0 +1,126 @@
+# frozen_string_literal: true
+
+require_relative "helper"
+
+module Arel
+ class UpdateManagerTest < Arel::Spec
+ describe "new" do
+ it "takes an engine" do
+ Arel::UpdateManager.new
+ end
+ end
+
+ it "should not quote sql literals" do
+ table = Table.new(:users)
+ um = Arel::UpdateManager.new
+ um.table table
+ um.set [[table[:name], Arel::Nodes::BindParam.new(1)]]
+ um.to_sql.must_be_like %{ UPDATE "users" SET "name" = ? }
+ end
+
+ it "handles limit properly" do
+ table = Table.new(:users)
+ um = Arel::UpdateManager.new
+ um.key = "id"
+ um.take 10
+ um.table table
+ um.set [[table[:name], nil]]
+ assert_match(/LIMIT 10/, um.to_sql)
+ end
+
+ describe "set" do
+ it "updates with null" do
+ table = Table.new(:users)
+ um = Arel::UpdateManager.new
+ um.table table
+ um.set [[table[:name], nil]]
+ um.to_sql.must_be_like %{ UPDATE "users" SET "name" = NULL }
+ end
+
+ it "takes a string" do
+ table = Table.new(:users)
+ um = Arel::UpdateManager.new
+ um.table table
+ um.set Nodes::SqlLiteral.new "foo = bar"
+ um.to_sql.must_be_like %{ UPDATE "users" SET foo = bar }
+ end
+
+ it "takes a list of lists" do
+ table = Table.new(:users)
+ um = Arel::UpdateManager.new
+ um.table table
+ um.set [[table[:id], 1], [table[:name], "hello"]]
+ um.to_sql.must_be_like %{
+ UPDATE "users" SET "id" = 1, "name" = 'hello'
+ }
+ end
+
+ it "chains" do
+ table = Table.new(:users)
+ um = Arel::UpdateManager.new
+ um.set([[table[:id], 1], [table[:name], "hello"]]).must_equal um
+ end
+ end
+
+ describe "table" do
+ it "generates an update statement" do
+ um = Arel::UpdateManager.new
+ um.table Table.new(:users)
+ um.to_sql.must_be_like %{ UPDATE "users" }
+ end
+
+ it "chains" do
+ um = Arel::UpdateManager.new
+ um.table(Table.new(:users)).must_equal um
+ end
+
+ it "generates an update statement with joins" do
+ um = Arel::UpdateManager.new
+
+ table = Table.new(:users)
+ join_source = Arel::Nodes::JoinSource.new(
+ table,
+ [table.create_join(Table.new(:posts))]
+ )
+
+ um.table join_source
+ um.to_sql.must_be_like %{ UPDATE "users" INNER JOIN "posts" }
+ end
+ end
+
+ describe "where" do
+ it "generates a where clause" do
+ table = Table.new :users
+ um = Arel::UpdateManager.new
+ um.table table
+ um.where table[:id].eq(1)
+ um.to_sql.must_be_like %{
+ UPDATE "users" WHERE "users"."id" = 1
+ }
+ end
+
+ it "chains" do
+ table = Table.new :users
+ um = Arel::UpdateManager.new
+ um.table table
+ um.where(table[:id].eq(1)).must_equal um
+ end
+ end
+
+ describe "key" do
+ before do
+ @table = Table.new :users
+ @um = Arel::UpdateManager.new
+ @um.key = @table[:foo]
+ end
+
+ it "can be set" do
+ @um.ast.key.must_equal @table[:foo]
+ end
+
+ it "can be accessed" do
+ @um.key.must_equal @table[:foo]
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/arel/visitors/depth_first_test.rb b/activerecord/test/cases/arel/visitors/depth_first_test.rb
new file mode 100644
index 0000000000..3baccc7316
--- /dev/null
+++ b/activerecord/test/cases/arel/visitors/depth_first_test.rb
@@ -0,0 +1,271 @@
+# frozen_string_literal: true
+
+require_relative "../helper"
+
+module Arel
+ module Visitors
+ class TestDepthFirst < Arel::Test
+ Collector = Struct.new(:calls) do
+ def call(object)
+ calls << object
+ end
+ end
+
+ def setup
+ @collector = Collector.new []
+ @visitor = Visitors::DepthFirst.new @collector
+ end
+
+ def test_raises_with_object
+ assert_raises(TypeError) do
+ @visitor.accept(Object.new)
+ end
+ end
+
+
+ # unary ops
+ [
+ Arel::Nodes::Not,
+ Arel::Nodes::Group,
+ Arel::Nodes::On,
+ Arel::Nodes::Grouping,
+ Arel::Nodes::Offset,
+ Arel::Nodes::Ordering,
+ Arel::Nodes::StringJoin,
+ Arel::Nodes::UnqualifiedColumn,
+ Arel::Nodes::Top,
+ Arel::Nodes::Limit,
+ Arel::Nodes::Else,
+ ].each do |klass|
+ define_method("test_#{klass.name.gsub('::', '_')}") do
+ op = klass.new(:a)
+ @visitor.accept op
+ assert_equal [:a, op], @collector.calls
+ end
+ end
+
+ # functions
+ [
+ Arel::Nodes::Exists,
+ Arel::Nodes::Avg,
+ Arel::Nodes::Min,
+ Arel::Nodes::Max,
+ Arel::Nodes::Sum,
+ ].each do |klass|
+ define_method("test_#{klass.name.gsub('::', '_')}") do
+ func = klass.new(:a, "b")
+ @visitor.accept func
+ assert_equal [:a, "b", false, func], @collector.calls
+ end
+ end
+
+ def test_named_function
+ func = Arel::Nodes::NamedFunction.new(:a, :b, "c")
+ @visitor.accept func
+ assert_equal [:a, :b, false, "c", func], @collector.calls
+ end
+
+ def test_lock
+ lock = Nodes::Lock.new true
+ @visitor.accept lock
+ assert_equal [lock], @collector.calls
+ end
+
+ def test_count
+ count = Nodes::Count.new :a, :b, "c"
+ @visitor.accept count
+ assert_equal [:a, "c", :b, count], @collector.calls
+ end
+
+ def test_inner_join
+ join = Nodes::InnerJoin.new :a, :b
+ @visitor.accept join
+ assert_equal [:a, :b, join], @collector.calls
+ end
+
+ def test_full_outer_join
+ join = Nodes::FullOuterJoin.new :a, :b
+ @visitor.accept join
+ assert_equal [:a, :b, join], @collector.calls
+ end
+
+ def test_outer_join
+ join = Nodes::OuterJoin.new :a, :b
+ @visitor.accept join
+ assert_equal [:a, :b, join], @collector.calls
+ end
+
+ def test_right_outer_join
+ join = Nodes::RightOuterJoin.new :a, :b
+ @visitor.accept join
+ assert_equal [:a, :b, join], @collector.calls
+ end
+
+ [
+ Arel::Nodes::Assignment,
+ Arel::Nodes::Between,
+ Arel::Nodes::Concat,
+ Arel::Nodes::DoesNotMatch,
+ Arel::Nodes::Equality,
+ Arel::Nodes::GreaterThan,
+ Arel::Nodes::GreaterThanOrEqual,
+ Arel::Nodes::In,
+ Arel::Nodes::LessThan,
+ Arel::Nodes::LessThanOrEqual,
+ Arel::Nodes::Matches,
+ Arel::Nodes::NotEqual,
+ Arel::Nodes::NotIn,
+ Arel::Nodes::Or,
+ Arel::Nodes::TableAlias,
+ Arel::Nodes::Values,
+ Arel::Nodes::As,
+ Arel::Nodes::DeleteStatement,
+ Arel::Nodes::JoinSource,
+ Arel::Nodes::When,
+ ].each do |klass|
+ define_method("test_#{klass.name.gsub('::', '_')}") do
+ binary = klass.new(:a, :b)
+ @visitor.accept binary
+ assert_equal [:a, :b, binary], @collector.calls
+ end
+ end
+
+ def test_Arel_Nodes_InfixOperation
+ binary = Arel::Nodes::InfixOperation.new(:o, :a, :b)
+ @visitor.accept binary
+ assert_equal [:a, :b, binary], @collector.calls
+ end
+
+ # N-ary
+ [
+ Arel::Nodes::And,
+ ].each do |klass|
+ define_method("test_#{klass.name.gsub('::', '_')}") do
+ binary = klass.new([:a, :b, :c])
+ @visitor.accept binary
+ assert_equal [:a, :b, :c, binary], @collector.calls
+ end
+ end
+
+ [
+ Arel::Attributes::Integer,
+ Arel::Attributes::Float,
+ Arel::Attributes::String,
+ Arel::Attributes::Time,
+ Arel::Attributes::Boolean,
+ Arel::Attributes::Attribute
+ ].each do |klass|
+ define_method("test_#{klass.name.gsub('::', '_')}") do
+ binary = klass.new(:a, :b)
+ @visitor.accept binary
+ assert_equal [:a, :b, binary], @collector.calls
+ end
+ end
+
+ def test_table
+ relation = Arel::Table.new(:users)
+ @visitor.accept relation
+ assert_equal ["users", relation], @collector.calls
+ end
+
+ def test_array
+ node = Nodes::Or.new(:a, :b)
+ list = [node]
+ @visitor.accept list
+ assert_equal [:a, :b, node, list], @collector.calls
+ end
+
+ def test_set
+ node = Nodes::Or.new(:a, :b)
+ set = Set.new([node])
+ @visitor.accept set
+ assert_equal [:a, :b, node, set], @collector.calls
+ end
+
+ def test_hash
+ node = Nodes::Or.new(:a, :b)
+ hash = { node => node }
+ @visitor.accept hash
+ assert_equal [:a, :b, node, :a, :b, node, hash], @collector.calls
+ end
+
+ def test_update_statement
+ stmt = Nodes::UpdateStatement.new
+ stmt.relation = :a
+ stmt.values << :b
+ stmt.wheres << :c
+ stmt.orders << :d
+ stmt.limit = :e
+
+ @visitor.accept stmt
+ assert_equal [:a, :b, stmt.values, :c, stmt.wheres, :d, stmt.orders,
+ :e, stmt], @collector.calls
+ end
+
+ def test_select_core
+ core = Nodes::SelectCore.new
+ core.projections << :a
+ core.froms = :b
+ core.wheres << :c
+ core.groups << :d
+ core.windows << :e
+ core.havings << :f
+
+ @visitor.accept core
+ assert_equal [
+ :a, core.projections,
+ :b, [],
+ core.source,
+ :c, core.wheres,
+ :d, core.groups,
+ :e, core.windows,
+ :f, core.havings,
+ core], @collector.calls
+ end
+
+ def test_select_statement
+ ss = Nodes::SelectStatement.new
+ ss.cores.replace [:a]
+ ss.orders << :b
+ ss.limit = :c
+ ss.lock = :d
+ ss.offset = :e
+
+ @visitor.accept ss
+ assert_equal [
+ :a, ss.cores,
+ :b, ss.orders,
+ :c,
+ :d,
+ :e,
+ ss], @collector.calls
+ end
+
+ def test_insert_statement
+ stmt = Nodes::InsertStatement.new
+ stmt.relation = :a
+ stmt.columns << :b
+ stmt.values = :c
+
+ @visitor.accept stmt
+ assert_equal [:a, :b, stmt.columns, :c, stmt], @collector.calls
+ end
+
+ def test_case
+ node = Arel::Nodes::Case.new
+ node.case = :a
+ node.conditions << :b
+ node.default = :c
+
+ @visitor.accept node
+ assert_equal [:a, :b, node.conditions, :c, node], @collector.calls
+ end
+
+ def test_node
+ node = Nodes::Node.new
+ @visitor.accept node
+ assert_equal [node], @collector.calls
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/arel/visitors/dispatch_contamination_test.rb b/activerecord/test/cases/arel/visitors/dispatch_contamination_test.rb
new file mode 100644
index 0000000000..a07a1a050a
--- /dev/null
+++ b/activerecord/test/cases/arel/visitors/dispatch_contamination_test.rb
@@ -0,0 +1,72 @@
+# frozen_string_literal: true
+
+require_relative "../helper"
+require "concurrent"
+
+module Arel
+ module Visitors
+ class DummyVisitor < Visitor
+ def initialize
+ super
+ @barrier = Concurrent::CyclicBarrier.new(2)
+ end
+
+ def visit_Arel_Visitors_DummySuperNode(node)
+ 42
+ end
+
+ # This is terrible, but it's the only way to reliably reproduce
+ # the possible race where two threads attempt to correct the
+ # dispatch hash at the same time.
+ def send(*args)
+ super
+ rescue
+ # Both threads try (and fail) to dispatch to the subclass's name
+ @barrier.wait
+ raise
+ ensure
+ # Then one thread successfully completes (updating the dispatch
+ # table in the process) before the other finishes raising its
+ # exception.
+ Thread.current[:delay].wait if Thread.current[:delay]
+ end
+ end
+
+ class DummySuperNode
+ end
+
+ class DummySubNode < DummySuperNode
+ end
+
+ class DispatchContaminationTest < Arel::Spec
+ before do
+ @connection = Table.engine.connection
+ @table = Table.new(:users)
+ end
+
+ it "dispatches properly after failing upwards" do
+ node = Nodes::Union.new(Nodes::True.new, Nodes::False.new)
+ assert_equal "( TRUE UNION FALSE )", node.to_sql
+
+ node.first # from Nodes::Node's Enumerable mixin
+
+ assert_equal "( TRUE UNION FALSE )", node.to_sql
+ end
+
+ it "is threadsafe when implementing superclass fallback" do
+ visitor = DummyVisitor.new
+ main_thread_finished = Concurrent::Event.new
+
+ racing_thread = Thread.new do
+ Thread.current[:delay] = main_thread_finished
+ visitor.accept DummySubNode.new
+ end
+
+ assert_equal 42, visitor.accept(DummySubNode.new)
+ main_thread_finished.set
+
+ assert_equal 42, racing_thread.value
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/arel/visitors/dot_test.rb b/activerecord/test/cases/arel/visitors/dot_test.rb
new file mode 100644
index 0000000000..98f3bab620
--- /dev/null
+++ b/activerecord/test/cases/arel/visitors/dot_test.rb
@@ -0,0 +1,84 @@
+# frozen_string_literal: true
+
+require_relative "../helper"
+
+module Arel
+ module Visitors
+ class TestDot < Arel::Test
+ def setup
+ @visitor = Visitors::Dot.new
+ end
+
+ # functions
+ [
+ Nodes::Sum,
+ Nodes::Exists,
+ Nodes::Max,
+ Nodes::Min,
+ Nodes::Avg,
+ ].each do |klass|
+ define_method("test_#{klass.name.gsub('::', '_')}") do
+ op = klass.new(:a, "z")
+ @visitor.accept op, Collectors::PlainString.new
+ end
+ end
+
+ def test_named_function
+ func = Nodes::NamedFunction.new "omg", "omg"
+ @visitor.accept func, Collectors::PlainString.new
+ end
+
+ # unary ops
+ [
+ Arel::Nodes::Not,
+ Arel::Nodes::Group,
+ Arel::Nodes::On,
+ Arel::Nodes::Grouping,
+ Arel::Nodes::Offset,
+ Arel::Nodes::Ordering,
+ Arel::Nodes::UnqualifiedColumn,
+ Arel::Nodes::Top,
+ Arel::Nodes::Limit,
+ ].each do |klass|
+ define_method("test_#{klass.name.gsub('::', '_')}") do
+ op = klass.new(:a)
+ @visitor.accept op, Collectors::PlainString.new
+ end
+ end
+
+ # binary ops
+ [
+ Arel::Nodes::Assignment,
+ Arel::Nodes::Between,
+ Arel::Nodes::DoesNotMatch,
+ Arel::Nodes::Equality,
+ Arel::Nodes::GreaterThan,
+ Arel::Nodes::GreaterThanOrEqual,
+ Arel::Nodes::In,
+ Arel::Nodes::LessThan,
+ Arel::Nodes::LessThanOrEqual,
+ Arel::Nodes::Matches,
+ Arel::Nodes::NotEqual,
+ Arel::Nodes::NotIn,
+ Arel::Nodes::Or,
+ Arel::Nodes::TableAlias,
+ Arel::Nodes::Values,
+ Arel::Nodes::As,
+ Arel::Nodes::DeleteStatement,
+ Arel::Nodes::JoinSource,
+ Arel::Nodes::Casted,
+ ].each do |klass|
+ define_method("test_#{klass.name.gsub('::', '_')}") do
+ binary = klass.new(:a, :b)
+ @visitor.accept binary, Collectors::PlainString.new
+ end
+ end
+
+ def test_Arel_Nodes_BindParam
+ node = Arel::Nodes::BindParam.new(1)
+ collector = Collectors::PlainString.new
+ assert_match '[label="<f0>Arel::Nodes::BindParam"]', @visitor.accept(node, collector).value
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/arel/visitors/ibm_db_test.rb b/activerecord/test/cases/arel/visitors/ibm_db_test.rb
new file mode 100644
index 0000000000..7163cb34d3
--- /dev/null
+++ b/activerecord/test/cases/arel/visitors/ibm_db_test.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+require_relative "../helper"
+
+module Arel
+ module Visitors
+ class IbmDbTest < Arel::Spec
+ before do
+ @visitor = IBM_DB.new Table.engine.connection
+ end
+
+ def compile(node)
+ @visitor.accept(node, Collectors::SQLString.new).value
+ end
+
+ it "uses FETCH FIRST n ROWS to limit results" do
+ stmt = Nodes::SelectStatement.new
+ stmt.limit = Nodes::Limit.new(1)
+ sql = compile(stmt)
+ sql.must_be_like "SELECT FETCH FIRST 1 ROWS ONLY"
+ end
+
+ it "uses FETCH FIRST n ROWS in updates with a limit" do
+ table = Table.new(:users)
+ stmt = Nodes::UpdateStatement.new
+ stmt.relation = table
+ stmt.limit = Nodes::Limit.new(Nodes.build_quoted(1))
+ stmt.key = table[:id]
+ sql = compile(stmt)
+ sql.must_be_like "UPDATE \"users\" WHERE \"users\".\"id\" IN (SELECT \"users\".\"id\" FROM \"users\" FETCH FIRST 1 ROWS ONLY)"
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/arel/visitors/informix_test.rb b/activerecord/test/cases/arel/visitors/informix_test.rb
new file mode 100644
index 0000000000..b0b031cca3
--- /dev/null
+++ b/activerecord/test/cases/arel/visitors/informix_test.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+require_relative "../helper"
+
+module Arel
+ module Visitors
+ class InformixTest < Arel::Spec
+ before do
+ @visitor = Informix.new Table.engine.connection
+ end
+
+ def compile(node)
+ @visitor.accept(node, Collectors::SQLString.new).value
+ end
+
+ it "uses FIRST n to limit results" do
+ stmt = Nodes::SelectStatement.new
+ stmt.limit = Nodes::Limit.new(1)
+ sql = compile(stmt)
+ sql.must_be_like "SELECT FIRST 1"
+ end
+
+ it "uses FIRST n in updates with a limit" do
+ table = Table.new(:users)
+ stmt = Nodes::UpdateStatement.new
+ stmt.relation = table
+ stmt.limit = Nodes::Limit.new(Nodes.build_quoted(1))
+ stmt.key = table[:id]
+ sql = compile(stmt)
+ sql.must_be_like "UPDATE \"users\" WHERE \"users\".\"id\" IN (SELECT FIRST 1 \"users\".\"id\" FROM \"users\")"
+ end
+
+ it "uses SKIP n to jump results" do
+ stmt = Nodes::SelectStatement.new
+ stmt.offset = Nodes::Offset.new(10)
+ sql = compile(stmt)
+ sql.must_be_like "SELECT SKIP 10"
+ end
+
+ it "uses SKIP before FIRST" do
+ stmt = Nodes::SelectStatement.new
+ stmt.limit = Nodes::Limit.new(1)
+ stmt.offset = Nodes::Offset.new(1)
+ sql = compile(stmt)
+ sql.must_be_like "SELECT SKIP 1 FIRST 1"
+ end
+
+ it "uses INNER JOIN to perform joins" do
+ core = Nodes::SelectCore.new
+ table = Table.new(:posts)
+ core.source = Nodes::JoinSource.new(table, [table.create_join(Table.new(:comments))])
+
+ stmt = Nodes::SelectStatement.new([core])
+ sql = compile(stmt)
+ sql.must_be_like 'SELECT FROM "posts" INNER JOIN "comments"'
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/arel/visitors/mssql_test.rb b/activerecord/test/cases/arel/visitors/mssql_test.rb
new file mode 100644
index 0000000000..340376c3d6
--- /dev/null
+++ b/activerecord/test/cases/arel/visitors/mssql_test.rb
@@ -0,0 +1,99 @@
+# frozen_string_literal: true
+
+require_relative "../helper"
+
+module Arel
+ module Visitors
+ class MssqlTest < Arel::Spec
+ before do
+ @visitor = MSSQL.new Table.engine.connection
+ @table = Arel::Table.new "users"
+ end
+
+ def compile(node)
+ @visitor.accept(node, Collectors::SQLString.new).value
+ end
+
+ it "should not modify query if no offset or limit" do
+ stmt = Nodes::SelectStatement.new
+ sql = compile(stmt)
+ sql.must_be_like "SELECT"
+ end
+
+ it "should go over table PK if no .order() or .group()" do
+ stmt = Nodes::SelectStatement.new
+ stmt.cores.first.from = @table
+ stmt.limit = Nodes::Limit.new(10)
+ sql = compile(stmt)
+ sql.must_be_like "SELECT _t.* FROM (SELECT ROW_NUMBER() OVER (ORDER BY \"users\".\"id\") as _row_num FROM \"users\") as _t WHERE _row_num BETWEEN 1 AND 10"
+ end
+
+ it "caches the PK lookup for order" do
+ connection = Minitest::Mock.new
+ connection.expect(:primary_key, ["id"], ["users"])
+
+ # We don't care how many times these methods are called
+ def connection.quote_table_name(*); ""; end
+ def connection.quote_column_name(*); ""; end
+
+ @visitor = MSSQL.new(connection)
+ stmt = Nodes::SelectStatement.new
+ stmt.cores.first.from = @table
+ stmt.limit = Nodes::Limit.new(10)
+
+ compile(stmt)
+ compile(stmt)
+
+ connection.verify
+ end
+
+ it "should use TOP for limited deletes" do
+ stmt = Nodes::DeleteStatement.new
+ stmt.relation = @table
+ stmt.limit = Nodes::Limit.new(10)
+ sql = compile(stmt)
+
+ sql.must_be_like "DELETE TOP (10) FROM \"users\""
+ end
+
+ it "should go over query ORDER BY if .order()" do
+ stmt = Nodes::SelectStatement.new
+ stmt.limit = Nodes::Limit.new(10)
+ stmt.orders << Nodes::SqlLiteral.new("order_by")
+ sql = compile(stmt)
+ sql.must_be_like "SELECT _t.* FROM (SELECT ROW_NUMBER() OVER (ORDER BY order_by) as _row_num) as _t WHERE _row_num BETWEEN 1 AND 10"
+ end
+
+ it "should go over query GROUP BY if no .order() and there is .group()" do
+ stmt = Nodes::SelectStatement.new
+ stmt.cores.first.groups << Nodes::SqlLiteral.new("group_by")
+ stmt.limit = Nodes::Limit.new(10)
+ sql = compile(stmt)
+ sql.must_be_like "SELECT _t.* FROM (SELECT ROW_NUMBER() OVER (ORDER BY group_by) as _row_num GROUP BY group_by) as _t WHERE _row_num BETWEEN 1 AND 10"
+ end
+
+ it "should use BETWEEN if both .limit() and .offset" do
+ stmt = Nodes::SelectStatement.new
+ stmt.limit = Nodes::Limit.new(10)
+ stmt.offset = Nodes::Offset.new(20)
+ sql = compile(stmt)
+ sql.must_be_like "SELECT _t.* FROM (SELECT ROW_NUMBER() OVER (ORDER BY ) as _row_num) as _t WHERE _row_num BETWEEN 21 AND 30"
+ end
+
+ it "should use >= if only .offset" do
+ stmt = Nodes::SelectStatement.new
+ stmt.offset = Nodes::Offset.new(20)
+ sql = compile(stmt)
+ sql.must_be_like "SELECT _t.* FROM (SELECT ROW_NUMBER() OVER (ORDER BY ) as _row_num) as _t WHERE _row_num >= 21"
+ end
+
+ it "should generate subquery for .count" do
+ stmt = Nodes::SelectStatement.new
+ stmt.limit = Nodes::Limit.new(10)
+ stmt.cores.first.projections << Nodes::Count.new("*")
+ sql = compile(stmt)
+ sql.must_be_like "SELECT COUNT(1) as count_id FROM (SELECT _t.* FROM (SELECT ROW_NUMBER() OVER (ORDER BY ) as _row_num) as _t WHERE _row_num BETWEEN 1 AND 10) AS subquery"
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/arel/visitors/mysql_test.rb b/activerecord/test/cases/arel/visitors/mysql_test.rb
new file mode 100644
index 0000000000..9d3bad8516
--- /dev/null
+++ b/activerecord/test/cases/arel/visitors/mysql_test.rb
@@ -0,0 +1,80 @@
+# frozen_string_literal: true
+
+require_relative "../helper"
+
+module Arel
+ module Visitors
+ class MysqlTest < Arel::Spec
+ before do
+ @visitor = MySQL.new Table.engine.connection
+ end
+
+ def compile(node)
+ @visitor.accept(node, Collectors::SQLString.new).value
+ end
+
+ it "squashes parenthesis on multiple unions" do
+ subnode = Nodes::Union.new Arel.sql("left"), Arel.sql("right")
+ node = Nodes::Union.new subnode, Arel.sql("topright")
+ assert_equal 1, compile(node).scan("(").length
+
+ subnode = Nodes::Union.new Arel.sql("left"), Arel.sql("right")
+ node = Nodes::Union.new Arel.sql("topleft"), subnode
+ assert_equal 1, compile(node).scan("(").length
+ end
+
+ ###
+ # :'(
+ # http://dev.mysql.com/doc/refman/5.0/en/select.html#id3482214
+ it "defaults limit to 18446744073709551615" do
+ stmt = Nodes::SelectStatement.new
+ stmt.offset = Nodes::Offset.new(1)
+ sql = compile(stmt)
+ sql.must_be_like "SELECT FROM DUAL LIMIT 18446744073709551615 OFFSET 1"
+ end
+
+ it "should escape LIMIT" do
+ sc = Arel::Nodes::UpdateStatement.new
+ sc.relation = Table.new(:users)
+ sc.limit = Nodes::Limit.new(Nodes.build_quoted("omg"))
+ assert_equal("UPDATE \"users\" LIMIT 'omg'", compile(sc))
+ end
+
+ it "uses DUAL for empty from" do
+ stmt = Nodes::SelectStatement.new
+ sql = compile(stmt)
+ sql.must_be_like "SELECT FROM DUAL"
+ end
+
+ describe "locking" do
+ it "defaults to FOR UPDATE when locking" do
+ node = Nodes::Lock.new(Arel.sql("FOR UPDATE"))
+ compile(node).must_be_like "FOR UPDATE"
+ end
+
+ it "allows a custom string to be used as a lock" do
+ node = Nodes::Lock.new(Arel.sql("LOCK IN SHARE MODE"))
+ compile(node).must_be_like "LOCK IN SHARE MODE"
+ end
+ end
+
+ describe "concat" do
+ it "concats columns" do
+ @table = Table.new(:users)
+ query = @table[:name].concat(@table[:name])
+ compile(query).must_be_like %{
+ CONCAT("users"."name", "users"."name")
+ }
+ end
+
+ it "concats a string" do
+ @table = Table.new(:users)
+ query = @table[:name].concat(Nodes.build_quoted("abc"))
+ compile(query).must_be_like %{
+ CONCAT("users"."name", 'abc')
+ }
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/arel/visitors/oracle12_test.rb b/activerecord/test/cases/arel/visitors/oracle12_test.rb
new file mode 100644
index 0000000000..83a2ee36ca
--- /dev/null
+++ b/activerecord/test/cases/arel/visitors/oracle12_test.rb
@@ -0,0 +1,61 @@
+# frozen_string_literal: true
+
+require_relative "../helper"
+
+module Arel
+ module Visitors
+ class Oracle12Test < Arel::Spec
+ before do
+ @visitor = Oracle12.new Table.engine.connection
+ @table = Table.new(:users)
+ end
+
+ def compile(node)
+ @visitor.accept(node, Collectors::SQLString.new).value
+ end
+
+ it "modified except to be minus" do
+ left = Nodes::SqlLiteral.new("SELECT * FROM users WHERE age > 10")
+ right = Nodes::SqlLiteral.new("SELECT * FROM users WHERE age > 20")
+ sql = compile Nodes::Except.new(left, right)
+ sql.must_be_like %{
+ ( SELECT * FROM users WHERE age > 10 MINUS SELECT * FROM users WHERE age > 20 )
+ }
+ end
+
+ it "generates select options offset then limit" do
+ stmt = Nodes::SelectStatement.new
+ stmt.offset = Nodes::Offset.new(1)
+ stmt.limit = Nodes::Limit.new(10)
+ sql = compile(stmt)
+ sql.must_be_like "SELECT OFFSET 1 ROWS FETCH FIRST 10 ROWS ONLY"
+ end
+
+ describe "locking" do
+ it "generates ArgumentError if limit and lock are used" do
+ stmt = Nodes::SelectStatement.new
+ stmt.limit = Nodes::Limit.new(10)
+ stmt.lock = Nodes::Lock.new(Arel.sql("FOR UPDATE"))
+ assert_raises ArgumentError do
+ compile(stmt)
+ end
+ end
+
+ it "defaults to FOR UPDATE when locking" do
+ node = Nodes::Lock.new(Arel.sql("FOR UPDATE"))
+ compile(node).must_be_like "FOR UPDATE"
+ end
+ end
+
+ describe "Nodes::BindParam" do
+ it "increments each bind param" do
+ query = @table[:name].eq(Arel::Nodes::BindParam.new(1))
+ .and(@table[:id].eq(Arel::Nodes::BindParam.new(1)))
+ compile(query).must_be_like %{
+ "users"."name" = :a1 AND "users"."id" = :a2
+ }
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/arel/visitors/oracle_test.rb b/activerecord/test/cases/arel/visitors/oracle_test.rb
new file mode 100644
index 0000000000..e1dfe40cf9
--- /dev/null
+++ b/activerecord/test/cases/arel/visitors/oracle_test.rb
@@ -0,0 +1,197 @@
+# frozen_string_literal: true
+
+require_relative "../helper"
+
+module Arel
+ module Visitors
+ class OracleTest < Arel::Spec
+ before do
+ @visitor = Oracle.new Table.engine.connection
+ @table = Table.new(:users)
+ end
+
+ def compile(node)
+ @visitor.accept(node, Collectors::SQLString.new).value
+ end
+
+ it "modifies order when there is distinct and first value" do
+ # *sigh*
+ select = "DISTINCT foo.id, FIRST_VALUE(projects.name) OVER (foo) AS alias_0__"
+ stmt = Nodes::SelectStatement.new
+ stmt.cores.first.projections << Nodes::SqlLiteral.new(select)
+ stmt.orders << Nodes::SqlLiteral.new("foo")
+ sql = compile(stmt)
+ sql.must_be_like %{
+ SELECT #{select} ORDER BY alias_0__
+ }
+ end
+
+ it "is idempotent with crazy query" do
+ # *sigh*
+ select = "DISTINCT foo.id, FIRST_VALUE(projects.name) OVER (foo) AS alias_0__"
+ stmt = Nodes::SelectStatement.new
+ stmt.cores.first.projections << Nodes::SqlLiteral.new(select)
+ stmt.orders << Nodes::SqlLiteral.new("foo")
+
+ sql = compile(stmt)
+ sql2 = compile(stmt)
+ sql.must_equal sql2
+ end
+
+ it "splits orders with commas" do
+ # *sigh*
+ select = "DISTINCT foo.id, FIRST_VALUE(projects.name) OVER (foo) AS alias_0__"
+ stmt = Nodes::SelectStatement.new
+ stmt.cores.first.projections << Nodes::SqlLiteral.new(select)
+ stmt.orders << Nodes::SqlLiteral.new("foo, bar")
+ sql = compile(stmt)
+ sql.must_be_like %{
+ SELECT #{select} ORDER BY alias_0__, alias_1__
+ }
+ end
+
+ it "splits orders with commas and function calls" do
+ # *sigh*
+ select = "DISTINCT foo.id, FIRST_VALUE(projects.name) OVER (foo) AS alias_0__"
+ stmt = Nodes::SelectStatement.new
+ stmt.cores.first.projections << Nodes::SqlLiteral.new(select)
+ stmt.orders << Nodes::SqlLiteral.new("NVL(LOWER(bar, foo), foo) DESC, UPPER(baz)")
+ sql = compile(stmt)
+ sql.must_be_like %{
+ SELECT #{select} ORDER BY alias_0__ DESC, alias_1__
+ }
+ end
+
+ describe "Nodes::SelectStatement" do
+ describe "limit" do
+ it "adds a rownum clause" do
+ stmt = Nodes::SelectStatement.new
+ stmt.limit = Nodes::Limit.new(10)
+ sql = compile stmt
+ sql.must_be_like %{ SELECT WHERE ROWNUM <= 10 }
+ end
+
+ it "is idempotent" do
+ stmt = Nodes::SelectStatement.new
+ stmt.orders << Nodes::SqlLiteral.new("foo")
+ stmt.limit = Nodes::Limit.new(10)
+ sql = compile stmt
+ sql2 = compile stmt
+ sql.must_equal sql2
+ end
+
+ it "creates a subquery when there is order_by" do
+ stmt = Nodes::SelectStatement.new
+ stmt.orders << Nodes::SqlLiteral.new("foo")
+ stmt.limit = Nodes::Limit.new(10)
+ sql = compile stmt
+ sql.must_be_like %{
+ SELECT * FROM (SELECT ORDER BY foo ) WHERE ROWNUM <= 10
+ }
+ end
+
+ it "creates a subquery when there is group by" do
+ stmt = Nodes::SelectStatement.new
+ stmt.cores.first.groups << Nodes::SqlLiteral.new("foo")
+ stmt.limit = Nodes::Limit.new(10)
+ sql = compile stmt
+ sql.must_be_like %{
+ SELECT * FROM (SELECT GROUP BY foo ) WHERE ROWNUM <= 10
+ }
+ end
+
+ it "creates a subquery when there is DISTINCT" do
+ stmt = Nodes::SelectStatement.new
+ stmt.cores.first.set_quantifier = Arel::Nodes::Distinct.new
+ stmt.cores.first.projections << Nodes::SqlLiteral.new("id")
+ stmt.limit = Arel::Nodes::Limit.new(10)
+ sql = compile stmt
+ sql.must_be_like %{
+ SELECT * FROM (SELECT DISTINCT id ) WHERE ROWNUM <= 10
+ }
+ end
+
+ it "creates a different subquery when there is an offset" do
+ stmt = Nodes::SelectStatement.new
+ stmt.limit = Nodes::Limit.new(10)
+ stmt.offset = Nodes::Offset.new(10)
+ sql = compile stmt
+ sql.must_be_like %{
+ SELECT * FROM (
+ SELECT raw_sql_.*, rownum raw_rnum_
+ FROM (SELECT ) raw_sql_
+ WHERE rownum <= 20
+ )
+ WHERE raw_rnum_ > 10
+ }
+ end
+
+ it "creates a subquery when there is limit and offset with BindParams" do
+ stmt = Nodes::SelectStatement.new
+ stmt.limit = Nodes::Limit.new(Nodes::BindParam.new(1))
+ stmt.offset = Nodes::Offset.new(Nodes::BindParam.new(1))
+ sql = compile stmt
+ sql.must_be_like %{
+ SELECT * FROM (
+ SELECT raw_sql_.*, rownum raw_rnum_
+ FROM (SELECT ) raw_sql_
+ WHERE rownum <= (:a1 + :a2)
+ )
+ WHERE raw_rnum_ > :a3
+ }
+ end
+
+ it "is idempotent with different subquery" do
+ stmt = Nodes::SelectStatement.new
+ stmt.limit = Nodes::Limit.new(10)
+ stmt.offset = Nodes::Offset.new(10)
+ sql = compile stmt
+ sql2 = compile stmt
+ sql.must_equal sql2
+ end
+ end
+
+ describe "only offset" do
+ it "creates a select from subquery with rownum condition" do
+ stmt = Nodes::SelectStatement.new
+ stmt.offset = Nodes::Offset.new(10)
+ sql = compile stmt
+ sql.must_be_like %{
+ SELECT * FROM (
+ SELECT raw_sql_.*, rownum raw_rnum_
+ FROM (SELECT) raw_sql_
+ )
+ WHERE raw_rnum_ > 10
+ }
+ end
+ end
+ end
+
+ it "modified except to be minus" do
+ left = Nodes::SqlLiteral.new("SELECT * FROM users WHERE age > 10")
+ right = Nodes::SqlLiteral.new("SELECT * FROM users WHERE age > 20")
+ sql = compile Nodes::Except.new(left, right)
+ sql.must_be_like %{
+ ( SELECT * FROM users WHERE age > 10 MINUS SELECT * FROM users WHERE age > 20 )
+ }
+ end
+
+ describe "locking" do
+ it "defaults to FOR UPDATE when locking" do
+ node = Nodes::Lock.new(Arel.sql("FOR UPDATE"))
+ compile(node).must_be_like "FOR UPDATE"
+ end
+ end
+
+ describe "Nodes::BindParam" do
+ it "increments each bind param" do
+ query = @table[:name].eq(Arel::Nodes::BindParam.new(1))
+ .and(@table[:id].eq(Arel::Nodes::BindParam.new(1)))
+ compile(query).must_be_like %{
+ "users"."name" = :a1 AND "users"."id" = :a2
+ }
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/arel/visitors/postgres_test.rb b/activerecord/test/cases/arel/visitors/postgres_test.rb
new file mode 100644
index 0000000000..f7f2c76b6f
--- /dev/null
+++ b/activerecord/test/cases/arel/visitors/postgres_test.rb
@@ -0,0 +1,281 @@
+# frozen_string_literal: true
+
+require_relative "../helper"
+
+module Arel
+ module Visitors
+ class PostgresTest < Arel::Spec
+ before do
+ @visitor = PostgreSQL.new Table.engine.connection
+ @table = Table.new(:users)
+ @attr = @table[:id]
+ end
+
+ def compile(node)
+ @visitor.accept(node, Collectors::SQLString.new).value
+ end
+
+ describe "locking" do
+ it "defaults to FOR UPDATE" do
+ compile(Nodes::Lock.new(Arel.sql("FOR UPDATE"))).must_be_like %{
+ FOR UPDATE
+ }
+ end
+
+ it "allows a custom string to be used as a lock" do
+ node = Nodes::Lock.new(Arel.sql("FOR SHARE"))
+ compile(node).must_be_like %{
+ FOR SHARE
+ }
+ end
+ end
+
+ it "should escape LIMIT" do
+ sc = Arel::Nodes::SelectStatement.new
+ sc.limit = Nodes::Limit.new(Nodes.build_quoted("omg"))
+ sc.cores.first.projections << Arel.sql("DISTINCT ON")
+ sc.orders << Arel.sql("xyz")
+ sql = compile(sc)
+ assert_match(/LIMIT 'omg'/, sql)
+ assert_equal 1, sql.scan(/LIMIT/).length, "should have one limit"
+ end
+
+ it "should support DISTINCT ON" do
+ core = Arel::Nodes::SelectCore.new
+ core.set_quantifier = Arel::Nodes::DistinctOn.new(Arel.sql("aaron"))
+ assert_match "DISTINCT ON ( aaron )", compile(core)
+ end
+
+ it "should support DISTINCT" do
+ core = Arel::Nodes::SelectCore.new
+ core.set_quantifier = Arel::Nodes::Distinct.new
+ assert_equal "SELECT DISTINCT", compile(core)
+ end
+
+ it "encloses LATERAL queries in parens" do
+ subquery = @table.project(:id).where(@table[:name].matches("foo%"))
+ compile(subquery.lateral).must_be_like %{
+ LATERAL (SELECT id FROM "users" WHERE "users"."name" ILIKE 'foo%')
+ }
+ end
+
+ it "produces LATERAL queries with alias" do
+ subquery = @table.project(:id).where(@table[:name].matches("foo%"))
+ compile(subquery.lateral("bar")).must_be_like %{
+ LATERAL (SELECT id FROM "users" WHERE "users"."name" ILIKE 'foo%') bar
+ }
+ end
+
+ describe "Nodes::Matches" do
+ it "should know how to visit" do
+ node = @table[:name].matches("foo%")
+ node.must_be_kind_of Nodes::Matches
+ node.case_sensitive.must_equal(false)
+ compile(node).must_be_like %{
+ "users"."name" ILIKE 'foo%'
+ }
+ end
+
+ it "should know how to visit case sensitive" do
+ node = @table[:name].matches("foo%", nil, true)
+ node.case_sensitive.must_equal(true)
+ compile(node).must_be_like %{
+ "users"."name" LIKE 'foo%'
+ }
+ end
+
+ it "can handle ESCAPE" do
+ node = @table[:name].matches("foo!%", "!")
+ compile(node).must_be_like %{
+ "users"."name" ILIKE 'foo!%' ESCAPE '!'
+ }
+ end
+
+ it "can handle subqueries" do
+ subquery = @table.project(:id).where(@table[:name].matches("foo%"))
+ node = @attr.in subquery
+ compile(node).must_be_like %{
+ "users"."id" IN (SELECT id FROM "users" WHERE "users"."name" ILIKE 'foo%')
+ }
+ end
+ end
+
+ describe "Nodes::DoesNotMatch" do
+ it "should know how to visit" do
+ node = @table[:name].does_not_match("foo%")
+ node.must_be_kind_of Nodes::DoesNotMatch
+ node.case_sensitive.must_equal(false)
+ compile(node).must_be_like %{
+ "users"."name" NOT ILIKE 'foo%'
+ }
+ end
+
+ it "should know how to visit case sensitive" do
+ node = @table[:name].does_not_match("foo%", nil, true)
+ node.case_sensitive.must_equal(true)
+ compile(node).must_be_like %{
+ "users"."name" NOT LIKE 'foo%'
+ }
+ end
+
+ it "can handle ESCAPE" do
+ node = @table[:name].does_not_match("foo!%", "!")
+ compile(node).must_be_like %{
+ "users"."name" NOT ILIKE 'foo!%' ESCAPE '!'
+ }
+ end
+
+ it "can handle subqueries" do
+ subquery = @table.project(:id).where(@table[:name].does_not_match("foo%"))
+ node = @attr.in subquery
+ compile(node).must_be_like %{
+ "users"."id" IN (SELECT id FROM "users" WHERE "users"."name" NOT ILIKE 'foo%')
+ }
+ end
+ end
+
+ describe "Nodes::Regexp" do
+ it "should know how to visit" do
+ node = @table[:name].matches_regexp("foo.*")
+ node.must_be_kind_of Nodes::Regexp
+ node.case_sensitive.must_equal(true)
+ compile(node).must_be_like %{
+ "users"."name" ~ 'foo.*'
+ }
+ end
+
+ it "can handle case insensitive" do
+ node = @table[:name].matches_regexp("foo.*", false)
+ node.must_be_kind_of Nodes::Regexp
+ node.case_sensitive.must_equal(false)
+ compile(node).must_be_like %{
+ "users"."name" ~* 'foo.*'
+ }
+ end
+
+ it "can handle subqueries" do
+ subquery = @table.project(:id).where(@table[:name].matches_regexp("foo.*"))
+ node = @attr.in subquery
+ compile(node).must_be_like %{
+ "users"."id" IN (SELECT id FROM "users" WHERE "users"."name" ~ 'foo.*')
+ }
+ end
+ end
+
+ describe "Nodes::NotRegexp" do
+ it "should know how to visit" do
+ node = @table[:name].does_not_match_regexp("foo.*")
+ node.must_be_kind_of Nodes::NotRegexp
+ node.case_sensitive.must_equal(true)
+ compile(node).must_be_like %{
+ "users"."name" !~ 'foo.*'
+ }
+ end
+
+ it "can handle case insensitive" do
+ node = @table[:name].does_not_match_regexp("foo.*", false)
+ node.case_sensitive.must_equal(false)
+ compile(node).must_be_like %{
+ "users"."name" !~* 'foo.*'
+ }
+ end
+
+ it "can handle subqueries" do
+ subquery = @table.project(:id).where(@table[:name].does_not_match_regexp("foo.*"))
+ node = @attr.in subquery
+ compile(node).must_be_like %{
+ "users"."id" IN (SELECT id FROM "users" WHERE "users"."name" !~ 'foo.*')
+ }
+ end
+ end
+
+ describe "Nodes::BindParam" do
+ it "increments each bind param" do
+ query = @table[:name].eq(Arel::Nodes::BindParam.new(1))
+ .and(@table[:id].eq(Arel::Nodes::BindParam.new(1)))
+ compile(query).must_be_like %{
+ "users"."name" = $1 AND "users"."id" = $2
+ }
+ end
+ end
+
+ describe "Nodes::Cube" do
+ it "should know how to visit with array arguments" do
+ node = Arel::Nodes::Cube.new([@table[:name], @table[:bool]])
+ compile(node).must_be_like %{
+ CUBE( "users"."name", "users"."bool" )
+ }
+ end
+
+ it "should know how to visit with CubeDimension Argument" do
+ dimensions = Arel::Nodes::GroupingElement.new([@table[:name], @table[:bool]])
+ node = Arel::Nodes::Cube.new(dimensions)
+ compile(node).must_be_like %{
+ CUBE( "users"."name", "users"."bool" )
+ }
+ end
+
+ it "should know how to generate parenthesis when supplied with many Dimensions" do
+ dim1 = Arel::Nodes::GroupingElement.new(@table[:name])
+ dim2 = Arel::Nodes::GroupingElement.new([@table[:bool], @table[:created_at]])
+ node = Arel::Nodes::Cube.new([dim1, dim2])
+ compile(node).must_be_like %{
+ CUBE( ( "users"."name" ), ( "users"."bool", "users"."created_at" ) )
+ }
+ end
+ end
+
+ describe "Nodes::GroupingSet" do
+ it "should know how to visit with array arguments" do
+ node = Arel::Nodes::GroupingSet.new([@table[:name], @table[:bool]])
+ compile(node).must_be_like %{
+ GROUPING SETS( "users"."name", "users"."bool" )
+ }
+ end
+
+ it "should know how to visit with CubeDimension Argument" do
+ group = Arel::Nodes::GroupingElement.new([@table[:name], @table[:bool]])
+ node = Arel::Nodes::GroupingSet.new(group)
+ compile(node).must_be_like %{
+ GROUPING SETS( "users"."name", "users"."bool" )
+ }
+ end
+
+ it "should know how to generate parenthesis when supplied with many Dimensions" do
+ group1 = Arel::Nodes::GroupingElement.new(@table[:name])
+ group2 = Arel::Nodes::GroupingElement.new([@table[:bool], @table[:created_at]])
+ node = Arel::Nodes::GroupingSet.new([group1, group2])
+ compile(node).must_be_like %{
+ GROUPING SETS( ( "users"."name" ), ( "users"."bool", "users"."created_at" ) )
+ }
+ end
+ end
+
+ describe "Nodes::RollUp" do
+ it "should know how to visit with array arguments" do
+ node = Arel::Nodes::RollUp.new([@table[:name], @table[:bool]])
+ compile(node).must_be_like %{
+ ROLLUP( "users"."name", "users"."bool" )
+ }
+ end
+
+ it "should know how to visit with CubeDimension Argument" do
+ group = Arel::Nodes::GroupingElement.new([@table[:name], @table[:bool]])
+ node = Arel::Nodes::RollUp.new(group)
+ compile(node).must_be_like %{
+ ROLLUP( "users"."name", "users"."bool" )
+ }
+ end
+
+ it "should know how to generate parenthesis when supplied with many Dimensions" do
+ group1 = Arel::Nodes::GroupingElement.new(@table[:name])
+ group2 = Arel::Nodes::GroupingElement.new([@table[:bool], @table[:created_at]])
+ node = Arel::Nodes::RollUp.new([group1, group2])
+ compile(node).must_be_like %{
+ ROLLUP( ( "users"."name" ), ( "users"."bool", "users"."created_at" ) )
+ }
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/arel/visitors/sqlite_test.rb b/activerecord/test/cases/arel/visitors/sqlite_test.rb
new file mode 100644
index 0000000000..6650b6ff3a
--- /dev/null
+++ b/activerecord/test/cases/arel/visitors/sqlite_test.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+require_relative "../helper"
+
+module Arel
+ module Visitors
+ class SqliteTest < Arel::Spec
+ before do
+ @visitor = SQLite.new Table.engine.connection_pool
+ end
+
+ it "defaults limit to -1" do
+ stmt = Nodes::SelectStatement.new
+ stmt.offset = Nodes::Offset.new(1)
+ sql = @visitor.accept(stmt, Collectors::SQLString.new).value
+ sql.must_be_like "SELECT LIMIT -1 OFFSET 1"
+ end
+
+ it "does not support locking" do
+ node = Nodes::Lock.new(Arel.sql("FOR UPDATE"))
+ assert_equal "", @visitor.accept(node, Collectors::SQLString.new).value
+ end
+
+ it "does not support boolean" do
+ node = Nodes::True.new()
+ assert_equal "1", @visitor.accept(node, Collectors::SQLString.new).value
+ node = Nodes::False.new()
+ assert_equal "0", @visitor.accept(node, Collectors::SQLString.new).value
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/arel/visitors/to_sql_test.rb b/activerecord/test/cases/arel/visitors/to_sql_test.rb
new file mode 100644
index 0000000000..e8ac50bfa3
--- /dev/null
+++ b/activerecord/test/cases/arel/visitors/to_sql_test.rb
@@ -0,0 +1,654 @@
+# frozen_string_literal: true
+
+require_relative "../helper"
+require "bigdecimal"
+
+module Arel
+ module Visitors
+ describe "the to_sql visitor" do
+ before do
+ @conn = FakeRecord::Base.new
+ @visitor = ToSql.new @conn.connection
+ @table = Table.new(:users)
+ @attr = @table[:id]
+ end
+
+ def compile(node)
+ @visitor.accept(node, Collectors::SQLString.new).value
+ end
+
+ it "works with BindParams" do
+ node = Nodes::BindParam.new(1)
+ sql = compile node
+ sql.must_be_like "?"
+ end
+
+ it "does not quote BindParams used as part of a Values" do
+ bp = Nodes::BindParam.new(1)
+ values = Nodes::Values.new([bp])
+ sql = compile values
+ sql.must_be_like "VALUES (?)"
+ end
+
+ it "can define a dispatch method" do
+ visited = false
+ viz = Class.new(Arel::Visitors::Visitor) {
+ define_method(:hello) do |node, c|
+ visited = true
+ end
+
+ def dispatch
+ { Arel::Table => "hello" }
+ end
+ }.new
+
+ viz.accept(@table, Collectors::SQLString.new)
+ assert visited, "hello method was called"
+ end
+
+ it "should not quote sql literals" do
+ node = @table[Arel.star]
+ sql = compile node
+ sql.must_be_like '"users".*'
+ end
+
+ it "should visit named functions" do
+ function = Nodes::NamedFunction.new("omg", [Arel.star])
+ assert_equal "omg(*)", compile(function)
+ end
+
+ it "should chain predications on named functions" do
+ function = Nodes::NamedFunction.new("omg", [Arel.star])
+ sql = compile(function.eq(2))
+ sql.must_be_like %{ omg(*) = 2 }
+ end
+
+ it "should handle nil with named functions" do
+ function = Nodes::NamedFunction.new("omg", [Arel.star])
+ sql = compile(function.eq(nil))
+ sql.must_be_like %{ omg(*) IS NULL }
+ end
+
+ it "should visit built-in functions" do
+ function = Nodes::Count.new([Arel.star])
+ assert_equal "COUNT(*)", compile(function)
+
+ function = Nodes::Sum.new([Arel.star])
+ assert_equal "SUM(*)", compile(function)
+
+ function = Nodes::Max.new([Arel.star])
+ assert_equal "MAX(*)", compile(function)
+
+ function = Nodes::Min.new([Arel.star])
+ assert_equal "MIN(*)", compile(function)
+
+ function = Nodes::Avg.new([Arel.star])
+ assert_equal "AVG(*)", compile(function)
+ end
+
+ it "should visit built-in functions operating on distinct values" do
+ function = Nodes::Count.new([Arel.star])
+ function.distinct = true
+ assert_equal "COUNT(DISTINCT *)", compile(function)
+
+ function = Nodes::Sum.new([Arel.star])
+ function.distinct = true
+ assert_equal "SUM(DISTINCT *)", compile(function)
+
+ function = Nodes::Max.new([Arel.star])
+ function.distinct = true
+ assert_equal "MAX(DISTINCT *)", compile(function)
+
+ function = Nodes::Min.new([Arel.star])
+ function.distinct = true
+ assert_equal "MIN(DISTINCT *)", compile(function)
+
+ function = Nodes::Avg.new([Arel.star])
+ function.distinct = true
+ assert_equal "AVG(DISTINCT *)", compile(function)
+ end
+
+ it "works with lists" do
+ function = Nodes::NamedFunction.new("omg", [Arel.star, Arel.star])
+ assert_equal "omg(*, *)", compile(function)
+ end
+
+ describe "Nodes::Equality" do
+ it "should escape strings" do
+ test = Table.new(:users)[:name].eq "Aaron Patterson"
+ compile(test).must_be_like %{
+ "users"."name" = 'Aaron Patterson'
+ }
+ end
+
+ it "should handle false" do
+ table = Table.new(:users)
+ val = Nodes.build_quoted(false, table[:active])
+ sql = compile Nodes::Equality.new(val, val)
+ sql.must_be_like %{ 'f' = 'f' }
+ end
+
+ it "should handle nil" do
+ sql = compile Nodes::Equality.new(@table[:name], nil)
+ sql.must_be_like %{ "users"."name" IS NULL }
+ end
+ end
+
+ describe "Nodes::Grouping" do
+ it "wraps nested groupings in brackets only once" do
+ sql = compile Nodes::Grouping.new(Nodes::Grouping.new(Nodes.build_quoted("foo")))
+ sql.must_equal "('foo')"
+ end
+ end
+
+ describe "Nodes::NotEqual" do
+ it "should handle false" do
+ val = Nodes.build_quoted(false, @table[:active])
+ sql = compile Nodes::NotEqual.new(@table[:active], val)
+ sql.must_be_like %{ "users"."active" != 'f' }
+ end
+
+ it "should handle nil" do
+ val = Nodes.build_quoted(nil, @table[:active])
+ sql = compile Nodes::NotEqual.new(@table[:name], val)
+ sql.must_be_like %{ "users"."name" IS NOT NULL }
+ end
+ end
+
+ it "should visit string subclass" do
+ [
+ Class.new(String).new(":'("),
+ Class.new(Class.new(String)).new(":'("),
+ ].each do |obj|
+ val = Nodes.build_quoted(obj, @table[:active])
+ sql = compile Nodes::NotEqual.new(@table[:name], val)
+ sql.must_be_like %{ "users"."name" != ':\\'(' }
+ end
+ end
+
+ it "should visit_Class" do
+ compile(Nodes.build_quoted(DateTime)).must_equal "'DateTime'"
+ end
+
+ it "should escape LIMIT" do
+ sc = Arel::Nodes::SelectStatement.new
+ sc.limit = Arel::Nodes::Limit.new(Nodes.build_quoted("omg"))
+ assert_match(/LIMIT 'omg'/, compile(sc))
+ end
+
+ it "should contain a single space before ORDER BY" do
+ table = Table.new(:users)
+ test = table.order(table[:name])
+ sql = compile test
+ assert_match(/"users" ORDER BY/, sql)
+ end
+
+ it "should quote LIMIT without column type coercion" do
+ table = Table.new(:users)
+ sc = table.where(table[:name].eq(0)).take(1).ast
+ assert_match(/WHERE "users"."name" = 0 LIMIT 1/, compile(sc))
+ end
+
+ it "should visit_DateTime" do
+ dt = DateTime.now
+ table = Table.new(:users)
+ test = table[:created_at].eq dt
+ sql = compile test
+
+ sql.must_be_like %{"users"."created_at" = '#{dt.strftime("%Y-%m-%d %H:%M:%S")}'}
+ end
+
+ it "should visit_Float" do
+ test = Table.new(:products)[:price].eq 2.14
+ sql = compile test
+ sql.must_be_like %{"products"."price" = 2.14}
+ end
+
+ it "should visit_Not" do
+ sql = compile Nodes::Not.new(Arel.sql("foo"))
+ sql.must_be_like "NOT (foo)"
+ end
+
+ it "should apply Not to the whole expression" do
+ node = Nodes::And.new [@attr.eq(10), @attr.eq(11)]
+ sql = compile Nodes::Not.new(node)
+ sql.must_be_like %{NOT ("users"."id" = 10 AND "users"."id" = 11)}
+ end
+
+ it "should visit_As" do
+ as = Nodes::As.new(Arel.sql("foo"), Arel.sql("bar"))
+ sql = compile as
+ sql.must_be_like "foo AS bar"
+ end
+
+ it "should visit_Bignum" do
+ compile 8787878092
+ end
+
+ it "should visit_Hash" do
+ compile(Nodes.build_quoted(a: 1))
+ end
+
+ it "should visit_Set" do
+ compile Nodes.build_quoted(Set.new([1, 2]))
+ end
+
+ it "should visit_BigDecimal" do
+ compile Nodes.build_quoted(BigDecimal("2.14"))
+ end
+
+ it "should visit_Date" do
+ dt = Date.today
+ table = Table.new(:users)
+ test = table[:created_at].eq dt
+ sql = compile test
+
+ sql.must_be_like %{"users"."created_at" = '#{dt.strftime("%Y-%m-%d")}'}
+ end
+
+ it "should visit_NilClass" do
+ compile(Nodes.build_quoted(nil)).must_be_like "NULL"
+ end
+
+ it "unsupported input should raise UnsupportedVisitError" do
+ error = assert_raises(UnsupportedVisitError) { compile(nil) }
+ assert_match(/\AUnsupported/, error.message)
+ end
+
+ it "should visit_Arel_SelectManager, which is a subquery" do
+ mgr = Table.new(:foo).project(:bar)
+ compile(mgr).must_be_like '(SELECT bar FROM "foo")'
+ end
+
+ it "should visit_Arel_Nodes_And" do
+ node = Nodes::And.new [@attr.eq(10), @attr.eq(11)]
+ compile(node).must_be_like %{
+ "users"."id" = 10 AND "users"."id" = 11
+ }
+ end
+
+ it "should visit_Arel_Nodes_Or" do
+ node = Nodes::Or.new @attr.eq(10), @attr.eq(11)
+ compile(node).must_be_like %{
+ "users"."id" = 10 OR "users"."id" = 11
+ }
+ end
+
+ it "should visit_Arel_Nodes_Assignment" do
+ column = @table["id"]
+ node = Nodes::Assignment.new(
+ Nodes::UnqualifiedColumn.new(column),
+ Nodes::UnqualifiedColumn.new(column)
+ )
+ compile(node).must_be_like %{
+ "id" = "id"
+ }
+ end
+
+ it "should visit visit_Arel_Attributes_Time" do
+ attr = Attributes::Time.new(@attr.relation, @attr.name)
+ compile attr
+ end
+
+ it "should visit_TrueClass" do
+ test = Table.new(:users)[:bool].eq(true)
+ compile(test).must_be_like %{ "users"."bool" = 't' }
+ end
+
+ describe "Nodes::Matches" do
+ it "should know how to visit" do
+ node = @table[:name].matches("foo%")
+ compile(node).must_be_like %{
+ "users"."name" LIKE 'foo%'
+ }
+ end
+
+ it "can handle ESCAPE" do
+ node = @table[:name].matches("foo!%", "!")
+ compile(node).must_be_like %{
+ "users"."name" LIKE 'foo!%' ESCAPE '!'
+ }
+ end
+
+ it "can handle subqueries" do
+ subquery = @table.project(:id).where(@table[:name].matches("foo%"))
+ node = @attr.in subquery
+ compile(node).must_be_like %{
+ "users"."id" IN (SELECT id FROM "users" WHERE "users"."name" LIKE 'foo%')
+ }
+ end
+ end
+
+ describe "Nodes::DoesNotMatch" do
+ it "should know how to visit" do
+ node = @table[:name].does_not_match("foo%")
+ compile(node).must_be_like %{
+ "users"."name" NOT LIKE 'foo%'
+ }
+ end
+
+ it "can handle ESCAPE" do
+ node = @table[:name].does_not_match("foo!%", "!")
+ compile(node).must_be_like %{
+ "users"."name" NOT LIKE 'foo!%' ESCAPE '!'
+ }
+ end
+
+ it "can handle subqueries" do
+ subquery = @table.project(:id).where(@table[:name].does_not_match("foo%"))
+ node = @attr.in subquery
+ compile(node).must_be_like %{
+ "users"."id" IN (SELECT id FROM "users" WHERE "users"."name" NOT LIKE 'foo%')
+ }
+ end
+ end
+
+ describe "Nodes::Ordering" do
+ it "should know how to visit" do
+ node = @attr.desc
+ compile(node).must_be_like %{
+ "users"."id" DESC
+ }
+ end
+ end
+
+ describe "Nodes::In" do
+ it "should know how to visit" do
+ node = @attr.in [1, 2, 3]
+ compile(node).must_be_like %{
+ "users"."id" IN (1, 2, 3)
+ }
+ end
+
+ it "should return 1=0 when empty right which is always false" do
+ node = @attr.in []
+ compile(node).must_equal "1=0"
+ end
+
+ it "can handle two dot ranges" do
+ node = @attr.between 1..3
+ compile(node).must_be_like %{
+ "users"."id" BETWEEN 1 AND 3
+ }
+ end
+
+ it "can handle three dot ranges" do
+ node = @attr.between 1...3
+ compile(node).must_be_like %{
+ "users"."id" >= 1 AND "users"."id" < 3
+ }
+ end
+
+ it "can handle ranges bounded by infinity" do
+ node = @attr.between 1..Float::INFINITY
+ compile(node).must_be_like %{
+ "users"."id" >= 1
+ }
+ node = @attr.between(-Float::INFINITY..3)
+ compile(node).must_be_like %{
+ "users"."id" <= 3
+ }
+ node = @attr.between(-Float::INFINITY...3)
+ compile(node).must_be_like %{
+ "users"."id" < 3
+ }
+ node = @attr.between(-Float::INFINITY..Float::INFINITY)
+ compile(node).must_be_like %{1=1}
+ end
+
+ it "can handle subqueries" do
+ table = Table.new(:users)
+ subquery = table.project(:id).where(table[:name].eq("Aaron"))
+ node = @attr.in subquery
+ compile(node).must_be_like %{
+ "users"."id" IN (SELECT id FROM "users" WHERE "users"."name" = 'Aaron')
+ }
+ end
+ end
+
+ describe "Nodes::InfixOperation" do
+ it "should handle Multiplication" do
+ node = Arel::Attributes::Decimal.new(Table.new(:products), :price) * Arel::Attributes::Decimal.new(Table.new(:currency_rates), :rate)
+ compile(node).must_equal %("products"."price" * "currency_rates"."rate")
+ end
+
+ it "should handle Division" do
+ node = Arel::Attributes::Decimal.new(Table.new(:products), :price) / 5
+ compile(node).must_equal %("products"."price" / 5)
+ end
+
+ it "should handle Addition" do
+ node = Arel::Attributes::Decimal.new(Table.new(:products), :price) + 6
+ compile(node).must_equal %(("products"."price" + 6))
+ end
+
+ it "should handle Subtraction" do
+ node = Arel::Attributes::Decimal.new(Table.new(:products), :price) - 7
+ compile(node).must_equal %(("products"."price" - 7))
+ end
+
+ it "should handle Concatenation" do
+ table = Table.new(:users)
+ node = table[:name].concat(table[:name])
+ compile(node).must_equal %("users"."name" || "users"."name")
+ end
+
+ it "should handle BitwiseAnd" do
+ node = Arel::Attributes::Integer.new(Table.new(:products), :bitmap) & 16
+ compile(node).must_equal %(("products"."bitmap" & 16))
+ end
+
+ it "should handle BitwiseOr" do
+ node = Arel::Attributes::Integer.new(Table.new(:products), :bitmap) | 16
+ compile(node).must_equal %(("products"."bitmap" | 16))
+ end
+
+ it "should handle BitwiseXor" do
+ node = Arel::Attributes::Integer.new(Table.new(:products), :bitmap) ^ 16
+ compile(node).must_equal %(("products"."bitmap" ^ 16))
+ end
+
+ it "should handle BitwiseShiftLeft" do
+ node = Arel::Attributes::Integer.new(Table.new(:products), :bitmap) << 4
+ compile(node).must_equal %(("products"."bitmap" << 4))
+ end
+
+ it "should handle BitwiseShiftRight" do
+ node = Arel::Attributes::Integer.new(Table.new(:products), :bitmap) >> 4
+ compile(node).must_equal %(("products"."bitmap" >> 4))
+ end
+
+ it "should handle arbitrary operators" do
+ node = Arel::Nodes::InfixOperation.new(
+ "&&",
+ Arel::Attributes::String.new(Table.new(:products), :name),
+ Arel::Attributes::String.new(Table.new(:products), :name)
+ )
+ compile(node).must_equal %("products"."name" && "products"."name")
+ end
+ end
+
+ describe "Nodes::UnaryOperation" do
+ it "should handle BitwiseNot" do
+ node = ~ Arel::Attributes::Integer.new(Table.new(:products), :bitmap)
+ compile(node).must_equal %( ~ "products"."bitmap")
+ end
+
+ it "should handle arbitrary operators" do
+ node = Arel::Nodes::UnaryOperation.new("!", Arel::Attributes::String.new(Table.new(:products), :active))
+ compile(node).must_equal %( ! "products"."active")
+ end
+ end
+
+ describe "Nodes::NotIn" do
+ it "should know how to visit" do
+ node = @attr.not_in [1, 2, 3]
+ compile(node).must_be_like %{
+ "users"."id" NOT IN (1, 2, 3)
+ }
+ end
+
+ it "should return 1=1 when empty right which is always true" do
+ node = @attr.not_in []
+ compile(node).must_equal "1=1"
+ end
+
+ it "can handle two dot ranges" do
+ node = @attr.not_between 1..3
+ compile(node).must_equal(
+ %{("users"."id" < 1 OR "users"."id" > 3)}
+ )
+ end
+
+ it "can handle three dot ranges" do
+ node = @attr.not_between 1...3
+ compile(node).must_equal(
+ %{("users"."id" < 1 OR "users"."id" >= 3)}
+ )
+ end
+
+ it "can handle ranges bounded by infinity" do
+ node = @attr.not_between 1..Float::INFINITY
+ compile(node).must_be_like %{
+ "users"."id" < 1
+ }
+ node = @attr.not_between(-Float::INFINITY..3)
+ compile(node).must_be_like %{
+ "users"."id" > 3
+ }
+ node = @attr.not_between(-Float::INFINITY...3)
+ compile(node).must_be_like %{
+ "users"."id" >= 3
+ }
+ node = @attr.not_between(-Float::INFINITY..Float::INFINITY)
+ compile(node).must_be_like %{1=0}
+ end
+
+ it "can handle subqueries" do
+ table = Table.new(:users)
+ subquery = table.project(:id).where(table[:name].eq("Aaron"))
+ node = @attr.not_in subquery
+ compile(node).must_be_like %{
+ "users"."id" NOT IN (SELECT id FROM "users" WHERE "users"."name" = 'Aaron')
+ }
+ end
+ end
+
+ describe "Constants" do
+ it "should handle true" do
+ test = Table.new(:users).create_true
+ compile(test).must_be_like %{
+ TRUE
+ }
+ end
+
+ it "should handle false" do
+ test = Table.new(:users).create_false
+ compile(test).must_be_like %{
+ FALSE
+ }
+ end
+ end
+
+ describe "TableAlias" do
+ it "should use the underlying table for checking columns" do
+ test = Table.new(:users).alias("zomgusers")[:id].eq "3"
+ compile(test).must_be_like %{
+ "zomgusers"."id" = '3'
+ }
+ end
+ end
+
+ describe "distinct on" do
+ it "raises not implemented error" do
+ core = Arel::Nodes::SelectCore.new
+ core.set_quantifier = Arel::Nodes::DistinctOn.new(Arel.sql("aaron"))
+
+ assert_raises(NotImplementedError) do
+ compile(core)
+ end
+ end
+ end
+
+ describe "Nodes::Regexp" do
+ it "raises not implemented error" do
+ node = Arel::Nodes::Regexp.new(@table[:name], Nodes.build_quoted("foo%"))
+
+ assert_raises(NotImplementedError) do
+ compile(node)
+ end
+ end
+ end
+
+ describe "Nodes::NotRegexp" do
+ it "raises not implemented error" do
+ node = Arel::Nodes::NotRegexp.new(@table[:name], Nodes.build_quoted("foo%"))
+
+ assert_raises(NotImplementedError) do
+ compile(node)
+ end
+ end
+ end
+
+ describe "Nodes::Case" do
+ it "supports simple case expressions" do
+ node = Arel::Nodes::Case.new(@table[:name])
+ .when("foo").then(1)
+ .else(0)
+
+ compile(node).must_be_like %{
+ CASE "users"."name" WHEN 'foo' THEN 1 ELSE 0 END
+ }
+ end
+
+ it "supports extended case expressions" do
+ node = Arel::Nodes::Case.new
+ .when(@table[:name].in(%w(foo bar))).then(1)
+ .else(0)
+
+ compile(node).must_be_like %{
+ CASE WHEN "users"."name" IN ('foo', 'bar') THEN 1 ELSE 0 END
+ }
+ end
+
+ it "works without default branch" do
+ node = Arel::Nodes::Case.new(@table[:name])
+ .when("foo").then(1)
+
+ compile(node).must_be_like %{
+ CASE "users"."name" WHEN 'foo' THEN 1 END
+ }
+ end
+
+ it "allows chaining multiple conditions" do
+ node = Arel::Nodes::Case.new(@table[:name])
+ .when("foo").then(1)
+ .when("bar").then(2)
+ .else(0)
+
+ compile(node).must_be_like %{
+ CASE "users"."name" WHEN 'foo' THEN 1 WHEN 'bar' THEN 2 ELSE 0 END
+ }
+ end
+
+ it "supports #when with two arguments and no #then" do
+ node = Arel::Nodes::Case.new @table[:name]
+
+ { foo: 1, bar: 0 }.reduce(node) { |_node, pair| _node.when(*pair) }
+
+ compile(node).must_be_like %{
+ CASE "users"."name" WHEN 'foo' THEN 1 WHEN 'bar' THEN 0 END
+ }
+ end
+
+ it "can be chained as a predicate" do
+ node = @table[:name].when("foo").then("bar").else("baz")
+
+ compile(node).must_be_like %{
+ CASE "users"."name" WHEN 'foo' THEN 'bar' ELSE 'baz' END
+ }
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/associations/association_scope_test.rb b/activerecord/test/cases/associations/association_scope_test.rb
deleted file mode 100644
index c322333f6d..0000000000
--- a/activerecord/test/cases/associations/association_scope_test.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-require "cases/helper"
-require "models/post"
-require "models/author"
-
-module ActiveRecord
- module Associations
- class AssociationScopeTest < ActiveRecord::TestCase
- test "does not duplicate conditions" do
- scope = AssociationScope.scope(Author.new.association(:welcome_posts),
- Author.connection)
- binds = scope.where_clause.binds.map(&:value)
- assert_equal binds.uniq, binds
- end
- end
- end
-end
diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb
index 5875a1871f..0cc4ed7127 100644
--- a/activerecord/test/cases/associations/belongs_to_associations_test.rb
+++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/developer"
require "models/project"
@@ -35,6 +37,21 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_equal companies(:first_firm).name, firm.name
end
+ def test_assigning_belongs_to_on_destroyed_object
+ client = Client.create!(name: "Client")
+ client.destroy!
+ assert_raise(frozen_error_class) { client.firm = nil }
+ assert_raise(frozen_error_class) { client.firm = Firm.new(name: "Firm") }
+ end
+
+ def test_eager_loading_wont_mutate_owner_record
+ client = Client.eager_load(:firm_with_basic_id).first
+ assert_not_predicate client, :firm_id_came_from_user?
+
+ client = Client.preload(:firm_with_basic_id).first
+ assert_not_predicate client, :firm_id_came_from_user?
+ end
+
def test_missing_attribute_error_is_raised_when_no_foreign_key_attribute
assert_raises(ActiveModel::MissingAttributeError) { Client.select(:id).first.firm }
end
@@ -77,7 +94,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
end
account = model.new
- assert account.valid?
+ assert_predicate account, :valid?
ensure
ActiveRecord::Base.belongs_to_required_by_default = original_value
end
@@ -93,7 +110,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
end
account = model.new
- assert_not account.valid?
+ assert_not_predicate account, :valid?
assert_equal [{ error: :blank }], account.errors.details[:company]
ensure
ActiveRecord::Base.belongs_to_required_by_default = original_value
@@ -110,12 +127,50 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
end
account = model.new
- assert_not account.valid?
+ assert_not_predicate account, :valid?
assert_equal [{ error: :blank }], account.errors.details[:company]
ensure
ActiveRecord::Base.belongs_to_required_by_default = original_value
end
+ def test_default
+ david = developers(:david)
+ jamis = developers(:jamis)
+
+ model = Class.new(ActiveRecord::Base) do
+ self.table_name = "ships"
+ def self.name; "Temp"; end
+ belongs_to :developer, default: -> { david }
+ end
+
+ ship = model.create!
+ assert_equal david, ship.developer
+
+ ship = model.create!(developer: jamis)
+ assert_equal jamis, ship.developer
+
+ ship.update!(developer: nil)
+ assert_equal david, ship.developer
+ end
+
+ def test_default_with_lambda
+ model = Class.new(ActiveRecord::Base) do
+ self.table_name = "ships"
+ def self.name; "Temp"; end
+ belongs_to :developer, default: -> { default_developer }
+
+ def default_developer
+ Developer.first
+ end
+ end
+
+ ship = model.create!
+ assert_equal developers(:david), ship.developer
+
+ ship = model.create!(developer: developers(:jamis))
+ assert_equal developers(:jamis), ship.developer
+ end
+
def test_default_scope_on_relations_is_not_cached
counter = 0
@@ -177,6 +232,8 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
remove_column :admin_users, :region_id if column_exists?(:admin_users, :region_id)
drop_table :admin_regions, if_exists: true
end
+
+ Admin::User.reset_column_information
end
def test_natural_assignment
@@ -204,14 +261,14 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
Firm.create("name" => "Apple")
Client.create("name" => "Citibank", :firm_name => "Apple")
citibank_result = Client.all.merge!(where: { name: "Citibank" }, includes: :firm_with_primary_key).first
- assert citibank_result.association(:firm_with_primary_key).loaded?
+ assert_predicate citibank_result.association(:firm_with_primary_key), :loaded?
end
def test_eager_loading_with_primary_key_as_symbol
Firm.create("name" => "Apple")
Client.create("name" => "Citibank", :firm_name => "Apple")
citibank_result = Client.all.merge!(where: { name: "Citibank" }, includes: :firm_with_primary_key_symbols).first
- assert citibank_result.association(:firm_with_primary_key_symbols).loaded?
+ assert_predicate citibank_result.association(:firm_with_primary_key_symbols), :loaded?
end
def test_creating_the_belonging_object
@@ -223,6 +280,15 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_equal apple, citibank.firm
end
+ def test_creating_the_belonging_object_from_new_record
+ citibank = Account.new("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_creating_the_belonging_object_with_primary_key
client = Client.create(name: "Primary key client")
apple = client.create_firm_with_primary_key("name" => "Apple")
@@ -278,7 +344,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
client = Client.create!(name: "Jimmy")
account = client.create_account!(credit_limit: 10)
assert_equal account, client.account
- assert account.persisted?
+ assert_predicate account, :persisted?
client.save
client.reload
assert_equal account, client.account
@@ -288,7 +354,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
client = Client.create!(name: "Jimmy")
assert_raise(ActiveRecord::RecordInvalid) { client.create_account! }
assert_not_nil client.account
- assert client.account.new_record?
+ assert_predicate client.account, :new_record?
end
def test_reloading_the_belonging_object
@@ -342,7 +408,6 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
sponsor.sponsorable = Member.new name: "Bert"
assert_equal Member, sponsor.association(:sponsorable).send(:klass)
- assert_equal "members", sponsor.association(:sponsorable).aliased_table_name
end
def test_with_polymorphic_and_condition
@@ -401,7 +466,20 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
def test_belongs_to_with_primary_key_counter
debate = Topic.create("title" => "debate")
debate2 = Topic.create("title" => "debate2")
- reply = Reply.create("title" => "blah!", "content" => "world around!", "parent_title" => "debate")
+ reply = Reply.create("title" => "blah!", "content" => "world around!", "parent_title" => "debate2")
+
+ assert_equal 0, debate.reload.replies_count
+ assert_equal 1, debate2.reload.replies_count
+
+ reply.parent_title = "debate"
+ reply.save!
+
+ assert_equal 1, debate.reload.replies_count
+ assert_equal 0, debate2.reload.replies_count
+
+ assert_no_queries do
+ reply.topic_with_primary_key = debate
+ end
assert_equal 1, debate.reload.replies_count
assert_equal 0, debate2.reload.replies_count
@@ -479,6 +557,48 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_equal 1, Topic.find(topic.id)[:replies_count]
end
+ def test_belongs_to_counter_after_touch
+ topic = Topic.create!(title: "topic")
+
+ assert_equal 0, topic.replies_count
+ assert_equal 0, topic.after_touch_called
+
+ reply = Reply.create!(title: "blah!", content: "world around!", topic_with_primary_key: topic)
+
+ assert_equal 1, topic.replies_count
+ assert_equal 1, topic.after_touch_called
+
+ reply.destroy!
+
+ assert_equal 0, topic.replies_count
+ assert_equal 2, topic.after_touch_called
+ end
+
+ def test_belongs_to_touch_with_reassigning
+ debate = Topic.create!(title: "debate")
+ debate2 = Topic.create!(title: "debate2")
+ reply = Reply.create!(title: "blah!", content: "world around!", parent_title: "debate2")
+
+ time = 1.day.ago
+
+ debate.touch(time: time)
+ debate2.touch(time: time)
+
+ reply.parent_title = "debate"
+ reply.save!
+
+ assert_operator debate.reload.updated_at, :>, time
+ assert_operator debate2.reload.updated_at, :>, time
+
+ debate.touch(time: time)
+ debate2.touch(time: time)
+
+ reply.topic_with_primary_key = debate2
+
+ assert_operator debate.reload.updated_at, :>, time
+ assert_operator debate2.reload.updated_at, :>, time
+ end
+
def test_belongs_to_with_touch_option_on_touch
line_item = LineItem.create!
Invoice.create!(line_items: [line_item])
@@ -586,10 +706,10 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
final_cut = Client.new("name" => "Final Cut")
firm = Firm.find(1)
final_cut.firm = firm
- assert !final_cut.persisted?
+ assert_not_predicate final_cut, :persisted?
assert final_cut.save
- assert final_cut.persisted?
- assert firm.persisted?
+ assert_predicate final_cut, :persisted?
+ assert_predicate firm, :persisted?
assert_equal firm, final_cut.firm
final_cut.association(:firm).reload
assert_equal firm, final_cut.firm
@@ -599,10 +719,10 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
final_cut = Client.new("name" => "Final Cut")
firm = Firm.find(1)
final_cut.firm_with_primary_key = firm
- assert !final_cut.persisted?
+ assert_not_predicate final_cut, :persisted?
assert final_cut.save
- assert final_cut.persisted?
- assert firm.persisted?
+ assert_predicate final_cut, :persisted?
+ assert_predicate firm, :persisted?
assert_equal firm, final_cut.firm_with_primary_key
final_cut.association(:firm_with_primary_key).reload
assert_equal firm, final_cut.firm_with_primary_key
@@ -610,8 +730,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
def test_new_record_with_foreign_key_but_no_object
client = Client.new("firm_id" => 1)
- # sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first
- assert_equal Firm.all.merge!(order: "id").first, client.firm_with_basic_id
+ assert_equal Firm.first, client.firm_with_basic_id
end
def test_setting_foreign_key_after_nil_target_loaded
@@ -750,7 +869,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
def test_cant_save_readonly_association
assert_raise(ActiveRecord::ReadOnlyRecord) { companies(:first_client).readonly_firm.save! }
- assert companies(:first_client).readonly_firm.readonly?
+ assert_predicate companies(:first_client).readonly_firm, :readonly?
end
def test_polymorphic_assignment_foreign_key_type_string
@@ -891,6 +1010,30 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_equal error.message, "The :dependent option must be one of [:destroy, :delete], but is :nullify"
end
+ class DestroyableBook < ActiveRecord::Base
+ self.table_name = "books"
+ belongs_to :author, class_name: "UndestroyableAuthor", dependent: :destroy
+ end
+
+ class UndestroyableAuthor < ActiveRecord::Base
+ self.table_name = "authors"
+ has_one :book, class_name: "DestroyableBook", foreign_key: "author_id"
+ before_destroy :dont
+
+ def dont
+ throw(:abort)
+ end
+ end
+
+ def test_dependency_should_halt_parent_destruction
+ author = UndestroyableAuthor.create!(name: "Test")
+ book = DestroyableBook.create!(author: author)
+
+ assert_no_difference ["UndestroyableAuthor.count", "DestroyableBook.count"] do
+ assert_not book.destroy
+ end
+ end
+
def test_attributes_are_being_set_when_initialized_from_belongs_to_association_with_where_clause
new_firm = accounts(:signals37).build_firm(name: "Apple")
assert_equal new_firm.name, "Apple"
@@ -909,15 +1052,15 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
firm_proxy = client.send(:association_instance_get, :firm)
firm_with_condition_proxy = client.send(:association_instance_get, :firm_with_condition)
- assert !firm_proxy.stale_target?
- assert !firm_with_condition_proxy.stale_target?
+ assert_not_predicate firm_proxy, :stale_target?
+ assert_not_predicate firm_with_condition_proxy, :stale_target?
assert_equal companies(:first_firm), client.firm
assert_equal companies(:first_firm), client.firm_with_condition
client.client_of = companies(:another_firm).id
- assert firm_proxy.stale_target?
- assert firm_with_condition_proxy.stale_target?
+ assert_predicate firm_proxy, :stale_target?
+ assert_predicate firm_with_condition_proxy, :stale_target?
assert_equal companies(:another_firm), client.firm
assert_equal companies(:another_firm), client.firm_with_condition
end
@@ -928,12 +1071,12 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
sponsor.sponsorable
proxy = sponsor.send(:association_instance_get, :sponsorable)
- assert !proxy.stale_target?
+ assert_not_predicate proxy, :stale_target?
assert_equal members(:groucho), sponsor.sponsorable
sponsor.sponsorable_id = members(:some_other_guy).id
- assert proxy.stale_target?
+ assert_predicate proxy, :stale_target?
assert_equal members(:some_other_guy), sponsor.sponsorable
end
@@ -943,12 +1086,12 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
sponsor.sponsorable
proxy = sponsor.send(:association_instance_get, :sponsorable)
- assert !proxy.stale_target?
+ assert_not_predicate proxy, :stale_target?
assert_equal members(:groucho), sponsor.sponsorable
sponsor.sponsorable_type = "Firm"
- assert proxy.stale_target?
+ assert_predicate proxy, :stale_target?
assert_equal companies(:first_firm), sponsor.sponsorable
end
@@ -970,9 +1113,20 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
post = posts(:welcome)
comment = comments(:greetings)
- assert_difference lambda { post.reload.tags_count }, -1 do
+ assert_equal post.id, comment.id
+
+ assert_difference "post.reload.tags_count", -1 do
assert_difference "comment.reload.tags_count", +1 do
tagging.taggable = comment
+ tagging.save!
+ end
+ end
+
+ assert_difference "comment.reload.tags_count", -1 do
+ assert_difference "post.reload.tags_count", +1 do
+ tagging.taggable_type = post.class.polymorphic_name
+ tagging.taggable_id = post.id
+ tagging.save!
end
end
end
@@ -1082,7 +1236,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
comment.post_id = 9223372036854775808 # out of range in the bigint
assert_nil comment.post
- assert_not comment.valid?
+ assert_not_predicate comment, :valid?
assert_equal [{ error: :blank }], comment.errors.details[:post]
end
@@ -1102,7 +1256,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
citibank.firm_id = apple.id.to_s
- assert !citibank.association(:firm).stale_target?
+ assert_not_predicate citibank.association(:firm), :stale_target?
end
def test_reflect_the_most_recent_change
@@ -1131,6 +1285,17 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
Column.create! record: record
assert_equal 1, Column.count
end
+
+ def test_multiple_counter_cache_with_after_create_update
+ post = posts(:welcome)
+ parent = comments(:greetings)
+
+ assert_difference "parent.reload.children_count", +1 do
+ assert_difference "post.reload.comments_count", +1 do
+ CommentWithAfterCreateUpdate.create(body: "foo", post: post, parent: parent)
+ end
+ end
+ end
end
class BelongsToWithForeignKeyTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/associations/bidirectional_destroy_dependencies_test.rb b/activerecord/test/cases/associations/bidirectional_destroy_dependencies_test.rb
index 8a0e041864..88221b012e 100644
--- a/activerecord/test/cases/associations/bidirectional_destroy_dependencies_test.rb
+++ b/activerecord/test/cases/associations/bidirectional_destroy_dependencies_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/content"
diff --git a/activerecord/test/cases/associations/callbacks_test.rb b/activerecord/test/cases/associations/callbacks_test.rb
index 5fd2411f6f..25d55dc4c9 100644
--- a/activerecord/test/cases/associations/callbacks_test.rb
+++ b/activerecord/test/cases/associations/callbacks_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/post"
require "models/author"
@@ -7,13 +9,13 @@ require "models/computer"
require "models/company"
class AssociationCallbacksTest < ActiveRecord::TestCase
- fixtures :posts, :authors, :projects, :developers
+ fixtures :posts, :authors, :author_addresses, :projects, :developers
def setup
@david = authors(:david)
@thinking = posts(:thinking)
@authorless = posts(:authorless)
- assert @david.post_log.empty?
+ assert_empty @david.post_log
end
def test_adding_macro_callbacks
@@ -94,7 +96,7 @@ class AssociationCallbacksTest < ActiveRecord::TestCase
def test_has_and_belongs_to_many_add_callback
david = developers(:david)
ar = projects(:active_record)
- assert ar.developers_log.empty?
+ assert_empty ar.developers_log
ar.developers_with_callbacks << david
assert_equal ["before_adding#{david.id}", "after_adding#{david.id}"], ar.developers_log
ar.developers_with_callbacks << david
@@ -120,15 +122,15 @@ class AssociationCallbacksTest < ActiveRecord::TestCase
assert_equal alice, dev
assert_not_nil new_dev
assert new_dev, "record should not have been saved"
- assert_not alice.new_record?
+ assert_not_predicate alice, :new_record?
end
def test_has_and_belongs_to_many_after_add_called_after_save
ar = projects(:active_record)
- assert ar.developers_log.empty?
+ assert_empty ar.developers_log
alice = Developer.new(name: "alice")
ar.developers_with_callbacks << alice
- assert_equal"after_adding#{alice.id}", ar.developers_log.last
+ assert_equal "after_adding#{alice.id}", ar.developers_log.last
bob = ar.developers_with_callbacks.create(name: "bob")
assert_equal "after_adding#{bob.id}", ar.developers_log.last
@@ -141,7 +143,7 @@ class AssociationCallbacksTest < ActiveRecord::TestCase
david = developers(:david)
jamis = developers(:jamis)
activerecord = projects(:active_record)
- assert activerecord.developers_log.empty?
+ assert_empty activerecord.developers_log
activerecord.developers_with_callbacks.delete(david)
assert_equal ["before_removing#{david.id}", "after_removing#{david.id}"], activerecord.developers_log
@@ -152,7 +154,7 @@ class AssociationCallbacksTest < ActiveRecord::TestCase
def test_has_and_belongs_to_many_does_not_fire_callbacks_on_clear
activerecord = projects(:active_record)
- assert activerecord.developers_log.empty?
+ assert_empty activerecord.developers_log
if activerecord.developers_with_callbacks.size == 0
activerecord.developers << developers(:david)
activerecord.developers << developers(:jamis)
@@ -161,7 +163,7 @@ class AssociationCallbacksTest < ActiveRecord::TestCase
end
activerecord.developers_with_callbacks.flat_map { |d| ["before_removing#{d.id}", "after_removing#{d.id}"] }.sort
assert activerecord.developers_with_callbacks.clear
- assert_predicate activerecord.developers_log, :empty?
+ assert_empty activerecord.developers_log
end
def test_has_many_and_belongs_to_many_callbacks_for_save_on_parent
@@ -181,7 +183,7 @@ class AssociationCallbacksTest < ActiveRecord::TestCase
@david.unchangeable_posts << @authorless
rescue Exception
end
- assert @david.post_log.empty?
+ assert_empty @david.post_log
assert_not_includes @david.unchangeable_posts, @authorless
@david.reload
assert_not_includes @david.unchangeable_posts, @authorless
diff --git a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
index ddb5c7a4aa..e717621928 100644
--- a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
+++ b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/post"
require "models/comment"
@@ -12,7 +14,7 @@ require "models/vertex"
require "models/edge"
class CascadedEagerLoadingTest < ActiveRecord::TestCase
- fixtures :authors, :mixins, :companies, :posts, :topics, :accounts, :comments,
+ fixtures :authors, :author_addresses, :mixins, :companies, :posts, :topics, :accounts, :comments,
:categorizations, :people, :categories, :edges, :vertices
def test_eager_association_loading_with_cascaded_two_levels
@@ -34,18 +36,12 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
end
def test_eager_association_loading_with_hmt_does_not_table_name_collide_when_joining_associations
- assert_nothing_raised do
- Author.joins(:posts).eager_load(:comments).where(posts: { tags_count: 1 }).to_a
- end
authors = Author.joins(:posts).eager_load(:comments).where(posts: { tags_count: 1 }).to_a
- assert_equal 1, assert_no_queries { authors.size }
+ assert_equal 3, assert_no_queries { authors.size }
assert_equal 10, assert_no_queries { authors[0].comments.size }
end
def test_eager_association_loading_grafts_stashed_associations_to_correct_parent
- assert_nothing_raised do
- Person.eager_load(primary_contact: :primary_contact).where("primary_contacts_people_2.first_name = ?", "Susan").order("people.id").to_a
- end
assert_equal people(:michael), Person.eager_load(primary_contact: :primary_contact).where("primary_contacts_people_2.first_name = ?", "Susan").order("people.id").first
end
@@ -140,7 +136,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
end
def test_eager_association_loading_with_multiple_stis_and_order
- author = Author.all.merge!(includes: { posts: [ :special_comments , :very_special_comment ] }, order: ["authors.name", "comments.body", "very_special_comments_posts.body"], where: "posts.id = 4").first
+ author = Author.all.merge!(includes: { posts: [ :special_comments, :very_special_comment ] }, order: ["authors.name", "comments.body", "very_special_comments_posts.body"], where: "posts.id = 4").first
assert_equal authors(:david), author
assert_no_queries do
author.posts.first.special_comments
diff --git a/activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb b/activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb
index 4f0fe3236e..5fca972aee 100644
--- a/activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb
+++ b/activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/post"
require "models/tagging"
@@ -6,30 +8,81 @@ module Namespaced
class Post < ActiveRecord::Base
self.table_name = "posts"
has_one :tagging, as: :taggable, class_name: "Tagging"
+
+ def self.polymorphic_name
+ sti_name
+ end
end
end
-class EagerLoadIncludeFullStiClassNamesTest < ActiveRecord::TestCase
+module PolymorphicFullStiClassNamesSharedTest
def setup
- generate_test_objects
- end
+ @old_store_full_sti_class = ActiveRecord::Base.store_full_sti_class
+ ActiveRecord::Base.store_full_sti_class = store_full_sti_class
- def generate_test_objects
post = Namespaced::Post.create(title: "Great stuff", body: "This is not", author_id: 1)
- Tagging.create(taggable: post)
+ @tagging = Tagging.create(taggable: post)
+ end
+
+ def teardown
+ ActiveRecord::Base.store_full_sti_class = @old_store_full_sti_class
end
def test_class_names
- old = ActiveRecord::Base.store_full_sti_class
+ ActiveRecord::Base.store_full_sti_class = !store_full_sti_class
+ post = Namespaced::Post.find_by_title("Great stuff")
+ assert_nil post.tagging
- ActiveRecord::Base.store_full_sti_class = false
+ ActiveRecord::Base.store_full_sti_class = store_full_sti_class
+ post = Namespaced::Post.find_by_title("Great stuff")
+ assert_equal @tagging, post.tagging
+ end
+
+ def test_class_names_with_includes
+ ActiveRecord::Base.store_full_sti_class = !store_full_sti_class
post = Namespaced::Post.includes(:tagging).find_by_title("Great stuff")
assert_nil post.tagging
- ActiveRecord::Base.store_full_sti_class = true
+ ActiveRecord::Base.store_full_sti_class = store_full_sti_class
post = Namespaced::Post.includes(:tagging).find_by_title("Great stuff")
- assert_instance_of Tagging, post.tagging
- ensure
- ActiveRecord::Base.store_full_sti_class = old
+ assert_equal @tagging, post.tagging
+ end
+
+ def test_class_names_with_eager_load
+ ActiveRecord::Base.store_full_sti_class = !store_full_sti_class
+ post = Namespaced::Post.eager_load(:tagging).find_by_title("Great stuff")
+ assert_nil post.tagging
+
+ ActiveRecord::Base.store_full_sti_class = store_full_sti_class
+ post = Namespaced::Post.eager_load(:tagging).find_by_title("Great stuff")
+ assert_equal @tagging, post.tagging
+ end
+
+ def test_class_names_with_find_by
+ post = Namespaced::Post.find_by_title("Great stuff")
+
+ ActiveRecord::Base.store_full_sti_class = !store_full_sti_class
+ assert_nil Tagging.find_by(taggable: post)
+
+ ActiveRecord::Base.store_full_sti_class = store_full_sti_class
+ assert_equal @tagging, Tagging.find_by(taggable: post)
end
end
+
+class PolymorphicFullStiClassNamesTest < ActiveRecord::TestCase
+ include PolymorphicFullStiClassNamesSharedTest
+
+ private
+ def store_full_sti_class
+ true
+ end
+end
+
+class PolymorphicNonFullStiClassNamesTest < ActiveRecord::TestCase
+ include PolymorphicFullStiClassNamesSharedTest
+
+ private
+ def store_full_sti_class
+ false
+ end
+end
diff --git a/activerecord/test/cases/associations/eager_load_nested_include_test.rb b/activerecord/test/cases/associations/eager_load_nested_include_test.rb
index e9f551b6b2..c5b2b77bd4 100644
--- a/activerecord/test/cases/associations/eager_load_nested_include_test.rb
+++ b/activerecord/test/cases/associations/eager_load_nested_include_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/post"
require "models/tag"
diff --git a/activerecord/test/cases/associations/eager_singularization_test.rb b/activerecord/test/cases/associations/eager_singularization_test.rb
index 16eff15026..420a5a805b 100644
--- a/activerecord/test/cases/associations/eager_singularization_test.rb
+++ b/activerecord/test/cases/associations/eager_singularization_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
class EagerSingularizationTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index 11f4aae5b3..a1fba8dc66 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/post"
require "models/tagging"
@@ -38,6 +40,12 @@ class EagerAssociationTest < ActiveRecord::TestCase
assert_nil member.favourite_club
end
+ def test_should_work_inverse_of_with_eager_load
+ author = authors(:david)
+ assert_same author, author.posts.first.author
+ assert_same author, author.posts.eager_load(:comments).first.author
+ end
+
def test_loading_with_one_association
posts = Post.all.merge!(includes: :comments).to_a
post = posts.find { |p| p.id == 1 }
@@ -68,6 +76,71 @@ class EagerAssociationTest < ActiveRecord::TestCase
"expected to find only david's posts"
end
+ def test_loading_with_scope_including_joins
+ member = Member.first
+ assert_equal members(:groucho), member
+ assert_equal clubs(:boring_club), member.general_club
+
+ member = Member.preload(:general_club).first
+ assert_equal members(:groucho), member
+ assert_equal clubs(:boring_club), member.general_club
+
+ member = Member.eager_load(:general_club).first
+ assert_equal members(:groucho), member
+ assert_equal clubs(:boring_club), member.general_club
+ end
+
+ def test_loading_association_with_same_table_joins
+ super_memberships = [memberships(:super_membership_of_boring_club)]
+
+ member = Member.joins(:favourite_memberships).first
+ assert_equal members(:groucho), member
+ assert_equal super_memberships, member.super_memberships
+
+ member = Member.joins(:favourite_memberships).preload(:super_memberships).first
+ assert_equal members(:groucho), member
+ assert_equal super_memberships, member.super_memberships
+
+ member = Member.joins(:favourite_memberships).eager_load(:super_memberships).first
+ assert_equal members(:groucho), member
+ assert_equal super_memberships, member.super_memberships
+ end
+
+ def test_loading_association_with_intersection_joins
+ member = Member.joins(:current_membership).first
+ assert_equal members(:groucho), member
+ assert_equal clubs(:boring_club), member.club
+ assert_equal memberships(:membership_of_boring_club), member.current_membership
+
+ member = Member.joins(:current_membership).preload(:club, :current_membership).first
+ assert_equal members(:groucho), member
+ assert_equal clubs(:boring_club), member.club
+ assert_equal memberships(:membership_of_boring_club), member.current_membership
+
+ member = Member.joins(:current_membership).eager_load(:club, :current_membership).first
+ assert_equal members(:groucho), member
+ assert_equal clubs(:boring_club), member.club
+ assert_equal memberships(:membership_of_boring_club), member.current_membership
+ end
+
+ def test_loading_associations_dont_leak_instance_state
+ assertions = ->(firm) {
+ assert_equal companies(:first_firm), firm
+
+ assert_predicate firm.association(:readonly_account), :loaded?
+ assert_predicate firm.association(:accounts), :loaded?
+
+ assert_equal accounts(:signals37), firm.readonly_account
+ assert_equal [accounts(:signals37)], firm.accounts
+
+ assert_predicate firm.readonly_account, :readonly?
+ assert firm.accounts.none?(&:readonly?)
+ }
+
+ assertions.call(Firm.preload(:readonly_account, :accounts).first)
+ assertions.call(Firm.eager_load(:readonly_account, :accounts).first)
+ end
+
def test_with_ordering
list = Post.all.merge!(includes: :comments, order: "posts.id DESC").to_a
[:other_by_mary, :other_by_bob, :misc_by_mary, :misc_by_bob, :eager_other,
@@ -271,10 +344,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
def test_loading_from_an_association_that_has_a_hash_of_conditions
- assert_nothing_raised do
- Author.all.merge!(includes: :hello_posts_with_hash_conditions).to_a
- end
- assert !Author.all.merge!(includes: :hello_posts_with_hash_conditions).find(authors(:david).id).hello_posts.empty?
+ assert_not_empty Author.all.merge!(includes: :hello_posts_with_hash_conditions).find(authors(:david).id).hello_posts
end
def test_loading_with_no_associations
@@ -417,7 +487,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_eager_association_loading_with_belongs_to_and_order_string_with_quoted_table_name
quoted_posts_id = Comment.connection.quote_table_name("posts") + "." + Comment.connection.quote_column_name("id")
assert_nothing_raised do
- Comment.includes(:post).references(:posts).order(quoted_posts_id)
+ Comment.includes(:post).references(:posts).order(Arel.sql(quoted_posts_id))
end
end
@@ -520,6 +590,14 @@ class EagerAssociationTest < ActiveRecord::TestCase
assert_equal [comments(:does_it_hurt)], assert_no_queries { author.special_post_comments }
end
+ def test_preloading_has_many_through_with_implicit_source
+ authors = Author.includes(:very_special_comments).to_a
+ assert_no_queries do
+ special_comment_authors = authors.map { |author| [author.name, author.very_special_comments.size] }
+ assert_equal [["David", 1], ["Mary", 0], ["Bob", 0]], special_comment_authors
+ end
+ end
+
def test_eager_with_has_many_through_an_sti_join_model_with_conditions_on_both
author = Author.all.merge!(includes: :special_nonexistent_post_comments, order: "authors.id").first
assert_equal [], author.special_nonexistent_post_comments
@@ -769,7 +847,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
Tagging.create!(taggable_type: "Post", taggable_id: post2.id, tag: tag)
tag_with_includes = OrderedTag.includes(:tagged_posts).find(tag.id)
- assert_equal(tag_with_includes.taggings.map(&:taggable).map(&:title), tag_with_includes.tagged_posts.map(&:title))
+ assert_equal tag_with_includes.ordered_taggings.map(&:taggable).map(&:title), tag_with_includes.tagged_posts.map(&:title)
end
def test_eager_has_many_through_multiple_with_order
@@ -851,23 +929,19 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
end
- def find_all_ordered(className, include = nil)
- className.all.merge!(order: "#{className.table_name}.#{className.primary_key}", includes: include).to_a
- end
-
def test_limited_eager_with_order
assert_equal(
posts(:thinking, :sti_comments),
Post.all.merge!(
includes: [:author, :comments], where: { "authors.name" => "David" },
- order: "UPPER(posts.title)", limit: 2, offset: 1
+ order: Arel.sql("UPPER(posts.title)"), limit: 2, offset: 1
).to_a
)
assert_equal(
posts(:sti_post_and_comments, :sti_comments),
Post.all.merge!(
includes: [:author, :comments], where: { "authors.name" => "David" },
- order: "UPPER(posts.title) DESC", limit: 2, offset: 1
+ order: Arel.sql("UPPER(posts.title) DESC"), limit: 2, offset: 1
).to_a
)
end
@@ -877,14 +951,14 @@ class EagerAssociationTest < ActiveRecord::TestCase
posts(:thinking, :sti_comments),
Post.all.merge!(
includes: [:author, :comments], where: { "authors.name" => "David" },
- order: ["UPPER(posts.title)", "posts.id"], limit: 2, offset: 1
+ order: [Arel.sql("UPPER(posts.title)"), "posts.id"], limit: 2, offset: 1
).to_a
)
assert_equal(
posts(:sti_post_and_comments, :sti_comments),
Post.all.merge!(
includes: [:author, :comments], where: { "authors.name" => "David" },
- order: ["UPPER(posts.title) DESC", "posts.id"], limit: 2, offset: 1
+ order: [Arel.sql("UPPER(posts.title) DESC"), "posts.id"], limit: 2, offset: 1
).to_a
)
end
@@ -1059,7 +1133,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
def test_load_with_sti_sharing_association
- assert_queries(2) do #should not do 1 query per subclass
+ assert_queries(2) do # should not do 1 query per subclass
Comment.includes(:post).to_a
end
end
@@ -1094,12 +1168,6 @@ class EagerAssociationTest < ActiveRecord::TestCase
assert_equal authors(:david), assert_no_queries { posts[0].author }
posts = assert_queries(2) do
- Post.all.merge!(select: "distinct posts.*", includes: :author, joins: [:comments], where: "comments.body like 'Thank you%'", order: "posts.id").to_a
- end
- assert_equal [posts(:welcome)], posts
- assert_equal authors(:david), assert_no_queries { posts[0].author }
-
- posts = assert_queries(2) do
Post.all.merge!(includes: :author, joins: { taggings: :tag }, where: "tags.name = 'General'", order: "posts.id").to_a
end
assert_equal posts(:welcome, :thinking), posts
@@ -1210,6 +1278,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
client = assert_queries(2) { Client.preload(:firm).find(c.id) }
assert_no_queries { assert_nil client.firm }
+ assert_equal c.client_of, client.client_of
end
def test_preloading_empty_belongs_to_polymorphic
@@ -1217,6 +1286,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
tagging = assert_queries(2) { Tagging.preload(:taggable).find(t.id) }
assert_no_queries { assert_nil tagging.taggable }
+ assert_equal t.taggable_id, tagging.taggable_id
end
def test_preloading_through_empty_belongs_to
@@ -1295,6 +1365,11 @@ class EagerAssociationTest < ActiveRecord::TestCase
assert_equal projects.last.mentor.developers.first.contracts, projects.last.developers.last.contracts
end
+ def test_preloading_has_many_through_with_custom_scope
+ project = Project.includes(:developers_named_david_with_hash_conditions).find(projects(:active_record).id)
+ assert_equal [developers(:david)], project.developers_named_david_with_hash_conditions
+ end
+
test "scoping with a circular preload" do
assert_equal Comment.find(1), Comment.preload(post: :comments).scoping { Comment.find(1) }
end
@@ -1363,6 +1438,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
assert_nothing_raised do
authors(:david).essays.includes(:writer).any?
authors(:david).essays.includes(:writer).exists?
+ authors(:david).essays.includes(:owner).where("name IS NOT NULL").exists?
end
end
@@ -1430,51 +1506,51 @@ class EagerAssociationTest < ActiveRecord::TestCase
test "preloading readonly association" do
# has-one
firm = Firm.where(id: "1").preload(:readonly_account).first!
- assert firm.readonly_account.readonly?
+ assert_predicate firm.readonly_account, :readonly?
# has_and_belongs_to_many
project = Project.where(id: "2").preload(:readonly_developers).first!
- assert project.readonly_developers.first.readonly?
+ assert_predicate project.readonly_developers.first, :readonly?
# has-many :through
david = Author.where(id: "1").preload(:readonly_comments).first!
- assert david.readonly_comments.first.readonly?
+ assert_predicate david.readonly_comments.first, :readonly?
end
test "eager-loading non-readonly association" do
# has_one
firm = Firm.where(id: "1").eager_load(:account).first!
- assert_not firm.account.readonly?
+ assert_not_predicate firm.account, :readonly?
# has_and_belongs_to_many
project = Project.where(id: "2").eager_load(:developers).first!
- assert_not project.developers.first.readonly?
+ assert_not_predicate project.developers.first, :readonly?
# has_many :through
david = Author.where(id: "1").eager_load(:comments).first!
- assert_not david.comments.first.readonly?
+ assert_not_predicate david.comments.first, :readonly?
# belongs_to
post = Post.where(id: "1").eager_load(:author).first!
- assert_not post.author.readonly?
+ assert_not_predicate post.author, :readonly?
end
test "eager-loading readonly association" do
# has-one
firm = Firm.where(id: "1").eager_load(:readonly_account).first!
- assert firm.readonly_account.readonly?
+ assert_predicate firm.readonly_account, :readonly?
# has_and_belongs_to_many
project = Project.where(id: "2").eager_load(:readonly_developers).first!
- assert project.readonly_developers.first.readonly?
+ assert_predicate project.readonly_developers.first, :readonly?
# has-many :through
david = Author.where(id: "1").eager_load(:readonly_comments).first!
- assert david.readonly_comments.first.readonly?
+ assert_predicate david.readonly_comments.first, :readonly?
# belongs_to
post = Post.where(id: "1").eager_load(:readonly_author).first!
- assert post.readonly_author.readonly?
+ assert_predicate post.readonly_author, :readonly?
end
test "preloading a polymorphic association with references to the associated table" do
@@ -1487,9 +1563,50 @@ class EagerAssociationTest < ActiveRecord::TestCase
assert_equal posts(:welcome), post
end
+ test "eager-loading with a polymorphic association won't work consistently" do
+ assert_raise(ActiveRecord::EagerLoadPolymorphicError) { authors(:david).essays.eager_load(:writer).to_a }
+ assert_raise(ActiveRecord::EagerLoadPolymorphicError) { authors(:david).essays.eager_load(:writer).count }
+ assert_raise(ActiveRecord::EagerLoadPolymorphicError) { authors(:david).essays.eager_load(:writer).exists? }
+ end
+
# CollectionProxy#reader is expensive, so the preloader avoids calling it.
test "preloading has_many_through association avoids calling association.reader" do
- ActiveRecord::Associations::HasManyAssociation.any_instance.expects(:reader).never
- Author.preload(:readonly_comments).first!
+ assert_not_called_on_instance_of(ActiveRecord::Associations::HasManyAssociation, :reader) do
+ Author.preload(:readonly_comments).first!
+ end
+ end
+
+ test "preloading through a polymorphic association doesn't require the association to exist" do
+ sponsors = []
+ assert_queries 5 do
+ sponsors = Sponsor.where(sponsorable_id: 1).preload(sponsorable: [:post, :membership]).to_a
+ end
+ # check the preload worked
+ assert_queries 0 do
+ sponsors.map(&:sponsorable).map { |s| s.respond_to?(:posts) ? s.post.author : s.membership }
+ end
+ end
+
+ test "preloading a regular association through a polymorphic association doesn't require the association to exist on all types" do
+ sponsors = []
+ assert_queries 6 do
+ sponsors = Sponsor.where(sponsorable_id: 1).preload(sponsorable: [{ post: :first_comment }, :membership]).to_a
+ end
+ # check the preload worked
+ assert_queries 0 do
+ sponsors.map(&:sponsorable).map { |s| s.respond_to?(:posts) ? s.post.author : s.membership }
+ end
end
+
+ test "preloading a regular association with a typo through a polymorphic association still raises" do
+ # this test contains an intentional typo of first -> fist
+ assert_raises(ActiveRecord::AssociationNotFoundError) do
+ Sponsor.where(sponsorable_id: 1).preload(sponsorable: [{ post: :fist_comment }, :membership]).to_a
+ end
+ end
+
+ private
+ def find_all_ordered(klass, include = nil)
+ klass.order("#{klass.table_name}.#{klass.primary_key}").includes(include).to_a
+ end
end
diff --git a/activerecord/test/cases/associations/extension_test.rb b/activerecord/test/cases/associations/extension_test.rb
index 974a3080d4..5eacb5a3d8 100644
--- a/activerecord/test/cases/associations/extension_test.rb
+++ b/activerecord/test/cases/associations/extension_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/post"
require "models/comment"
@@ -36,6 +38,11 @@ class AssociationsExtensionsTest < ActiveRecord::TestCase
assert_equal comments(:greetings), posts(:welcome).comments.not_again.find_most_recent
end
+ def test_extension_with_dirty_target
+ comment = posts(:welcome).comments.build(body: "New comment")
+ assert_equal comment, posts(:welcome).comments.with_content("New comment")
+ end
+
def test_marshalling_extensions
david = developers(:david)
assert_equal projects(:action_controller), david.projects.find_most_recent
@@ -73,6 +80,12 @@ class AssociationsExtensionsTest < ActiveRecord::TestCase
assert_equal post.association(:comments), post.comments.where("1=1").the_association
end
+ def test_association_with_default_scope
+ assert_raises OopsError do
+ posts(:welcome).comments.destroy_all
+ end
+ end
+
private
def extend!(model)
diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
index d6b595d7e7..482302055d 100644
--- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/developer"
require "models/computer"
@@ -86,12 +88,6 @@ class DeveloperWithSymbolClassName < Developer
has_and_belongs_to_many :projects, class_name: :ProjectWithSymbolsForKeys
end
-ActiveSupport::Deprecation.silence do
- class DeveloperWithConstantClassName < Developer
- has_and_belongs_to_many :projects, class_name: ProjectWithSymbolsForKeys
- end
-end
-
class DeveloperWithExtendOption < Developer
module NamedExtension
def category
@@ -111,6 +107,21 @@ class ProjectUnscopingDavidDefaultScope < ActiveRecord::Base
association_foreign_key: "developer_id"
end
+class Kitchen < ActiveRecord::Base
+ has_one :sink
+end
+
+class Sink < ActiveRecord::Base
+ has_and_belongs_to_many :sources, join_table: :edges
+ belongs_to :kitchen
+ accepts_nested_attributes_for :kitchen
+end
+
+class Source < ActiveRecord::Base
+ self.table_name = "men"
+ has_and_belongs_to_many :sinks, join_table: :edges
+end
+
class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
fixtures :accounts, :companies, :categories, :posts, :categories_posts, :developers, :projects, :developers_projects,
:parrots, :pirates, :parrots_pirates, :treasures, :price_estimates, :tags, :taggings, :computers
@@ -169,11 +180,11 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
def test_has_and_belongs_to_many
david = Developer.find(1)
- assert !david.projects.empty?
+ assert_not_empty david.projects
assert_equal 2, david.projects.size
active_record = Project.find(1)
- assert !active_record.developers.empty?
+ assert_not_empty active_record.developers
assert_equal 3, active_record.developers.size
assert_includes active_record.developers, david
end
@@ -251,10 +262,10 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
no_of_projects = Project.count
aredridel = Developer.new("name" => "Aredridel")
aredridel.projects.concat([Project.find(1), p = Project.new("name" => "Projekt")])
- assert !aredridel.persisted?
- assert !p.persisted?
+ assert_not_predicate aredridel, :persisted?
+ assert_not_predicate p, :persisted?
assert aredridel.save
- assert aredridel.persisted?
+ assert_predicate aredridel, :persisted?
assert_equal no_of_devels + 1, Developer.count
assert_equal no_of_projects + 1, Project.count
assert_equal 2, aredridel.projects.size
@@ -300,14 +311,14 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
def test_build
devel = Developer.find(1)
proj = assert_no_queries(ignore_none: false) { devel.projects.build("name" => "Projekt") }
- assert !devel.projects.loaded?
+ assert_not_predicate devel.projects, :loaded?
assert_equal devel.projects.last, proj
- assert devel.projects.loaded?
+ assert_predicate devel.projects, :loaded?
- assert !proj.persisted?
+ assert_not_predicate proj, :persisted?
devel.save
- assert proj.persisted?
+ assert_predicate proj, :persisted?
assert_equal devel.projects.last, proj
assert_equal Developer.find(1).projects.sort_by(&:id).last, proj # prove join table is updated
end
@@ -315,14 +326,14 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
def test_new_aliased_to_build
devel = Developer.find(1)
proj = assert_no_queries(ignore_none: false) { devel.projects.new("name" => "Projekt") }
- assert !devel.projects.loaded?
+ assert_not_predicate devel.projects, :loaded?
assert_equal devel.projects.last, proj
- assert devel.projects.loaded?
+ assert_predicate devel.projects, :loaded?
- assert !proj.persisted?
+ assert_not_predicate proj, :persisted?
devel.save
- assert proj.persisted?
+ assert_predicate proj, :persisted?
assert_equal devel.projects.last, proj
assert_equal Developer.find(1).projects.sort_by(&:id).last, proj # prove join table is updated
end
@@ -332,10 +343,10 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
devel.projects.build(name: "Make bed")
proj2 = devel.projects.build(name: "Lie in it")
assert_equal devel.projects.last, proj2
- assert !proj2.persisted?
+ assert_not_predicate proj2, :persisted?
devel.save
- assert devel.persisted?
- assert proj2.persisted?
+ assert_predicate devel, :persisted?
+ assert_predicate proj2, :persisted?
assert_equal devel.projects.last, proj2
assert_equal Developer.find_by_name("Marcel").projects.last, proj2 # prove join table is updated
end
@@ -343,40 +354,27 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
def test_create
devel = Developer.find(1)
proj = devel.projects.create("name" => "Projekt")
- assert !devel.projects.loaded?
+ assert_not_predicate devel.projects, :loaded?
assert_equal devel.projects.last, proj
- assert !devel.projects.loaded?
+ assert_not_predicate devel.projects, :loaded?
- assert proj.persisted?
+ assert_predicate proj, :persisted?
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)
- devel.projects.build(name: "Make bed")
- proj2 = devel.projects.build(name: "Lie in it")
- assert_equal devel.projects.last, proj2
- assert !proj2.persisted?
- devel.save
- assert devel.persisted?
- assert proj2.persisted?
- assert_equal devel.projects.last, proj2
- assert_equal Developer.find_by_name("Marcel").projects.last, proj2 # prove join table is updated
- end
-
def test_creation_respects_hash_condition
# in Oracle '' is saved as null therefore need to save ' ' in not null column
post = categories(:general).post_with_conditions.build(body: " ")
- assert post.save
- assert_equal "Yet Another Testing Title", post.title
+ assert post.save
+ assert_equal "Yet Another Testing Title", post.title
# in Oracle '' is saved as null therefore need to save ' ' in not null column
another_post = categories(:general).post_with_conditions.create(body: " ")
- assert another_post.persisted?
- assert_equal "Yet Another Testing Title", another_post.title
+ assert_predicate another_post, :persisted?
+ assert_equal "Yet Another Testing Title", another_post.title
end
def test_distinct_after_the_fact
@@ -443,10 +441,10 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
def test_removing_associations_on_destroy
david = DeveloperWithBeforeDestroyRaise.find(1)
- assert !david.projects.empty?
+ assert_not_empty david.projects
david.destroy
- assert david.projects.empty?
- assert DeveloperWithBeforeDestroyRaise.connection.select_all("SELECT * FROM developers_projects WHERE developer_id = 1").empty?
+ assert_empty david.projects
+ assert_empty DeveloperWithBeforeDestroyRaise.connection.select_all("SELECT * FROM developers_projects WHERE developer_id = 1")
end
def test_destroying
@@ -461,7 +459,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
end
join_records = Developer.connection.select_all("SELECT * FROM developers_projects WHERE developer_id = #{david.id} AND project_id = #{project.id}")
- assert join_records.empty?
+ assert_empty join_records
assert_equal 1, david.reload.projects.size
assert_equal 1, david.projects.reload.size
@@ -477,7 +475,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
end
join_records = Developer.connection.select_all("SELECT * FROM developers_projects WHERE developer_id = #{david.id}")
- assert join_records.empty?
+ assert_empty join_records
assert_equal 0, david.reload.projects.size
assert_equal 0, david.projects.reload.size
@@ -486,23 +484,23 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
def test_destroy_all
david = Developer.find(1)
david.projects.reload
- assert !david.projects.empty?
+ assert_not_empty david.projects
assert_no_difference "Project.count" do
david.projects.destroy_all
end
join_records = Developer.connection.select_all("SELECT * FROM developers_projects WHERE developer_id = #{david.id}")
- assert join_records.empty?
+ assert_empty join_records
- assert david.projects.empty?
- assert david.projects.reload.empty?
+ assert_empty david.projects
+ assert_empty david.projects.reload
end
def test_destroy_associations_destroys_multiple_associations
george = parrots(:george)
- assert !george.pirates.empty?
- assert !george.treasures.empty?
+ assert_not_empty george.pirates
+ assert_not_empty george.treasures
assert_no_difference "Pirate.count" do
assert_no_difference "Treasure.count" do
@@ -511,12 +509,12 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
end
join_records = Parrot.connection.select_all("SELECT * FROM parrots_pirates WHERE parrot_id = #{george.id}")
- assert join_records.empty?
- assert george.pirates.reload.empty?
+ assert_empty join_records
+ assert_empty george.pirates.reload
join_records = Parrot.connection.select_all("SELECT * FROM parrots_treasures WHERE parrot_id = #{george.id}")
- assert join_records.empty?
- assert george.treasures.reload.empty?
+ assert_empty join_records
+ assert_empty george.treasures.reload
end
def test_associations_with_conditions
@@ -549,7 +547,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
developer = project.developers.first
assert_no_queries(ignore_none: false) do
- assert project.developers.loaded?
+ assert_predicate project.developers, :loaded?
assert_includes project.developers, developer
end
end
@@ -559,19 +557,19 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
developer = project.developers.first
project.reload
- assert ! project.developers.loaded?
+ assert_not_predicate project.developers, :loaded?
assert_queries(1) do
assert_includes project.developers, developer
end
- assert ! project.developers.loaded?
+ assert_not_predicate project.developers, :loaded?
end
def test_include_returns_false_for_non_matching_record_to_verify_scoping
project = projects(:active_record)
developer = Developer.create name: "Bryan", salary: 50_000
- assert ! project.developers.loaded?
- assert ! project.developers.include?(developer)
+ assert_not_predicate project.developers, :loaded?
+ assert_not project.developers.include?(developer)
end
def test_find_with_merged_options
@@ -664,7 +662,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert_includes developer.sym_special_projects, sp
end
- def test_update_attributes_after_push_without_duplicate_join_table_rows
+ def test_update_columns_after_push_without_duplicate_join_table_rows
developer = Developer.new("name" => "Kano")
project = SpecialProject.create("name" => "Special Project")
assert developer.save
@@ -699,24 +697,13 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
end
def test_join_table_alias
- # FIXME: `references` has no impact on the aliases generated for the join
- # query. The fact that we pass `:developers_projects_join` to `references`
- # and that the SQL string contains `developers_projects_join` is merely a
- # coincidence.
assert_equal(
3,
- Developer.references(:developers_projects_join).merge(
- includes: { projects: :developers },
- where: "projects_developers_projects_join.joined_on IS NOT NULL"
- ).to_a.size
+ Developer.includes(projects: :developers).where.not("projects_developers_projects_join.joined_on": nil).to_a.size
)
end
def test_join_with_group
- # FIXME: `references` has no impact on the aliases generated for the join
- # query. The fact that we pass `:developers_projects_join` to `references`
- # and that the SQL string contains `developers_projects_join` is merely a
- # coincidence.
group = Developer.columns.inject([]) do |g, c|
g << "developers.#{c.name}"
g << "developers_projects_2.#{c.name}"
@@ -725,10 +712,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert_equal(
3,
- Developer.references(:developers_projects_join).merge(
- includes: { projects: :developers }, where: "projects_developers_projects_join.joined_on IS NOT NULL",
- group: group.join(",")
- ).to_a.size
+ Developer.includes(projects: :developers).where.not("projects_developers_projects_join.joined_on": nil).group(group.join(",")).to_a.size
)
end
@@ -765,9 +749,9 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
def test_get_ids_for_unloaded_associations_does_not_load_them
developer = developers(:david)
- assert !developer.projects.loaded?
+ assert_not_predicate developer.projects, :loaded?
assert_equal projects(:active_record, :action_controller).map(&:id).sort, developer.project_ids.sort
- assert !developer.projects.loaded?
+ assert_not_predicate developer.projects, :loaded?
end
def test_assign_ids
@@ -797,7 +781,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
end
def test_has_many_through_polymorphic_has_manys_works
- assert_equal [10, 20].to_set, pirates(:redbeard).treasure_estimates.map(&:price).to_set
+ assert_equal ["$10.00", "$20.00"].to_set, pirates(:redbeard).treasure_estimates.map(&:price).to_set
end
def test_symbols_as_keys
@@ -939,7 +923,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert_not_nil Developer._reflections["shared_computers"]
# Checking the fixture for named association is important here, because it's the only way
# we've been able to reproduce this bug
- assert_not_nil File.read(File.expand_path("../../../fixtures/developers.yml", __FILE__)).index("shared_computers")
+ assert_not_nil File.read(File.expand_path("../../fixtures/developers.yml", __dir__)).index("shared_computers")
assert_equal developers(:david).shared_computers.first, computers(:laptop)
end
@@ -950,13 +934,6 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
end
end
- def test_with_constant_class_name
- assert_nothing_raised do
- developer = DeveloperWithConstantClassName.new
- developer.projects
- end
- end
-
def test_alternate_database
professor = Professor.create(name: "Plum")
course = Course.create(name: "Forensics")
@@ -1021,4 +998,9 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
ActiveRecord::Base.partial_writes = original_partial_writes
end
end
+
+ def test_has_and_belongs_to_many_with_belongs_to
+ sink = Sink.create! kitchen: Kitchen.new, sources: [Source.new]
+ assert_equal 1, sink.sources.count
+ end
end
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index ede3a44090..a2f6174dc1 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/developer"
require "models/computer"
@@ -25,7 +27,6 @@ require "models/categorization"
require "models/minivan"
require "models/speedometer"
require "models/reference"
-require "models/job"
require "models/college"
require "models/student"
require "models/pirate"
@@ -40,7 +41,7 @@ require "models/zine"
require "models/interest"
class HasManyAssociationsTestForReorderWithJoinDependency < ActiveRecord::TestCase
- fixtures :authors, :posts, :comments
+ fixtures :authors, :author_addresses, :posts, :comments
def test_should_generate_valid_sql
author = authors(:david)
@@ -51,33 +52,44 @@ class HasManyAssociationsTestForReorderWithJoinDependency < ActiveRecord::TestCa
end
class HasManyAssociationsTestPrimaryKeys < ActiveRecord::TestCase
- fixtures :authors, :essays, :subscribers, :subscriptions, :people
+ fixtures :authors, :author_addresses, :essays, :subscribers, :subscriptions, :people
def test_custom_primary_key_on_new_record_should_fetch_with_query
subscriber = Subscriber.new(nick: "webster132")
- assert !subscriber.subscriptions.loaded?
+ assert_not_predicate subscriber.subscriptions, :loaded?
assert_queries 1 do
assert_equal 2, subscriber.subscriptions.size
end
- assert_equal subscriber.subscriptions, Subscription.where(subscriber_id: "webster132")
+ assert_equal Subscription.where(subscriber_id: "webster132"), subscriber.subscriptions
end
def test_association_primary_key_on_new_record_should_fetch_with_query
author = Author.new(name: "David")
- assert !author.essays.loaded?
+ assert_not_predicate author.essays, :loaded?
assert_queries 1 do
assert_equal 1, author.essays.size
end
- assert_equal author.essays, Essay.where(writer_id: "David")
+ assert_equal Essay.where(writer_id: "David"), author.essays
end
def test_has_many_custom_primary_key
david = authors(:david)
- assert_equal david.essays, Essay.where(writer_id: "David")
+ assert_equal Essay.where(writer_id: "David"), david.essays
+ end
+
+ def test_ids_on_unloaded_association_with_custom_primary_key
+ david = people(:david)
+ assert_equal Essay.where(writer_id: "David").pluck(:id), david.essay_ids
+ end
+
+ def test_ids_on_loaded_association_with_custom_primary_key
+ david = people(:david)
+ david.essays.load
+ assert_equal Essay.where(writer_id: "David").pluck(:id), david.essay_ids
end
def test_has_many_assignment_with_custom_primary_key
@@ -90,7 +102,7 @@ class HasManyAssociationsTestPrimaryKeys < ActiveRecord::TestCase
def test_blank_custom_primary_key_on_new_record_should_not_run_queries
author = Author.new
- assert !author.essays.loaded?
+ assert_not_predicate author.essays, :loaded?
assert_queries 0 do
assert_equal 0, author.essays.size
@@ -100,8 +112,8 @@ end
class HasManyAssociationsTest < ActiveRecord::TestCase
fixtures :accounts, :categories, :companies, :developers, :projects,
- :developers_projects, :topics, :authors, :comments,
- :posts, :readers, :taggings, :cars, :jobs, :tags,
+ :developers_projects, :topics, :authors, :author_addresses, :comments,
+ :posts, :readers, :taggings, :cars, :tags,
:categorizations, :zines, :interests
def setup
@@ -188,7 +200,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
part.reload
assert_nil part.ship
- assert !part.updated_at_changed?
+ assert_not_predicate part, :updated_at_changed?
end
def test_create_from_association_should_respect_default_scope
@@ -241,6 +253,13 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal "defaulty", bulb.name
end
+ def test_build_from_association_sets_inverse_instance
+ car = Car.new(name: "honda")
+
+ bulb = car.bulbs.build
+ assert_equal car, bulb.car
+ end
+
def test_do_not_call_callbacks_for_delete_all
car = Car.create(name: "honda")
car.funky_bulbs.create!
@@ -357,6 +376,27 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal invoice.id, line_item.invoice_id
end
+ class SpecialAuthor < ActiveRecord::Base
+ self.table_name = "authors"
+ has_many :books, class_name: "SpecialBook", foreign_key: :author_id
+ end
+
+ class SpecialBook < ActiveRecord::Base
+ self.table_name = "books"
+
+ belongs_to :author
+ enum read_status: { unread: 0, reading: 2, read: 3, forgotten: nil }
+ end
+
+ def test_association_enum_works_properly
+ author = SpecialAuthor.create!(name: "Test")
+ book = SpecialBook.create!(read_status: "reading")
+ author.books << book
+
+ assert_equal "reading", book.read_status
+ assert_not_equal 0, SpecialAuthor.joins(:books).where(books: { read_status: "reading" }).count
+ end
+
# When creating objects on the association, we must not do it within a scope (even though it
# would be convenient), because this would cause that scope to be applied to any callbacks etc.
def test_build_and_create_should_not_happen_within_scope
@@ -424,7 +464,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
new_clients << company.clients_of_firm.build(name: "Another Client III")
end
- assert_not company.clients_of_firm.loaded?
+ assert_not_predicate company.clients_of_firm, :loaded?
assert_queries(1) do
assert_same new_clients[0], company.clients_of_firm.third
assert_same new_clients[1], company.clients_of_firm.fourth
@@ -444,7 +484,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
new_clients << company.clients_of_firm.build(name: "Another Client III")
end
- assert_not company.clients_of_firm.loaded?
+ assert_not_predicate company.clients_of_firm, :loaded?
assert_queries(1) do
assert_same new_clients[0], company.clients_of_firm.third!
assert_same new_clients[1], company.clients_of_firm.fourth!
@@ -456,7 +496,10 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_create_resets_cached_counters
+ Reader.delete_all
+
person = Person.create!(first_name: "tenderlove")
+
post = Post.first
assert_equal [], person.readers
@@ -474,8 +517,6 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
person = Person.new
person.first_name = "Naruto"
person.references << Reference.new
- person.id = 10
- person.references
person.save!
assert_equal 1, person.references.update_all(favourite: true)
end
@@ -484,27 +525,24 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
person = Person.new
person.first_name = "Sasuke"
person.references << Reference.new
- person.id = 10
- person.references
person.save!
assert_predicate person.references, :exists?
end
- # sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first
def test_counting_with_counter_sql
- assert_equal 3, Firm.all.merge!(order: "id").first.clients.count
+ assert_equal 3, Firm.first.clients.count
end
def test_counting
- assert_equal 3, Firm.all.merge!(order: "id").first.plain_clients.count
+ assert_equal 3, Firm.first.plain_clients.count
end
def test_counting_with_single_hash
- assert_equal 1, Firm.all.merge!(order: "id").first.plain_clients.where(name: "Microsoft").count
+ assert_equal 1, Firm.first.plain_clients.where(name: "Microsoft").count
end
def test_counting_with_column_name_and_hash
- assert_equal 3, Firm.all.merge!(order: "id").first.plain_clients.count(:name)
+ assert_equal 3, Firm.first.plain_clients.count(:name)
end
def test_counting_with_association_limit
@@ -514,7 +552,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_finding
- assert_equal 3, Firm.all.merge!(order: "id").first.clients.length
+ assert_equal 3, Firm.first.clients.length
end
def test_finding_array_compatibility
@@ -554,17 +592,25 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_taking_with_a_number
+ klass = Class.new(Author) do
+ has_many :posts, -> { order(:id) }
+
+ def self.name
+ "Author"
+ end
+ end
+
# taking from unloaded Relation
- bob = Author.find(authors(:bob).id)
+ bob = klass.find(authors(:bob).id)
new_post = bob.posts.build
- assert_not bob.posts.loaded?
+ assert_not_predicate bob.posts, :loaded?
assert_equal [posts(:misc_by_bob)], bob.posts.take(1)
assert_equal [posts(:misc_by_bob), posts(:other_by_bob)], bob.posts.take(2)
assert_equal [posts(:misc_by_bob), posts(:other_by_bob), new_post], bob.posts.take(3)
# taking from loaded Relation
bob.posts.load
- assert bob.posts.loaded?
+ assert_predicate bob.posts, :loaded?
assert_equal [posts(:misc_by_bob)], bob.posts.take(1)
assert_equal [posts(:misc_by_bob), posts(:other_by_bob)], bob.posts.take(2)
assert_equal [posts(:misc_by_bob), posts(:other_by_bob), new_post], bob.posts.take(3)
@@ -586,27 +632,27 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_finding_default_orders
- assert_equal "Summit", Firm.all.merge!(order: "id").first.clients.first.name
+ assert_equal "Summit", Firm.first.clients.first.name
end
def test_finding_with_different_class_name_and_order
- assert_equal "Apex", Firm.all.merge!(order: "id").first.clients_sorted_desc.first.name
+ assert_equal "Apex", Firm.first.clients_sorted_desc.first.name
end
def test_finding_with_foreign_key
- assert_equal "Microsoft", Firm.all.merge!(order: "id").first.clients_of_firm.first.name
+ assert_equal "Microsoft", Firm.first.clients_of_firm.first.name
end
def test_finding_with_condition
- assert_equal "Microsoft", Firm.all.merge!(order: "id").first.clients_like_ms.first.name
+ assert_equal "Microsoft", Firm.first.clients_like_ms.first.name
end
def test_finding_with_condition_hash
- assert_equal "Microsoft", Firm.all.merge!(order: "id").first.clients_like_ms_with_hash_conditions.first.name
+ assert_equal "Microsoft", Firm.first.clients_like_ms_with_hash_conditions.first.name
end
def test_finding_using_primary_key
- assert_equal "Summit", Firm.all.merge!(order: "id").first.clients_using_primary_key.first.name
+ assert_equal "Summit", Firm.first.clients_using_primary_key.first.name
end
def test_update_all_on_association_accessed_before_save
@@ -629,7 +675,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_find_ids
- firm = Firm.all.merge!(order: "id").first
+ firm = Firm.first
assert_raise(ActiveRecord::RecordNotFound) { firm.clients.find }
@@ -649,7 +695,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_find_one_message_on_primary_key
- firm = Firm.all.merge!(order: "id").first
+ firm = Firm.first
e = assert_raises(ActiveRecord::RecordNotFound) do
firm.clients.find(0)
@@ -675,7 +721,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_find_all
- firm = Firm.all.merge!(order: "id").first
+ firm = Firm.first
assert_equal 3, firm.clients.where("#{QUOTED_TYPE} = 'Client'").to_a.length
assert_equal 1, firm.clients.where("name = 'Summit'").to_a.length
end
@@ -683,13 +729,13 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_find_each
firm = companies(:first_firm)
- assert ! firm.clients.loaded?
+ assert_not_predicate firm.clients, :loaded?
assert_queries(4) do
firm.clients.find_each(batch_size: 1) { |c| assert_equal firm.id, c.firm_id }
end
- assert ! firm.clients.loaded?
+ assert_not_predicate firm.clients, :loaded?
end
def test_find_each_with_conditions
@@ -702,13 +748,13 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
end
- assert ! firm.clients.loaded?
+ assert_not_predicate firm.clients, :loaded?
end
def test_find_in_batches
firm = companies(:first_firm)
- assert ! firm.clients.loaded?
+ assert_not_predicate firm.clients, :loaded?
assert_queries(2) do
firm.clients.find_in_batches(batch_size: 2) do |clients|
@@ -716,29 +762,63 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
end
- assert ! firm.clients.loaded?
+ assert_not_predicate firm.clients, :loaded?
end
def test_find_all_sanitized
- # sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first
- firm = Firm.all.merge!(order: "id").first
+ firm = Firm.first
summit = firm.clients.where("name = 'Summit'").to_a
assert_equal summit, firm.clients.where("name = ?", "Summit").to_a
assert_equal summit, firm.clients.where("name = :name", name: "Summit").to_a
end
def test_find_first
- firm = Firm.all.merge!(order: "id").first
+ firm = Firm.first
client2 = Client.find(2)
assert_equal firm.clients.first, firm.clients.order("id").first
assert_equal client2, firm.clients.where("#{QUOTED_TYPE} = 'Client'").order("id").first
end
def test_find_first_sanitized
- firm = Firm.all.merge!(order: "id").first
+ firm = Firm.first
client2 = Client.find(2)
- assert_equal client2, firm.clients.merge!(where: ["#{QUOTED_TYPE} = ?", "Client"], order: "id").first
- assert_equal client2, firm.clients.merge!(where: ["#{QUOTED_TYPE} = :type", { type: "Client" }], order: "id").first
+ assert_equal client2, firm.clients.where("#{QUOTED_TYPE} = ?", "Client").first
+ assert_equal client2, firm.clients.where("#{QUOTED_TYPE} = :type", type: "Client").first
+ end
+
+ def test_find_first_after_reset_scope
+ firm = Firm.first
+ collection = firm.clients
+
+ original_object = collection.first
+ assert_same original_object, collection.first, "Expected second call to #first to cache the same object"
+
+ # It should return a different object, since the association has been reloaded
+ assert_not_same original_object, firm.clients.first, "Expected #first to return a new object"
+ end
+
+ def test_find_first_after_reset
+ firm = Firm.first
+ collection = firm.clients
+
+ original_object = collection.first
+ assert_same original_object, collection.first, "Expected second call to #first to cache the same object"
+ collection.reset
+
+ # It should return a different object, since the association has been reloaded
+ assert_not_same original_object, collection.first, "Expected #first after #reset to return a new object"
+ end
+
+ def test_find_first_after_reload
+ firm = Firm.first
+ collection = firm.clients
+
+ original_object = collection.first
+ assert_same original_object, collection.first, "Expected second call to #first to cache the same object"
+ collection.reload
+
+ # It should return a different object, since the association has been reloaded
+ assert_not_same original_object, collection.first, "Expected #first after #reload to return a new object"
end
def test_find_all_with_include_and_conditions
@@ -767,7 +847,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_find_scoped_grouped_having
- assert_equal 1, authors(:david).popular_grouped_posts.length
+ assert_equal 2, authors(:david).popular_grouped_posts.length
assert_equal 0, authors(:mary).popular_grouped_posts.length
end
@@ -783,6 +863,12 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal [1], posts(:welcome).comments.select { |c| c.id == 1 }.map(&:id)
end
+ def test_select_with_block_and_dirty_target
+ assert_equal 2, posts(:welcome).comments.select { true }.size
+ posts(:welcome).comments.build
+ assert_equal 3, posts(:welcome).comments.select { true }.size
+ end
+
def test_select_without_foreign_key
assert_equal companies(:first_firm).accounts.first.credit_limit, companies(:first_firm).accounts.select(:credit_limit).first.credit_limit
end
@@ -827,7 +913,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_create_with_bang_on_has_many_raises_when_record_not_saved
assert_raise(ActiveRecord::RecordInvalid) do
- firm = Firm.all.merge!(order: "id").first
+ firm = Firm.first
firm.plain_clients.create!
end
end
@@ -885,20 +971,20 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_new_aliased_to_build
company = companies(:first_firm)
new_client = assert_no_queries(ignore_none: false) { company.clients_of_firm.new("name" => "Another Client") }
- assert !company.clients_of_firm.loaded?
+ assert_not_predicate company.clients_of_firm, :loaded?
assert_equal "Another Client", new_client.name
- assert !new_client.persisted?
+ assert_not_predicate new_client, :persisted?
assert_equal new_client, company.clients_of_firm.last
end
def test_build
company = companies(:first_firm)
new_client = assert_no_queries(ignore_none: false) { company.clients_of_firm.build("name" => "Another Client") }
- assert !company.clients_of_firm.loaded?
+ assert_not_predicate company.clients_of_firm, :loaded?
assert_equal "Another Client", new_client.name
- assert !new_client.persisted?
+ assert_not_predicate new_client, :persisted?
assert_equal new_client, company.clients_of_firm.last
end
@@ -912,9 +998,29 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_collection_not_empty_after_building
company = companies(:first_firm)
- assert_predicate company.contracts, :empty?
+ assert_empty company.contracts
company.contracts.build
- assert_not_predicate company.contracts, :empty?
+ assert_not_empty company.contracts
+ end
+
+ def test_collection_size_with_dirty_target
+ post = posts(:thinking)
+ assert_equal [], post.reader_ids
+ assert_equal 0, post.readers.size
+ post.readers.reset
+ post.readers.build
+ assert_equal [nil], post.reader_ids
+ assert_equal 1, post.readers.size
+ end
+
+ def test_collection_empty_with_dirty_target
+ post = posts(:thinking)
+ assert_equal [], post.reader_ids
+ assert_empty post.readers
+ post.readers.reset
+ post.readers.build
+ assert_equal [nil], post.reader_ids
+ assert_not_empty post.readers
end
def test_collection_size_twice_for_regressions
@@ -938,7 +1044,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_build_followed_by_save_does_not_load_target
companies(:first_firm).clients_of_firm.build("name" => "Another Client")
assert companies(:first_firm).save
- assert !companies(:first_firm).clients_of_firm.loaded?
+ assert_not_predicate companies(:first_firm).clients_of_firm, :loaded?
end
def test_build_without_loading_association
@@ -958,10 +1064,10 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_build_via_block
company = companies(:first_firm)
new_client = assert_no_queries(ignore_none: false) { company.clients_of_firm.build { |client| client.name = "Another Client" } }
- assert !company.clients_of_firm.loaded?
+ assert_not_predicate company.clients_of_firm, :loaded?
assert_equal "Another Client", new_client.name
- assert !new_client.persisted?
+ assert_not_predicate new_client, :persisted?
assert_equal new_client, company.clients_of_firm.last
end
@@ -999,7 +1105,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_predicate companies(:first_firm).clients_of_firm, :loaded?
new_client = companies(:first_firm).clients_of_firm.create("name" => "Another Client")
- assert new_client.persisted?
+ assert_predicate new_client, :persisted?
assert_equal new_client, companies(:first_firm).clients_of_firm.last
assert_equal new_client, companies(:first_firm).clients_of_firm.reload.last
end
@@ -1012,7 +1118,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_create_followed_by_save_does_not_load_target
companies(:first_firm).clients_of_firm.create("name" => "Another Client")
assert companies(:first_firm).save
- assert !companies(:first_firm).clients_of_firm.loaded?
+ assert_not_predicate companies(:first_firm).clients_of_firm, :loaded?
end
def test_deleting
@@ -1038,7 +1144,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
# option is not given on the association.
ship = Ship.create(name: "Countless", treasures_count: 10)
- assert_not Ship.reflect_on_association(:treasures).has_cached_counter?
+ assert_not_predicate Ship.reflect_on_association(:treasures), :has_cached_counter?
# Count should come from sql count() of treasures rather than treasures_count attribute
assert_equal ship.treasures.size, 0
@@ -1129,7 +1235,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_calling_empty_with_counter_cache
post = posts(:welcome)
assert_queries(0) do
- assert_not post.comments.empty?
+ assert_not_empty post.comments
end
end
@@ -1141,20 +1247,20 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
end
- def test_calling_update_attributes_on_id_changes_the_counter_cache
+ def test_calling_update_on_id_changes_the_counter_cache
topic = Topic.order("id ASC").first
original_count = topic.replies.to_a.size
assert_equal original_count, topic.replies_count
first_reply = topic.replies.first
- first_reply.update_attributes(parent_id: nil)
+ first_reply.update(parent_id: nil)
assert_equal original_count - 1, topic.reload.replies_count
- first_reply.update_attributes(parent_id: topic.id)
+ first_reply.update(parent_id: topic.id)
assert_equal original_count, topic.reload.replies_count
end
- def test_calling_update_attributes_changing_ids_doesnt_change_counter_cache
+ def test_calling_update_changing_ids_doesnt_change_counter_cache
topic1 = Topic.find(1)
topic2 = Topic.find(3)
original_count1 = topic1.replies.to_a.size
@@ -1163,11 +1269,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
reply1 = topic1.replies.first
reply2 = topic2.replies.first
- reply1.update_attributes(parent_id: topic2.id)
+ reply1.update(parent_id: topic2.id)
assert_equal original_count1 - 1, topic1.reload.replies_count
assert_equal original_count2 + 1, topic2.reload.replies_count
- reply2.update_attributes(parent_id: topic1.id)
+ reply2.update(parent_id: topic1.id)
assert_equal original_count1, topic1.reload.replies_count
assert_equal original_count2, topic2.reload.replies_count
end
@@ -1349,7 +1455,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
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.where(client_of: firm.id).size
- assert_equal 1, firm.dependent_sanitized_conditional_clients_of_firm.size
+ assert_equal 1, firm.dependent_hash_conditional_clients_of_firm.size
firm.destroy
# only the correctly associated client should have been deleted
assert_equal 1, Client.where(client_of: firm.id).size
@@ -1371,13 +1477,13 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_creation_respects_hash_condition
ms_client = companies(:first_firm).clients_like_ms_with_hash_conditions.build
- assert ms_client.save
- assert_equal "Microsoft", ms_client.name
+ assert ms_client.save
+ assert_equal "Microsoft", ms_client.name
another_ms_client = companies(:first_firm).clients_like_ms_with_hash_conditions.create
- assert another_ms_client.persisted?
- assert_equal "Microsoft", another_ms_client.name
+ assert_predicate another_ms_client, :persisted?
+ assert_equal "Microsoft", another_ms_client.name
end
def test_clearing_without_initial_access
@@ -1488,7 +1594,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_predicate companies(:first_firm).clients_of_firm, :loaded?
clients = companies(:first_firm).clients_of_firm.to_a
- assert !clients.empty?, "37signals has clients after load"
+ assert_not clients.empty?, "37signals has clients after load"
destroyed = companies(:first_firm).clients_of_firm.destroy_all
assert_equal clients.sort_by(&:id), destroyed.sort_by(&:id)
assert destroyed.all?(&:frozen?), "destroyed clients should be frozen"
@@ -1500,7 +1606,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
firm = companies(:first_firm)
assert_equal 3, firm.clients.size
firm.destroy
- assert Client.all.merge!(where: "firm_id=#{firm.id}").to_a.empty?
+ assert_empty Client.all.merge!(where: "firm_id=#{firm.id}").to_a
end
def test_dependence_for_associations_with_hash_condition
@@ -1509,8 +1615,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_destroy_dependent_when_deleted_from_association
- # sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first
- firm = Firm.all.merge!(order: "id").first
+ firm = Firm.first
assert_equal 3, firm.clients.size
client = firm.clients.first
@@ -1564,7 +1669,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
firm = RestrictedWithExceptionFirm.create!(name: "restrict")
firm.companies.create(name: "child")
- assert !firm.companies.empty?
+ assert_not_empty firm.companies
assert_raise(ActiveRecord::DeleteRestrictionError) { firm.destroy }
assert RestrictedWithExceptionFirm.exists?(name: "restrict")
assert firm.companies.exists?(name: "child")
@@ -1574,11 +1679,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
firm = RestrictedWithErrorFirm.create!(name: "restrict")
firm.companies.create(name: "child")
- assert !firm.companies.empty?
+ assert_not_empty firm.companies
firm.destroy
- assert !firm.errors.empty?
+ assert_not_empty firm.errors
assert_equal "Cannot delete record because dependent companies exist", firm.errors[:base].first
assert RestrictedWithErrorFirm.exists?(name: "restrict")
@@ -1591,11 +1696,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
firm = RestrictedWithErrorFirm.create!(name: "restrict")
firm.companies.create(name: "child")
- assert !firm.companies.empty?
+ assert_not_empty firm.companies
firm.destroy
- assert !firm.errors.empty?
+ assert_not_empty firm.errors
assert_equal "Cannot delete record because dependent client companies exist", firm.errors[:base].first
assert RestrictedWithErrorFirm.exists?(name: "restrict")
@@ -1620,7 +1725,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_replace_with_less
- firm = Firm.all.merge!(order: "id").first
+ firm = Firm.first
firm.clients = [companies(:first_client)]
assert firm.save, "Could not save firm"
firm.reload
@@ -1634,7 +1739,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_replace_with_new
- firm = Firm.all.merge!(order: "id").first
+ firm = Firm.first
firm.clients = [companies(:second_client), Client.new("name" => "New Client")]
firm.save
firm.reload
@@ -1647,8 +1752,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
account = Account.new
orig_accounts = firm.accounts.to_a
- assert !account.valid?
- assert !orig_accounts.empty?
+ assert_not_predicate account, :valid?
+ assert_not_empty orig_accounts
error = assert_raise ActiveRecord::RecordNotSaved do
firm.accounts = [account]
end
@@ -1706,9 +1811,9 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_get_ids_for_unloaded_associations_does_not_load_them
company = companies(:first_firm)
- assert !company.clients.loaded?
+ assert_not_predicate company.clients, :loaded?
assert_equal [companies(:first_client).id, companies(:second_client).id, companies(:another_first_firm_client).id], company.client_ids
- assert !company.clients.loaded?
+ assert_not_predicate company.clients, :loaded?
end
def test_counter_cache_on_unloaded_association
@@ -1773,6 +1878,13 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
].each { |block| assert_raise(ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection, &block) }
end
+ def test_associations_order_should_be_priority_over_throughs_order
+ david = authors(:david)
+ expected = [12, 10, 9, 8, 7, 6, 5, 3, 2, 1]
+ assert_equal expected, david.comments_desc.map(&:id)
+ assert_equal expected, Author.includes(:comments_desc).find(david.id).comments_desc.map(&:id)
+ end
+
def test_dynamic_find_should_respect_association_order_for_through
assert_equal Comment.find(10), authors(:david).comments_desc.where("comments.type = 'SpecialComment'").first
assert_equal Comment.find(10), authors(:david).comments_desc.find_by_type("SpecialComment")
@@ -1790,7 +1902,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
client = firm.clients.first
assert_no_queries do
- assert firm.clients.loaded?
+ assert_predicate firm.clients, :loaded?
assert_equal true, firm.clients.include?(client)
end
end
@@ -1800,18 +1912,18 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
client = firm.clients.first
firm.reload
- assert ! firm.clients.loaded?
+ assert_not_predicate firm.clients, :loaded?
assert_queries(1) do
assert_equal true, firm.clients.include?(client)
end
- assert ! firm.clients.loaded?
+ assert_not_predicate firm.clients, :loaded?
end
def test_include_returns_false_for_non_matching_record_to_verify_scoping
firm = companies(:first_firm)
client = Client.create!(name: "Not Associated")
- assert ! firm.clients.loaded?
+ assert_not_predicate firm.clients, :loaded?
assert_equal false, firm.clients.include?(client)
end
@@ -1820,13 +1932,13 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
firm.clients.first
firm.clients.second
firm.clients.last
- assert !firm.clients.loaded?
+ assert_not_predicate firm.clients, :loaded?
end
def test_calling_first_or_last_on_loaded_association_should_not_fetch_with_query
firm = companies(:first_firm)
firm.clients.load_target
- assert firm.clients.loaded?
+ assert_predicate firm.clients, :loaded?
assert_no_queries(ignore_none: false) do
firm.clients.first
@@ -1839,7 +1951,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_calling_first_or_last_on_existing_record_with_build_should_load_association
firm = companies(:first_firm)
firm.clients.build(name: "Foo")
- assert !firm.clients.loaded?
+ assert_not_predicate firm.clients, :loaded?
assert_queries 1 do
firm.clients.first
@@ -1847,13 +1959,13 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
firm.clients.last
end
- assert firm.clients.loaded?
+ assert_predicate firm.clients, :loaded?
end
def test_calling_first_nth_or_last_on_existing_record_with_create_should_not_load_association
firm = companies(:first_firm)
firm.clients.create(name: "Foo")
- assert !firm.clients.loaded?
+ assert_not_predicate firm.clients, :loaded?
assert_queries 3 do
firm.clients.first
@@ -1861,7 +1973,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
firm.clients.last
end
- assert !firm.clients.loaded?
+ assert_not_predicate firm.clients, :loaded?
end
def test_calling_first_nth_or_last_on_new_record_should_not_run_queries
@@ -1877,14 +1989,14 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_calling_first_or_last_with_integer_on_association_should_not_load_association
firm = companies(:first_firm)
firm.clients.create(name: "Foo")
- assert !firm.clients.loaded?
+ assert_not_predicate firm.clients, :loaded?
assert_queries 2 do
firm.clients.first(2)
firm.clients.last(2)
end
- assert !firm.clients.loaded?
+ assert_not_predicate firm.clients, :loaded?
end
def test_calling_many_should_count_instead_of_loading_association
@@ -1892,37 +2004,38 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_queries(1) do
firm.clients.many? # use count query
end
- assert !firm.clients.loaded?
+ assert_not_predicate firm.clients, :loaded?
end
def test_calling_many_on_loaded_association_should_not_use_query
firm = companies(:first_firm)
- firm.clients.collect # force load
+ firm.clients.load # force load
assert_no_queries { assert firm.clients.many? }
end
def test_calling_many_should_defer_to_collection_if_using_a_block
firm = companies(:first_firm)
assert_queries(1) do
- firm.clients.expects(:size).never
- firm.clients.many? { true }
+ assert_not_called(firm.clients, :size) do
+ firm.clients.many? { true }
+ end
end
- assert firm.clients.loaded?
+ assert_predicate firm.clients, :loaded?
end
def test_calling_many_should_return_false_if_none_or_one
firm = companies(:another_firm)
- assert !firm.clients_like_ms.many?
+ assert_not_predicate firm.clients_like_ms, :many?
assert_equal 0, firm.clients_like_ms.size
firm = companies(:first_firm)
- assert !firm.limited_clients.many?
+ assert_not_predicate firm.limited_clients, :many?
assert_equal 1, firm.limited_clients.size
end
def test_calling_many_should_return_true_if_more_than_one
firm = companies(:first_firm)
- assert firm.clients.many?
+ assert_predicate firm.clients, :many?
assert_equal 3, firm.clients.size
end
@@ -1931,33 +2044,34 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_queries(1) do
firm.clients.none? # use count query
end
- assert !firm.clients.loaded?
+ assert_not_predicate firm.clients, :loaded?
end
def test_calling_none_on_loaded_association_should_not_use_query
firm = companies(:first_firm)
- firm.clients.collect # force load
- assert_no_queries { assert ! firm.clients.none? }
+ firm.clients.load # force load
+ assert_no_queries { assert_not firm.clients.none? }
end
def test_calling_none_should_defer_to_collection_if_using_a_block
firm = companies(:first_firm)
assert_queries(1) do
- firm.clients.expects(:size).never
- firm.clients.none? { true }
+ assert_not_called(firm.clients, :size) do
+ firm.clients.none? { true }
+ end
end
- assert firm.clients.loaded?
+ assert_predicate firm.clients, :loaded?
end
def test_calling_none_should_return_true_if_none
firm = companies(:another_firm)
- assert firm.clients_like_ms.none?
+ assert_predicate firm.clients_like_ms, :none?
assert_equal 0, firm.clients_like_ms.size
end
def test_calling_none_should_return_false_if_any
firm = companies(:first_firm)
- assert !firm.limited_clients.none?
+ assert_not_predicate firm.limited_clients, :none?
assert_equal 1, firm.limited_clients.size
end
@@ -1966,39 +2080,40 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_queries(1) do
firm.clients.one? # use count query
end
- assert !firm.clients.loaded?
+ assert_not_predicate firm.clients, :loaded?
end
def test_calling_one_on_loaded_association_should_not_use_query
firm = companies(:first_firm)
- firm.clients.collect # force load
- assert_no_queries { assert ! firm.clients.one? }
+ firm.clients.load # force load
+ assert_no_queries { assert_not firm.clients.one? }
end
def test_calling_one_should_defer_to_collection_if_using_a_block
firm = companies(:first_firm)
assert_queries(1) do
- firm.clients.expects(:size).never
- firm.clients.one? { true }
+ assert_not_called(firm.clients, :size) do
+ firm.clients.one? { true }
+ end
end
- assert firm.clients.loaded?
+ assert_predicate firm.clients, :loaded?
end
def test_calling_one_should_return_false_if_zero
firm = companies(:another_firm)
- assert ! firm.clients_like_ms.one?
+ assert_not_predicate firm.clients_like_ms, :one?
assert_equal 0, firm.clients_like_ms.size
end
def test_calling_one_should_return_true_if_one
firm = companies(:first_firm)
- assert firm.limited_clients.one?
+ assert_predicate firm.limited_clients, :one?
assert_equal 1, firm.limited_clients.size
end
def test_calling_one_should_return_false_if_more_than_one
firm = companies(:first_firm)
- assert ! firm.clients.one?
+ assert_not_predicate firm.clients, :one?
assert_equal 3, firm.clients.size
end
@@ -2020,9 +2135,10 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_association_proxy_transaction_method_starts_transaction_in_association_class
- Comment.expects(:transaction)
- Post.first.comments.transaction do
- # nothing
+ assert_called(Comment, :transaction) do
+ Post.first.comments.transaction do
+ # nothing
+ end
end
end
@@ -2031,34 +2147,36 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal client_association.new.attributes, client_association.send(:new).attributes
end
- def test_respond_to_private_class_methods
- client_association = companies(:first_firm).clients
- assert !client_association.respond_to?(:private_method)
- assert client_association.respond_to?(:private_method, true)
- end
-
def test_creating_using_primary_key
- firm = Firm.all.merge!(order: "id").first
+ firm = Firm.first
client = firm.clients_using_primary_key.create!(name: "test")
assert_equal firm.name, client.firm_name
end
def test_defining_has_many_association_with_delete_all_dependency_lazily_evaluates_target_class
- ActiveRecord::Reflection::AssociationReflection.any_instance.expects(:class_name).never
- class_eval(<<-EOF, __FILE__, __LINE__ + 1)
- class DeleteAllModel < ActiveRecord::Base
- has_many :nonentities, :dependent => :delete_all
- end
- EOF
+ assert_not_called_on_instance_of(
+ ActiveRecord::Reflection::AssociationReflection,
+ :class_name,
+ ) do
+ class_eval(<<-EOF, __FILE__, __LINE__ + 1)
+ class DeleteAllModel < ActiveRecord::Base
+ has_many :nonentities, :dependent => :delete_all
+ end
+ EOF
+ end
end
def test_defining_has_many_association_with_nullify_dependency_lazily_evaluates_target_class
- ActiveRecord::Reflection::AssociationReflection.any_instance.expects(:class_name).never
- class_eval(<<-EOF, __FILE__, __LINE__ + 1)
- class NullifyModel < ActiveRecord::Base
- has_many :nonentities, :dependent => :nullify
- end
- EOF
+ assert_not_called_on_instance_of(
+ ActiveRecord::Reflection::AssociationReflection,
+ :class_name,
+ ) do
+ class_eval(<<-EOF, __FILE__, __LINE__ + 1)
+ class NullifyModel < ActiveRecord::Base
+ has_many :nonentities, :dependent => :nullify
+ end
+ EOF
+ end
end
def test_attributes_are_being_set_when_initialized_from_has_many_association_with_where_clause
@@ -2140,6 +2258,13 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal "Post", tagging.taggable_type
end
+ def test_build_from_polymorphic_association_sets_inverse_instance
+ post = Post.new
+ tagging = post.taggings.build
+
+ assert_equal post, tagging.taggable
+ end
+
def test_dont_call_save_callbacks_twice_on_has_many
firm = companies(:first_firm)
contract = firm.contracts.create!
@@ -2217,7 +2342,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
post = posts(:welcome)
assert post.taggings_with_delete_all.count > 0
- assert !post.taggings_with_delete_all.loaded?
+ assert_not_predicate post.taggings_with_delete_all, :loaded?
# 2 queries: one DELETE and another to update the counter cache
assert_queries(2) do
@@ -2239,7 +2364,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
test "collection proxy respects default scope" do
author = authors(:mary)
- assert !author.first_posts.exists?
+ assert_not_predicate author.first_posts, :exists?
end
test "association with extend option" do
@@ -2251,7 +2376,15 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
test "association with extend option with multiple extensions" do
post = posts(:welcome)
assert_equal "lifo", post.comments_with_extend_2.author
- assert_equal "hello", post.comments_with_extend_2.greeting
+ assert_equal "hullo", post.comments_with_extend_2.greeting
+ end
+
+ test "extend option affects per association" do
+ post = posts(:welcome)
+ assert_equal "lifo", post.comments_with_extend.author
+ assert_equal "lifo", post.comments_with_extend_2.author
+ assert_equal "hello", post.comments_with_extend.greeting
+ assert_equal "hullo", post.comments_with_extend_2.greeting
end
test "delete record with complex joins" do
@@ -2272,7 +2405,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
test "does not duplicate associations when used with natural primary keys" do
speedometer = Speedometer.create!(id: "4")
- speedometer.minivans.create!(minivan_id: "a-van-red" , name: "a van", color: "red")
+ speedometer.minivans.create!(minivan_id: "a-van-red", name: "a van", color: "red")
assert_equal 1, speedometer.minivans.to_a.size, "Only one association should be present:\n#{speedometer.minivans.to_a}"
assert_equal 1, speedometer.reload.minivans.to_a.size
@@ -2311,8 +2444,9 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
car = Car.create!
bulb = Bulb.create! name: "other", car: car
- assert_equal bulb, Car.find(car.id).all_bulbs.first
- assert_equal bulb, Car.includes(:all_bulbs).find(car.id).all_bulbs.first
+ assert_equal [bulb], Car.find(car.id).all_bulbs
+ assert_equal [bulb], Car.includes(:all_bulbs).find(car.id).all_bulbs
+ assert_equal [bulb], Car.eager_load(:all_bulbs).find(car.id).all_bulbs
end
test "raises RecordNotDestroyed when replaced child can't be destroyed" do
@@ -2349,7 +2483,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
pirate = FamousPirate.new
pirate.famous_ships << ship = FamousShip.new
- assert pirate.valid?
+ assert_predicate pirate, :valid?
assert_not pirate.valid?(:conference)
assert_equal "can't be blank", ship.errors[:name].first
end
@@ -2430,6 +2564,15 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_same car, new_bulb.car
end
+ test "reattach to new objects replaces inverse association and foreign key" do
+ bulb = Bulb.create!(car: Car.create!)
+ assert bulb.car_id
+ car = Car.new
+ car.bulbs << bulb
+ assert_equal car, bulb.car
+ assert_nil bulb.car_id
+ end
+
test "in memory replacement maintains order" do
first_bulb = Bulb.create!
second_bulb = Bulb.create!
@@ -2441,15 +2584,101 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal [first_bulb, second_bulb], car.bulbs
end
- test "double insertion of new object to association when same association used in the after create callback of a new object" do
+ test "association size calculation works with default scoped selects when not previously fetched" do
+ firm = Firm.create!(name: "Firm")
+ 5.times { firm.developers_with_select << Developer.create!(name: "Developer") }
+
+ same_firm = Firm.find(firm.id)
+ assert_equal 5, same_firm.developers_with_select.size
+ end
+
+ test "prevent double insertion of new object when the parent association loaded in the after save callback" do
reset_callbacks(:save, Bulb) do
Bulb.after_save { |record| record.car.bulbs.load }
+
car = Car.create!
car.bulbs << Bulb.new
+
assert_equal 1, car.bulbs.size
end
end
+ test "prevent double firing the before save callback of new object when the parent association saved in the callback" do
+ reset_callbacks(:save, Bulb) do
+ count = 0
+ Bulb.before_save { |record| record.car.save && count += 1 }
+
+ car = Car.create!
+ car.bulbs.create!
+
+ assert_equal 1, count
+ end
+ end
+
+ test "calling size on an association that has not been loaded performs a query" do
+ car = Car.create!
+ Bulb.create(car_id: car.id)
+
+ car_two = Car.create!
+
+ assert_queries(1) do
+ assert_equal 1, car.bulbs.size
+ end
+
+ assert_queries(1) do
+ assert_equal 0, car_two.bulbs.size
+ end
+ end
+
+ test "calling size on an association that has been loaded does not perform query" do
+ car = Car.create!
+ Bulb.create(car_id: car.id)
+ car.bulb_ids
+
+ car_two = Car.create!
+ car_two.bulb_ids
+
+ assert_no_queries do
+ assert_equal 1, car.bulbs.size
+ end
+
+ assert_no_queries do
+ assert_equal 0, car_two.bulbs.size
+ end
+ end
+
+ test "calling empty on an association that has not been loaded performs a query" do
+ car = Car.create!
+ Bulb.create(car_id: car.id)
+
+ car_two = Car.create!
+
+ assert_queries(1) do
+ assert_not_empty car.bulbs
+ end
+
+ assert_queries(1) do
+ assert_empty car_two.bulbs
+ end
+ end
+
+ test "calling empty on an association that has been loaded does not performs query" do
+ car = Car.create!
+ Bulb.create(car_id: car.id)
+ car.bulb_ids
+
+ car_two = Car.create!
+ car_two.bulb_ids
+
+ assert_no_queries do
+ assert_not_empty car.bulbs
+ end
+
+ assert_no_queries do
+ assert_empty car_two.bulbs
+ end
+ end
+
class AuthorWithErrorDestroyingAssociation < ActiveRecord::Base
self.table_name = "authors"
has_many :posts_with_error_destroying,
@@ -2486,11 +2715,16 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal [bulb.id], car.bulb_ids
assert_no_queries { car.bulb_ids }
+
+ bulb2 = car.bulbs.create!
+
+ assert_equal [bulb.id, bulb2.id], car.bulb_ids
+ assert_no_queries { car.bulb_ids }
end
def test_loading_association_in_validate_callback_doesnt_affect_persistence
reset_callbacks(:validation, Bulb) do
- Bulb.after_validation { |m| m.car.bulbs.load }
+ Bulb.after_validation { |record| record.car.bulbs.load }
car = Car.create!(name: "Car")
bulb = car.bulbs.create!
@@ -2499,6 +2733,17 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
end
+ def test_create_children_could_be_rolled_back_by_after_save
+ firm = Firm.create!(name: "A New Firm, Inc")
+ assert_no_difference "Client.count" do
+ client = firm.clients.create(name: "New Client") do |cli|
+ cli.rollback_on_save = true
+ assert_not cli.rollback_on_create_called
+ end
+ assert client.rollback_on_create_called
+ end
+ end
+
private
def force_signal37_to_load_all_clients_of_firm
diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb
index ea52fb5a67..d5573b6d02 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/post"
require "models/person"
@@ -44,6 +46,11 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
Reader.create person_id: 0, post_id: 0
end
+ def test_marshal_dump
+ preloaded = Post.includes(:first_blue_tags).first
+ assert_equal preloaded, Marshal.load(Marshal.dump(preloaded))
+ end
+
def test_preload_sti_rhs_class
developers = Developer.includes(:firms).all.to_a
assert_no_queries do
@@ -64,10 +71,6 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
club1.members.sort_by(&:id)
end
- def make_model(name)
- Class.new(ActiveRecord::Base) { define_singleton_method(:name) { name } }
- end
-
def test_ordered_has_many_through
person_prime = Class.new(ActiveRecord::Base) do
def self.name; "Person"; end
@@ -152,20 +155,6 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert after_destroy_called, "after destroy should be called"
end
- def make_no_pk_hm_t
- lesson = make_model "Lesson"
- student = make_model "Student"
-
- lesson_student = make_model "LessonStudent"
- lesson_student.table_name = "lessons_students"
-
- lesson_student.belongs_to :lesson, anonymous_class: lesson
- lesson_student.belongs_to :student, anonymous_class: student
- lesson.has_many :lesson_students, anonymous_class: lesson_student
- lesson.has_many :students, through: :lesson_students, anonymous_class: student
- [lesson, lesson_student, student]
- end
-
def test_pk_is_not_required_for_join
post = Post.includes(:scategories).first
post2 = Post.includes(:categories).first
@@ -337,6 +326,17 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert_includes post.single_people, person
end
+ def test_build_then_remove_then_save
+ post = posts(:thinking)
+ post.people.build(first_name: "Bob")
+ ted = post.people.build(first_name: "Ted")
+ post.people.delete(ted)
+ post.save!
+ post.reload
+
+ assert_equal ["Bob"], post.people.collect(&:first_name)
+ end
+
def test_both_parent_ids_set_when_saving_new
post = Post.new(title: "Hello", body: "world")
person = Person.new(first_name: "Sean")
@@ -358,10 +358,10 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
end
assert_queries(1) do
- assert posts(:welcome).people.empty?
+ assert_empty posts(:welcome).people
end
- assert posts(:welcome).reload.people.reload.empty?
+ assert_empty posts(:welcome).reload.people.reload
end
def test_destroy_association
@@ -371,8 +371,8 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
end
end
- assert posts(:welcome).reload.people.empty?
- assert posts(:welcome).people.reload.empty?
+ assert_empty posts(:welcome).reload.people
+ assert_empty posts(:welcome).people.reload
end
def test_destroy_all
@@ -382,8 +382,8 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
end
end
- assert posts(:welcome).reload.people.empty?
- assert posts(:welcome).people.reload.empty?
+ assert_empty posts(:welcome).reload.people
+ assert_empty posts(:welcome).people.reload
end
def test_should_raise_exception_for_destroying_mismatching_records
@@ -543,6 +543,16 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
end
end
+ def test_update_counter_caches_on_destroy_with_indestructible_through_record
+ post = posts(:welcome)
+ tag = post.indestructible_tags.create!(name: "doomed")
+ post.update_columns(indestructible_tags_count: post.indestructible_tags.count)
+
+ assert_no_difference "post.reload.indestructible_tags_count" do
+ posts(:welcome).indestructible_tags.destroy(tag)
+ end
+ end
+
def test_replace_association
assert_queries(4) { posts(:welcome);people(:david);people(:michael); posts(:welcome).people.reload }
@@ -680,10 +690,10 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
end
assert_queries(0) do
- assert posts(:welcome).people.empty?
+ assert_empty posts(:welcome).people
end
- assert posts(:welcome).reload.people.reload.empty?
+ assert_empty posts(:welcome).reload.people.reload
end
def test_association_callback_ordering
@@ -727,6 +737,18 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
[:added, :before, "Roger"],
[:added, :after, "Roger"]
], log.last(4)
+
+ post.people_with_callbacks.build { |person| person.first_name = "Ted" }
+ assert_equal [
+ [:added, :before, "Ted"],
+ [:added, :after, "Ted"]
+ ], log.last(2)
+
+ post.people_with_callbacks.create { |person| person.first_name = "Sam" }
+ assert_equal [
+ [:added, :before, "Sam"],
+ [:added, :after, "Sam"]
+ ], log.last(2)
end
def test_dynamic_find_should_respect_association_include
@@ -765,9 +787,9 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
def test_get_ids_for_unloaded_associations_does_not_load_them
person = people(:michael)
- assert !person.posts.loaded?
+ assert_not_predicate person.posts, :loaded?
assert_equal [posts(:welcome).id, posts(:authorless).id].sort, person.post_ids.sort
- assert !person.posts.loaded?
+ assert_not_predicate person.posts, :loaded?
end
def test_association_proxy_transaction_method_starts_transaction_in_association_class
@@ -822,7 +844,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
sarah = Person.create!(first_name: "Sarah", primary_contact_id: people(:susan).id, gender: "F", number1_fan_id: 1)
john = Person.create!(first_name: "John", primary_contact_id: sarah.id, gender: "M", number1_fan_id: 1)
assert_equal sarah.agents, [john]
- assert_equal people(:susan).agents.flat_map(&:agents), people(:susan).agents_of_agents
+ assert_equal people(:susan).agents.flat_map(&:agents).sort, people(:susan).agents_of_agents.sort
end
def test_associate_existing_with_nonstandard_primary_key_on_belongs_to
@@ -856,8 +878,8 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
author = authors(:mary)
category = author.named_categories.create(name: "Primary")
author.named_categories.delete(category)
- assert !Categorization.exists?(author_id: author.id, named_category_name: category.name)
- assert author.named_categories.reload.empty?
+ assert_not Categorization.exists?(author_id: author.id, named_category_name: category.name)
+ assert_empty author.named_categories.reload
end
def test_collection_singular_ids_getter_with_string_primary_keys
@@ -874,6 +896,14 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert_equal [dev], company.developers
end
+ def test_collection_singular_ids_setter_with_required_type_cast
+ company = companies(:rails_core)
+ dev = Developer.first
+
+ company.developer_ids = [dev.id.to_s]
+ assert_equal [dev], company.developers
+ end
+
def test_collection_singular_ids_setter_with_string_primary_keys
assert_nothing_raised do
book = books(:awdr)
@@ -885,32 +915,20 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
end
end
- def test_collection_singular_ids_setter_with_changed_primary_key
- company = companies(:first_firm)
- client = companies(:first_client)
- company.clients_using_primary_key_ids = [client.name]
- assert_equal [client], company.clients_using_primary_key
- end
-
def test_collection_singular_ids_setter_raises_exception_when_invalid_ids_set
company = companies(:rails_core)
ids = [Developer.first.id, -9999]
e = assert_raises(ActiveRecord::RecordNotFound) { company.developer_ids = ids }
- assert_match(/Couldn't find all Developers with 'id'/, e.message)
- end
-
- def test_collection_singular_ids_setter_raises_exception_when_invalid_ids_set_with_changed_primary_key
- company = companies(:first_firm)
- ids = [Client.first.name, "unknown client"]
- e = assert_raises(ActiveRecord::RecordNotFound) { company.clients_using_primary_key_ids = ids }
- assert_match(/Couldn't find all Clients with 'name'/, e.message)
+ msg = "Couldn't find all Developers with 'id': (1, -9999) (found 1 results, but was looking for 2). Couldn't find Developer with id -9999."
+ assert_equal(msg, e.message)
end
def test_collection_singular_ids_through_setter_raises_exception_when_invalid_ids_set
author = authors(:david)
ids = [categories(:general).name, "Unknown"]
e = assert_raises(ActiveRecord::RecordNotFound) { author.essay_category_ids = ids }
- assert_equal "Couldn't find all Categories with 'name': (General, Unknown) (found 1 results, but was looking for 2)", e.message
+ msg = "Couldn't find all Categories with 'name': (General, Unknown) (found 1 results, but was looking for 2). Couldn't find Category with name Unknown."
+ assert_equal msg, e.message
end
def test_build_a_model_from_hm_through_association_with_where_clause
@@ -943,8 +961,8 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
end
def test_through_association_readonly_should_be_false
- assert !people(:michael).posts.first.readonly?
- assert !people(:michael).posts.to_a.first.readonly?
+ assert_not_predicate people(:michael).posts.first, :readonly?
+ assert_not_predicate people(:michael).posts.to_a.first, :readonly?
end
def test_can_update_through_association
@@ -953,6 +971,13 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
end
end
+ def test_has_many_through_polymorphic_with_rewhere
+ post = TaggedPost.create!(title: "Tagged", body: "Post")
+ tag = post.tags.create!(name: "Tag")
+ assert_equal [tag], TaggedPost.preload(:tags).last.tags
+ assert_equal [tag], TaggedPost.eager_load(:tags).last.tags
+ end
+
def test_has_many_through_polymorphic_with_primary_key_option
assert_equal [categories(:general)], authors(:david).essay_categories
@@ -1026,12 +1051,12 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
post.author_categorizations
proxy = post.send(:association_instance_get, :author_categorizations)
- assert !proxy.stale_target?
+ assert_not_predicate proxy, :stale_target?
assert_equal authors(:mary).categorizations.sort_by(&:id), post.author_categorizations.sort_by(&:id)
post.author_id = authors(:david).id
- assert proxy.stale_target?
+ assert_predicate proxy, :stale_target?
assert_equal authors(:david).categorizations.sort_by(&:id), post.author_categorizations.sort_by(&:id)
end
@@ -1048,7 +1073,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert_includes post.author_addresses, address
post.author_addresses.delete(address)
- assert post[:author_count].nil?
+ assert_predicate post[:author_count], :nil?
end
def test_primary_key_option_on_source
@@ -1135,6 +1160,32 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert_equal ["parrot", "bulbul"], owner.toys.map { |r| r.pet.name }
end
+ def test_has_many_through_associations_sum_on_columns
+ post1 = Post.create(title: "active", body: "sample")
+ post2 = Post.create(title: "inactive", body: "sample")
+
+ person1 = Person.create(first_name: "aaron", followers_count: 1)
+ person2 = Person.create(first_name: "schmit", followers_count: 2)
+ person3 = Person.create(first_name: "bill", followers_count: 3)
+ person4 = Person.create(first_name: "cal", followers_count: 4)
+
+ Reader.create(post_id: post1.id, person_id: person1.id)
+ Reader.create(post_id: post1.id, person_id: person2.id)
+ Reader.create(post_id: post1.id, person_id: person3.id)
+ Reader.create(post_id: post1.id, person_id: person4.id)
+
+ Reader.create(post_id: post2.id, person_id: person1.id)
+ Reader.create(post_id: post2.id, person_id: person2.id)
+ Reader.create(post_id: post2.id, person_id: person3.id)
+ Reader.create(post_id: post2.id, person_id: person4.id)
+
+ active_persons = Person.joins(:readers).joins(:posts).distinct(true).where("posts.title" => "active")
+
+ assert_equal active_persons.map(&:followers_count).reduce(:+), 10
+ assert_equal active_persons.sum(:followers_count), 10
+ assert_equal active_persons.sum(:followers_count), active_persons.map(&:followers_count).reduce(:+)
+ end
+
def test_has_many_through_associations_on_new_records_use_null_relations
person = Person.new
@@ -1234,6 +1285,22 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
TenantMembership.current_member = nil
end
+ def test_has_many_through_with_scope_that_has_joined_same_table_with_parent_relation
+ assert_equal authors(:david), Author.joins(:comments_for_first_author).take
+ end
+
+ def test_has_many_through_with_left_joined_same_table_with_through_table
+ assert_equal [comments(:eager_other_comment1)], authors(:mary).comments.left_joins(:post)
+ end
+
+ def test_has_many_through_with_unscope_should_affect_to_through_scope
+ assert_equal [comments(:eager_other_comment1)], authors(:mary).unordered_comments
+ end
+
+ def test_has_many_through_with_scope_should_accept_string_and_hash_join
+ assert_equal authors(:david), Author.joins({ comments_for_first_author: :post }, "inner join posts posts_alias on authors.id = posts_alias.author_id").eager_load(:categories).take
+ end
+
def test_has_many_through_with_scope_should_respect_table_alias
family = Family.create!
users = 3.times.map { User.create! }
@@ -1245,6 +1312,25 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert_equal 0, users[2].family_members.to_a.size
end
+ def test_through_scope_is_affected_by_unscoping
+ author = authors(:david)
+
+ expected = author.comments.to_a
+ FirstPost.unscoped do
+ assert_equal expected.sort_by(&:id), author.comments_on_first_posts.sort_by(&:id)
+ end
+ end
+
+ def test_through_scope_isnt_affected_by_scoping
+ author = authors(:david)
+
+ expected = author.comments_on_first_posts.to_a
+ FirstPost.where(id: 2).scoping do
+ author.comments_on_first_posts.reset
+ assert_equal expected.sort_by(&:id), author.comments_on_first_posts.sort_by(&:id)
+ end
+ end
+
def test_incorrectly_ordered_through_associations
assert_raises(ActiveRecord::HasManyThroughOrderError) do
DeveloperWithIncorrectlyOrderedHasManyThrough.create(
@@ -1252,4 +1338,87 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
)
end
end
+
+ def test_has_many_through_update_ids_with_conditions
+ author = Author.create!(name: "Bill")
+ category = categories(:general)
+
+ author.update(
+ special_categories_with_condition_ids: [category.id],
+ nonspecial_categories_with_condition_ids: [category.id]
+ )
+
+ assert_equal [category.id], author.special_categories_with_condition_ids
+ assert_equal [category.id], author.nonspecial_categories_with_condition_ids
+
+ author.update(nonspecial_categories_with_condition_ids: [])
+ author.reload
+
+ assert_equal [category.id], author.special_categories_with_condition_ids
+ assert_equal [], author.nonspecial_categories_with_condition_ids
+ end
+
+ def test_single_has_many_through_association_with_unpersisted_parent_instance
+ post_with_single_has_many_through = Class.new(Post) do
+ def self.name; "PostWithSingleHasManyThrough"; end
+ has_many :subscriptions, through: :author
+ end
+ post = post_with_single_has_many_through.new
+
+ post.author = authors(:mary)
+ book1 = Book.create!(name: "essays on single has many through associations 1")
+ post.author.books << book1
+ subscription1 = Subscription.first
+ book1.subscriptions << subscription1
+ assert_equal [subscription1], post.subscriptions.to_a
+
+ post.author = authors(:bob)
+ book2 = Book.create!(name: "essays on single has many through associations 2")
+ post.author.books << book2
+ subscription2 = Subscription.second
+ book2.subscriptions << subscription2
+ assert_equal [subscription2], post.subscriptions.to_a
+ end
+
+ def test_nested_has_many_through_association_with_unpersisted_parent_instance
+ post_with_nested_has_many_through = Class.new(Post) do
+ def self.name; "PostWithNestedHasManyThrough"; end
+ has_many :books, through: :author
+ has_many :subscriptions, through: :books
+ end
+ post = post_with_nested_has_many_through.new
+
+ post.author = authors(:mary)
+ book1 = Book.create!(name: "essays on nested has many through associations 1")
+ post.author.books << book1
+ subscription1 = Subscription.first
+ book1.subscriptions << subscription1
+ assert_equal [subscription1], post.subscriptions.to_a
+
+ post.author = authors(:bob)
+ book2 = Book.create!(name: "essays on nested has many through associations 2")
+ post.author.books << book2
+ subscription2 = Subscription.second
+ book2.subscriptions << subscription2
+ assert_equal [subscription2], post.subscriptions.to_a
+ end
+
+ private
+ def make_model(name)
+ Class.new(ActiveRecord::Base) { define_singleton_method(:name) { name } }
+ end
+
+ def make_no_pk_hm_t
+ lesson = make_model "Lesson"
+ student = make_model "Student"
+
+ lesson_student = make_model "LessonStudent"
+ lesson_student.table_name = "lessons_students"
+
+ lesson_student.belongs_to :lesson, anonymous_class: lesson
+ lesson_student.belongs_to :student, anonymous_class: student
+ lesson.has_many :lesson_students, anonymous_class: lesson_student
+ lesson.has_many :students, through: :lesson_students, anonymous_class: student
+ [lesson, lesson_student, student]
+ end
end
diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb
index 7c11d2e7fc..9eea34d2b9 100644
--- a/activerecord/test/cases/associations/has_one_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_associations_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/developer"
require "models/computer"
@@ -13,7 +15,7 @@ require "models/post"
class HasOneAssociationsTest < ActiveRecord::TestCase
self.use_transactional_tests = false unless supports_savepoints?
- fixtures :accounts, :companies, :developers, :projects, :developers_projects, :ships, :pirates
+ fixtures :accounts, :companies, :developers, :projects, :developers_projects, :ships, :pirates, :authors, :author_addresses
def setup
Account.destroyed_account_ids.clear
@@ -28,7 +30,8 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
ActiveRecord::SQLCounter.clear_log
companies(:first_firm).account
ensure
- assert ActiveRecord::SQLCounter.log_all.all? { |sql| /order by/i !~ sql }, "ORDER BY was used in the query"
+ log_all = ActiveRecord::SQLCounter.log_all
+ assert log_all.all? { |sql| /order by/i !~ sql }, "ORDER BY was used in the query: #{log_all}"
end
def test_has_one_cache_nils
@@ -111,8 +114,8 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
developer = Developer.create!(name: "Someone")
ship = Ship.create!(name: "Planet Caravan", developer: developer)
ship.destroy
- assert !ship.persisted?
- assert !developer.persisted?
+ assert_not_predicate ship, :persisted?
+ assert_not_predicate developer, :persisted?
end
def test_natural_assignment_to_nil_after_destroy
@@ -183,7 +186,7 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
assert_raise(ActiveRecord::DeleteRestrictionError) { firm.destroy }
assert RestrictedWithExceptionFirm.exists?(name: "restrict")
- assert firm.account.present?
+ assert_predicate firm.account, :present?
end
def test_restrict_with_error
@@ -194,10 +197,10 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
firm.destroy
- assert !firm.errors.empty?
+ assert_not_empty firm.errors
assert_equal "Cannot delete record because a dependent account exists", firm.errors[:base].first
assert RestrictedWithErrorFirm.exists?(name: "restrict")
- assert firm.account.present?
+ assert_predicate firm.account, :present?
end
def test_restrict_with_error_with_locale
@@ -210,10 +213,10 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
firm.destroy
- assert !firm.errors.empty?
+ assert_not_empty firm.errors
assert_equal "Cannot delete record because a dependent firm account exists", firm.errors[:base].first
assert RestrictedWithErrorFirm.exists?(name: "restrict")
- assert firm.account.present?
+ assert_predicate firm.account, :present?
ensure
I18n.backend.reload!
end
@@ -307,6 +310,15 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
end
end
+ def test_create_when_parent_is_new_raises
+ firm = Firm.new
+ error = assert_raise(ActiveRecord::RecordNotSaved) do
+ firm.create_account
+ end
+
+ assert_equal "You cannot call create unless the parent is saved", error.message
+ end
+
def test_reload_association
odegy = companies(:odegy)
@@ -365,7 +377,7 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
def test_assignment_before_child_saved
firm = Firm.find(1)
firm.account = a = Account.new("credit_limit" => 1000)
- assert a.persisted?
+ assert_predicate a, :persisted?
assert_equal a, firm.account
assert_equal a, firm.account
firm.association(:account).reload
@@ -383,7 +395,7 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
def test_cant_save_readonly_association
assert_raise(ActiveRecord::ReadOnlyRecord) { companies(:first_firm).readonly_account.save! }
- assert companies(:first_firm).readonly_account.readonly?
+ assert_predicate companies(:first_firm).readonly_account, :readonly?
end
def test_has_one_proxy_should_not_respond_to_private_methods
@@ -421,7 +433,7 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
def test_create_respects_hash_condition
account = companies(:first_firm).create_account_limit_500_with_hash_conditions
- assert account.persisted?
+ assert_predicate account, :persisted?
assert_equal 500, account.credit_limit
end
@@ -438,9 +450,9 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
new_ship = pirate.create_ship
assert_not_equal ships(:black_pearl), new_ship
assert_equal new_ship, pirate.ship
- assert new_ship.new_record?
+ assert_predicate new_ship, :new_record?
assert_nil orig_ship.pirate_id
- assert !orig_ship.changed? # check it was saved
+ assert_not orig_ship.changed? # check it was saved
end
def test_creation_failure_with_dependent_option
@@ -448,8 +460,8 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
orig_ship = pirate.dependent_ship
new_ship = pirate.create_dependent_ship
- assert new_ship.new_record?
- assert orig_ship.destroyed?
+ assert_predicate new_ship, :new_record?
+ assert_predicate orig_ship, :destroyed?
end
def test_creation_failure_due_to_new_record_should_raise_error
@@ -469,7 +481,7 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
pirate = pirates(:blackbeard)
pirate.ship.name = nil
- assert !pirate.ship.valid?
+ assert_not_predicate pirate.ship, :valid?
error = assert_raise(ActiveRecord::RecordNotSaved) do
pirate.ship = ships(:interceptor)
end
@@ -576,7 +588,7 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
ship.save!
ship.name = "new name"
- assert ship.changed?
+ assert_predicate ship, :changed?
assert_queries(1) do
# One query for updating name, not triggering query for updating pirate_id
pirate.ship = ship
@@ -649,6 +661,8 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
self.table_name = "books"
belongs_to :author, class_name: "SpecialAuthor"
has_one :subscription, class_name: "SpecialSupscription", foreign_key: "subscriber_id"
+
+ enum status: [:proposed, :written, :published]
end
class SpecialAuthor < ActiveRecord::Base
@@ -666,7 +680,8 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
book = SpecialBook.create!(status: "published")
author.book = book
- refute_equal 0, SpecialAuthor.joins(:book).where(books: { status: "published" }).count
+ assert_equal "published", book.status
+ assert_not_equal 0, SpecialAuthor.joins(:book).where(books: { status: "published" }).count
end
def test_association_enum_works_properly_with_nested_join
@@ -679,4 +694,62 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
SpecialAuthor.joins(book: :subscription).where.not(where_clause)
end
end
+
+ class DestroyByParentBook < ActiveRecord::Base
+ self.table_name = "books"
+ belongs_to :author, class_name: "DestroyByParentAuthor"
+ before_destroy :dont, unless: :destroyed_by_association
+
+ def dont
+ throw(:abort)
+ end
+ end
+
+ class DestroyByParentAuthor < ActiveRecord::Base
+ self.table_name = "authors"
+ has_one :book, class_name: "DestroyByParentBook", foreign_key: "author_id", dependent: :destroy
+ end
+
+ test "destroyed_by_association set in child destroy callback on parent destroy" do
+ author = DestroyByParentAuthor.create!(name: "Test")
+ book = DestroyByParentBook.create!(author: author)
+
+ author.destroy
+
+ assert_not DestroyByParentBook.exists?(book.id)
+ end
+
+ test "destroyed_by_association set in child destroy callback on replace" do
+ author = DestroyByParentAuthor.create!(name: "Test")
+ book = DestroyByParentBook.create!(author: author)
+
+ author.book = DestroyByParentBook.create!
+ author.save!
+
+ assert_not DestroyByParentBook.exists?(book.id)
+ end
+
+ class UndestroyableBook < ActiveRecord::Base
+ self.table_name = "books"
+ belongs_to :author, class_name: "DestroyableAuthor"
+ before_destroy :dont
+
+ def dont
+ throw(:abort)
+ end
+ end
+
+ class DestroyableAuthor < ActiveRecord::Base
+ self.table_name = "authors"
+ has_one :book, class_name: "UndestroyableBook", foreign_key: "author_id", dependent: :destroy
+ end
+
+ def test_dependency_should_halt_parent_destruction
+ author = DestroyableAuthor.create!(name: "Test")
+ UndestroyableBook.create!(author: author)
+
+ assert_no_difference ["DestroyableAuthor.count", "UndestroyableBook.count"] do
+ assert_not author.destroy
+ end
+ end
end
diff --git a/activerecord/test/cases/associations/has_one_through_associations_test.rb b/activerecord/test/cases/associations/has_one_through_associations_test.rb
index 38a729d2d4..0309663943 100644
--- a/activerecord/test/cases/associations/has_one_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_through_associations_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/club"
require "models/member_type"
@@ -23,7 +25,7 @@ require "models/customer_carrier"
class HasOneThroughAssociationsTest < ActiveRecord::TestCase
fixtures :member_types, :members, :clubs, :memberships, :sponsors, :organizations, :minivans,
- :dashboards, :speedometers, :authors, :posts, :comments, :categories, :essays, :owners
+ :dashboards, :speedometers, :authors, :author_addresses, :posts, :comments, :categories, :essays, :owners
def setup
@member = members(:groucho)
@@ -40,6 +42,18 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
assert_not_nil new_member.club
end
+ def test_creating_association_builds_through_record
+ new_member = Member.create(name: "Chris")
+ new_club = new_member.association(:club).build
+ assert new_member.current_membership
+ assert_equal new_club, new_member.club
+ assert_predicate new_club, :new_record?
+ assert_predicate new_member.current_membership, :new_record?
+ assert new_member.save
+ assert_predicate new_club, :persisted?
+ assert_predicate new_member.current_membership, :persisted?
+ end
+
def test_creating_association_builds_through_record_for_new
new_member = Member.new(name: "Jane")
new_member.club = clubs(:moustache_club)
@@ -50,6 +64,24 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
assert_equal clubs(:moustache_club), new_member.club
end
+ def test_building_multiple_associations_builds_through_record
+ member_type = MemberType.create!
+ member = Member.create!
+ member_detail_with_one_association = MemberDetail.new(member_type: member_type)
+ assert_predicate member_detail_with_one_association.member, :new_record?
+ member_detail_with_two_associations = MemberDetail.new(member_type: member_type, admittable: member)
+ assert_predicate member_detail_with_two_associations.member, :new_record?
+ end
+
+ def test_creating_multiple_associations_creates_through_record
+ member_type = MemberType.create!
+ member = Member.create!
+ member_detail_with_one_association = MemberDetail.create!(member_type: member_type)
+ assert_not_predicate member_detail_with_one_association.member, :new_record?
+ member_detail_with_two_associations = MemberDetail.create!(member_type: member_type, admittable: member)
+ assert_not_predicate member_detail_with_two_associations.member, :new_record?
+ end
+
def test_creating_association_sets_both_parent_ids_for_new
member = Member.new(name: "Sean Griffin")
club = Club.new(name: "Da Club")
@@ -98,7 +130,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
end
def test_has_one_through_eager_loading
- members = assert_queries(3) do #base table, through table, clubs table
+ members = assert_queries(3) do # base table, through table, clubs table
Member.all.merge!(includes: :club, where: ["name = ?", "Groucho Marx"]).to_a
end
assert_equal 1, members.size
@@ -106,7 +138,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
end
def test_has_one_through_eager_loading_through_polymorphic
- members = assert_queries(3) do #base table, through table, clubs table
+ members = assert_queries(3) do # base table, through table, clubs table
Member.all.merge!(includes: :sponsor_club, where: ["name = ?", "Groucho Marx"]).to_a
end
assert_equal 1, members.size
@@ -137,7 +169,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
def test_has_one_through_nonpreload_eagerloading
members = assert_queries(1) do
- Member.all.merge!(includes: :club, where: ["members.name = ?", "Groucho Marx"], order: "clubs.name").to_a #force fallback
+ Member.all.merge!(includes: :club, where: ["members.name = ?", "Groucho Marx"], order: "clubs.name").to_a # force fallback
end
assert_equal 1, members.size
assert_not_nil assert_no_queries { members[0].club }
@@ -145,7 +177,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
def test_has_one_through_nonpreload_eager_loading_through_polymorphic
members = assert_queries(1) do
- Member.all.merge!(includes: :sponsor_club, where: ["members.name = ?", "Groucho Marx"], order: "clubs.name").to_a #force fallback
+ Member.all.merge!(includes: :sponsor_club, where: ["members.name = ?", "Groucho Marx"], order: "clubs.name").to_a # force fallback
end
assert_equal 1, members.size
assert_not_nil assert_no_queries { members[0].sponsor_club }
@@ -154,7 +186,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
def test_has_one_through_nonpreload_eager_loading_through_polymorphic_with_more_than_one_through_record
Sponsor.new(sponsor_club: clubs(:crazy_club), sponsorable: members(:groucho)).save!
members = assert_queries(1) do
- Member.all.merge!(includes: :sponsor_club, where: ["members.name = ?", "Groucho Marx"], order: "clubs.name DESC").to_a #force fallback
+ Member.all.merge!(includes: :sponsor_club, where: ["members.name = ?", "Groucho Marx"], order: "clubs.name DESC").to_a # force fallback
end
assert_equal 1, members.size
assert_not_nil assert_no_queries { members[0].sponsor_club }
@@ -227,7 +259,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
MemberDetail.all.merge!(includes: :member_type).to_a
end
@new_detail = @member_details[0]
- assert @new_detail.send(:association, :member_type).loaded?
+ assert_predicate @new_detail.send(:association, :member_type), :loaded?
assert_no_queries { @new_detail.member_type }
end
@@ -315,12 +347,12 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
minivan.dashboard
proxy = minivan.send(:association_instance_get, :dashboard)
- assert !proxy.stale_target?
+ assert_not_predicate proxy, :stale_target?
assert_equal dashboards(:cool_first), minivan.dashboard
minivan.speedometer_id = speedometers(:second).id
- assert proxy.stale_target?
+ assert_predicate proxy, :stale_target?
assert_equal dashboards(:second), minivan.dashboard
end
@@ -332,7 +364,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
minivan.speedometer_id = speedometers(:second).id
- assert proxy.stale_target?
+ assert_predicate proxy, :stale_target?
assert_equal dashboards(:second), minivan.dashboard
end
diff --git a/activerecord/test/cases/associations/inner_join_association_test.rb b/activerecord/test/cases/associations/inner_join_association_test.rb
index 7414869c8f..c33dcdee61 100644
--- a/activerecord/test/cases/associations/inner_join_association_test.rb
+++ b/activerecord/test/cases/associations/inner_join_association_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/post"
require "models/comment"
@@ -10,7 +12,7 @@ require "models/tagging"
require "models/tag"
class InnerJoinAssociationTest < ActiveRecord::TestCase
- fixtures :authors, :essays, :posts, :comments, :categories, :categories_posts, :categorizations,
+ fixtures :authors, :author_addresses, :essays, :posts, :comments, :categories, :categories_posts, :categorizations,
:taggings, :tags
def test_construct_finder_sql_applies_aliases_tables_on_association_conditions
@@ -25,6 +27,24 @@ class InnerJoinAssociationTest < ActiveRecord::TestCase
end
end
+ def test_construct_finder_sql_does_not_table_name_collide_on_duplicate_associations_with_left_outer_joins
+ sql = Person.joins(agents: :agents).left_outer_joins(agents: :agents).to_sql
+ assert_match(/agents_people_4/i, sql)
+ end
+
+ def test_construct_finder_sql_does_not_table_name_collide_with_string_joins
+ sql = Person.joins(:agents).joins("JOIN people agents_people ON agents_people.primary_contact_id = people.id").to_sql
+ assert_match(/agents_people_2/i, sql)
+ end
+
+ def test_construct_finder_sql_does_not_table_name_collide_with_aliased_joins
+ people = Person.arel_table
+ agents = people.alias("agents_people")
+ constraint = agents[:primary_contact_id].eq(people[:id])
+ sql = Person.joins(:agents).joins(agents.create_join(agents, agents.create_on(constraint))).to_sql
+ assert_match(/agents_people_2/i, sql)
+ end
+
def test_construct_finder_sql_ignores_empty_joins_hash
sql = Author.joins({}).to_sql
assert_no_match(/JOIN/i, sql)
@@ -59,19 +79,19 @@ class InnerJoinAssociationTest < ActiveRecord::TestCase
def test_find_with_implicit_inner_joins_honors_readonly_with_select
authors = Author.joins(:posts).select("authors.*").to_a
- assert !authors.empty?, "expected authors to be non-empty"
+ assert_not 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.joins(:posts).readonly(false).to_a
- assert !authors.empty?, "expected authors to be non-empty"
+ assert_not 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.joins(:posts).select("authors.*").to_a
- assert !authors.empty?, "expected authors to be non-empty"
+ assert_not authors.empty?, "expected authors to be non-empty"
assert authors.all? { |a| !a.instance_variable_defined?(:@posts) }, "expected no authors to have the @posts association loaded"
end
@@ -95,19 +115,19 @@ class InnerJoinAssociationTest < ActiveRecord::TestCase
scope = Post.joins(:special_comments).where(id: posts(:sti_comments).id)
# The join should match SpecialComment and its subclasses only
- assert scope.where("comments.type" => "Comment").empty?
- assert !scope.where("comments.type" => "SpecialComment").empty?
- assert !scope.where("comments.type" => "SubSpecialComment").empty?
+ assert_empty scope.where("comments.type" => "Comment")
+ assert_not_empty scope.where("comments.type" => "SpecialComment")
+ assert_not_empty scope.where("comments.type" => "SubSpecialComment")
end
def test_find_with_conditions_on_reflection
- assert !posts(:welcome).comments.empty?
+ assert_not_empty posts(:welcome).comments
assert Post.joins(:nonexistent_comments).where(id: posts(:welcome).id).empty? # [sic!]
end
def test_find_with_conditions_on_through_reflection
- assert !posts(:welcome).tags.empty?
- assert Post.joins(:misc_tags).where(id: posts(:welcome).id).empty?
+ assert_not_empty posts(:welcome).tags
+ assert_empty Post.joins(:misc_tags).where(id: posts(:welcome).id)
end
test "the default scope of the target is applied when joining associations" do
diff --git a/activerecord/test/cases/associations/inverse_associations_test.rb b/activerecord/test/cases/associations/inverse_associations_test.rb
index 287b3e9ebc..eb4dc73423 100644
--- a/activerecord/test/cases/associations/inverse_associations_test.rb
+++ b/activerecord/test/cases/associations/inverse_associations_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/man"
require "models/face"
@@ -16,6 +18,10 @@ require "models/admin/user"
require "models/developer"
require "models/company"
require "models/project"
+require "models/author"
+require "models/post"
+require "models/department"
+require "models/hotel"
class AutomaticInverseFindingTests < ActiveRecord::TestCase
fixtures :ratings, :comments, :cars
@@ -24,11 +30,9 @@ class AutomaticInverseFindingTests < ActiveRecord::TestCase
monkey_reflection = MixedCaseMonkey.reflect_on_association(:man)
man_reflection = Man.reflect_on_association(:mixed_case_monkey)
- assert_respond_to monkey_reflection, :has_inverse?
assert monkey_reflection.has_inverse?, "The monkey reflection should have an inverse"
assert_equal man_reflection, monkey_reflection.inverse_of, "The monkey reflection's inverse should be the man reflection"
- assert_respond_to man_reflection, :has_inverse?
assert man_reflection.has_inverse?, "The man reflection should have an inverse"
assert_equal monkey_reflection, man_reflection.inverse_of, "The man reflection's inverse should be the monkey reflection"
end
@@ -37,7 +41,6 @@ class AutomaticInverseFindingTests < ActiveRecord::TestCase
account_reflection = Admin::Account.reflect_on_association(:users)
user_reflection = Admin::User.reflect_on_association(:account)
- assert_respond_to account_reflection, :has_inverse?
assert account_reflection.has_inverse?, "The Admin::Account reflection should have an inverse"
assert_equal user_reflection, account_reflection.inverse_of, "The Admin::Account reflection's inverse should be the Admin::User reflection"
end
@@ -46,11 +49,9 @@ class AutomaticInverseFindingTests < ActiveRecord::TestCase
car_reflection = Car.reflect_on_association(:bulb)
bulb_reflection = Bulb.reflect_on_association(:car)
- assert_respond_to car_reflection, :has_inverse?
assert car_reflection.has_inverse?, "The Car reflection should have an inverse"
assert_equal bulb_reflection, car_reflection.inverse_of, "The Car reflection's inverse should be the Bulb reflection"
- assert_respond_to bulb_reflection, :has_inverse?
assert bulb_reflection.has_inverse?, "The Bulb reflection should have an inverse"
assert_equal car_reflection, bulb_reflection.inverse_of, "The Bulb reflection's inverse should be the Car reflection"
end
@@ -59,11 +60,24 @@ class AutomaticInverseFindingTests < ActiveRecord::TestCase
comment_reflection = Comment.reflect_on_association(:ratings)
rating_reflection = Rating.reflect_on_association(:comment)
- assert_respond_to comment_reflection, :has_inverse?
assert comment_reflection.has_inverse?, "The Comment reflection should have an inverse"
assert_equal rating_reflection, comment_reflection.inverse_of, "The Comment reflection's inverse should be the Rating reflection"
end
+ def test_has_many_and_belongs_to_should_find_inverse_automatically_for_sti
+ author_reflection = Author.reflect_on_association(:posts)
+ author_child_reflection = Author.reflect_on_association(:special_posts)
+ post_reflection = Post.reflect_on_association(:author)
+
+ assert_respond_to author_reflection, :has_inverse?
+ assert author_reflection.has_inverse?, "The Author reflection should have an inverse"
+ assert_equal post_reflection, author_reflection.inverse_of, "The Author reflection's inverse should be the Post reflection"
+
+ assert_respond_to author_child_reflection, :has_inverse?
+ assert author_child_reflection.has_inverse?, "The Author reflection should have an inverse"
+ assert_equal post_reflection, author_child_reflection.inverse_of, "The Author reflection's inverse should be the Post reflection"
+ end
+
def test_has_one_and_belongs_to_automatic_inverse_shares_objects
car = Car.first
bulb = Bulb.create!(car: car)
@@ -107,24 +121,17 @@ class AutomaticInverseFindingTests < ActiveRecord::TestCase
def test_polymorphic_and_has_many_through_relationships_should_not_have_inverses
sponsor_reflection = Sponsor.reflect_on_association(:sponsorable)
- assert_respond_to sponsor_reflection, :has_inverse?
- assert !sponsor_reflection.has_inverse?, "A polymorphic association should not find an inverse automatically"
+ assert_not sponsor_reflection.has_inverse?, "A polymorphic association should not find an inverse automatically"
club_reflection = Club.reflect_on_association(:members)
- assert_respond_to club_reflection, :has_inverse?
- assert !club_reflection.has_inverse?, "A has_many_through association should not find an inverse automatically"
+ assert_not club_reflection.has_inverse?, "A has_many_through association should not find an inverse automatically"
end
- def test_polymorphic_relationships_should_still_not_have_inverses_when_non_polymorphic_relationship_has_the_same_name
+ def test_polymorphic_has_one_should_find_inverse_automatically
man_reflection = Man.reflect_on_association(:polymorphic_face_without_inverse)
- face_reflection = Face.reflect_on_association(:man)
-
- assert_respond_to face_reflection, :has_inverse?
- assert face_reflection.has_inverse?, "For this test, the non-polymorphic association must have an inverse"
- assert_respond_to man_reflection, :has_inverse?
- assert !man_reflection.has_inverse?, "The target of a polymorphic association should not find an inverse automatically"
+ assert_predicate man_reflection, :has_inverse?
end
end
@@ -145,39 +152,22 @@ class InverseAssociationTests < ActiveRecord::TestCase
def test_should_be_able_to_ask_a_reflection_if_it_has_an_inverse
has_one_with_inverse_ref = Man.reflect_on_association(:face)
- assert_respond_to has_one_with_inverse_ref, :has_inverse?
- assert has_one_with_inverse_ref.has_inverse?
+ assert_predicate has_one_with_inverse_ref, :has_inverse?
has_many_with_inverse_ref = Man.reflect_on_association(:interests)
- assert_respond_to has_many_with_inverse_ref, :has_inverse?
- assert has_many_with_inverse_ref.has_inverse?
+ assert_predicate has_many_with_inverse_ref, :has_inverse?
belongs_to_with_inverse_ref = Face.reflect_on_association(:man)
- assert_respond_to belongs_to_with_inverse_ref, :has_inverse?
- assert belongs_to_with_inverse_ref.has_inverse?
+ assert_predicate belongs_to_with_inverse_ref, :has_inverse?
has_one_without_inverse_ref = Club.reflect_on_association(:sponsor)
- assert_respond_to has_one_without_inverse_ref, :has_inverse?
- assert !has_one_without_inverse_ref.has_inverse?
+ assert_not_predicate has_one_without_inverse_ref, :has_inverse?
has_many_without_inverse_ref = Club.reflect_on_association(:memberships)
- assert_respond_to has_many_without_inverse_ref, :has_inverse?
- assert !has_many_without_inverse_ref.has_inverse?
+ assert_not_predicate has_many_without_inverse_ref, :has_inverse?
belongs_to_without_inverse_ref = Sponsor.reflect_on_association(:sponsor_club)
- assert_respond_to belongs_to_without_inverse_ref, :has_inverse?
- assert !belongs_to_without_inverse_ref.has_inverse?
- end
-
- def test_should_be_able_to_ask_a_reflection_what_it_is_the_inverse_of
- has_one_ref = Man.reflect_on_association(:face)
- assert_respond_to has_one_ref, :inverse_of
-
- has_many_ref = Man.reflect_on_association(:interests)
- assert_respond_to has_many_ref, :inverse_of
-
- belongs_to_ref = Face.reflect_on_association(:man)
- assert_respond_to belongs_to_ref, :inverse_of
+ assert_not_predicate belongs_to_without_inverse_ref, :has_inverse?
end
def test_inverse_of_method_should_supply_the_actual_reflection_instance_it_is_the_inverse_of
@@ -202,6 +192,16 @@ class InverseAssociationTests < ActiveRecord::TestCase
assert_nil belongs_to_ref.inverse_of
end
+ def test_polymorphic_associations_dont_attempt_to_find_inverse_of
+ belongs_to_ref = Sponsor.reflect_on_association(:sponsor)
+ assert_raise(ArgumentError) { belongs_to_ref.klass }
+ assert_nil belongs_to_ref.inverse_of
+
+ belongs_to_ref = Face.reflect_on_association(:human)
+ assert_raise(ArgumentError) { belongs_to_ref.klass }
+ assert_nil belongs_to_ref.inverse_of
+ end
+
def test_this_inverse_stuff
firm = Firm.create!(name: "Adequate Holdings")
Project.create!(name: "Project 1", firm: firm)
@@ -295,7 +295,7 @@ class InverseHasOneTests < ActiveRecord::TestCase
end
class InverseHasManyTests < ActiveRecord::TestCase
- fixtures :men, :interests
+ fixtures :men, :interests, :posts, :authors, :author_addresses
def test_parent_instance_should_be_shared_with_every_child_on_find
m = men(:gordon)
@@ -309,6 +309,27 @@ class InverseHasManyTests < ActiveRecord::TestCase
end
end
+ def test_parent_instance_should_be_shared_with_every_child_on_find_for_sti
+ a = authors(:david)
+ ps = a.posts
+ ps.each do |p|
+ assert_equal a.name, p.author.name, "Name of man should be the same before changes to parent instance"
+ a.name = "Bongo"
+ assert_equal a.name, p.author.name, "Name of man should be the same after changes to parent instance"
+ p.author.name = "Mungo"
+ assert_equal a.name, p.author.name, "Name of man should be the same after changes to child-owned instance"
+ end
+
+ sps = a.special_posts
+ sps.each do |sp|
+ assert_equal a.name, sp.author.name, "Name of man should be the same before changes to parent instance"
+ a.name = "Bongo"
+ assert_equal a.name, sp.author.name, "Name of man should be the same after changes to parent instance"
+ sp.author.name = "Mungo"
+ assert_equal a.name, sp.author.name, "Name of man should be the same after changes to child-owned instance"
+ end
+ end
+
def test_parent_instance_should_be_shared_with_eager_loaded_children
m = Man.all.merge!(where: { name: "Gordon" }, includes: :interests).first
is = m.interests
@@ -455,7 +476,7 @@ class InverseHasManyTests < ActiveRecord::TestCase
interest = Interest.create!(man: man)
man.interests.find(interest.id)
- assert_not man.interests.loaded?
+ assert_not_predicate man.interests, :loaded?
end
def test_raise_record_not_found_error_when_invalid_ids_are_passed
@@ -475,7 +496,10 @@ class InverseHasManyTests < ActiveRecord::TestCase
def test_raise_record_not_found_error_when_no_ids_are_passed
man = Man.create!
- assert_raise(ActiveRecord::RecordNotFound) { man.interests.find() }
+ exception = assert_raise(ActiveRecord::RecordNotFound) { man.interests.load.find() }
+
+ assert_equal exception.model, "Interest"
+ assert_equal exception.primary_key, "id"
end
def test_trying_to_use_inverses_that_dont_exist_should_raise_an_error
@@ -492,16 +516,16 @@ class InverseHasManyTests < ActiveRecord::TestCase
i.man.name = "Charles"
assert_equal i.man.name, man.name
- assert !man.persisted?
+ assert_not_predicate man, :persisted?
end
def test_inverse_instance_should_be_set_before_find_callbacks_are_run
reset_callbacks(Interest, :find) do
Interest.after_find { raise unless association(:man).loaded? && man.present? }
- assert Man.first.interests.reload.any?
- assert Man.includes(:interests).first.interests.any?
- assert Man.joins(:interests).includes(:interests).first.interests.any?
+ assert_predicate Man.first.interests.reload, :any?
+ assert_predicate Man.includes(:interests).first.interests, :any?
+ assert_predicate Man.joins(:interests).includes(:interests).first.interests, :any?
end
end
@@ -509,9 +533,9 @@ class InverseHasManyTests < ActiveRecord::TestCase
reset_callbacks(Interest, :initialize) do
Interest.after_initialize { raise unless association(:man).loaded? && man.present? }
- assert Man.first.interests.reload.any?
- assert Man.includes(:interests).first.interests.any?
- assert Man.joins(:interests).includes(:interests).first.interests.any?
+ assert_predicate Man.first.interests.reload, :any?
+ assert_predicate Man.includes(:interests).first.interests, :any?
+ assert_predicate Man.joins(:interests).includes(:interests).first.interests, :any?
end
end
@@ -651,20 +675,6 @@ class InversePolymorphicBelongsToTests < ActiveRecord::TestCase
assert_equal face.description, new_man.polymorphic_face.description, "Description of face should be the same after changes to replaced-parent-owned instance"
end
- def test_child_instance_should_be_shared_with_replaced_via_method_parent
- face = faces(:confused)
- new_man = Man.new
-
- assert_not_nil face.polymorphic_man
- face.polymorphic_man = new_man
-
- assert_equal face.description, new_man.polymorphic_face.description, "Description of face should be the same before changes to parent instance"
- face.description = "Bongo"
- assert_equal face.description, new_man.polymorphic_face.description, "Description of face should be the same after changes to parent instance"
- new_man.polymorphic_face.description = "Mungo"
- assert_equal face.description, new_man.polymorphic_face.description, "Description of face should be the same after changes to replaced-parent-owned instance"
- end
-
def test_inversed_instance_should_not_be_reloaded_after_stale_state_changed
new_man = Man.new
face = Face.new
@@ -677,6 +687,16 @@ class InversePolymorphicBelongsToTests < ActiveRecord::TestCase
assert_equal old_inversed_man.object_id, new_inversed_man.object_id
end
+ def test_inversed_instance_should_not_be_reloaded_after_stale_state_changed_with_validation
+ face = Face.new man: Man.new
+
+ old_inversed_man = face.man
+ face.save!
+ new_inversed_man = face.man
+
+ assert_equal old_inversed_man.object_id, new_inversed_man.object_id
+ end
+
def test_should_not_try_to_set_inverse_instances_when_the_inverse_is_a_has_many
i = interests(:llama_wrangling)
m = i.polymorphic_man
@@ -706,6 +726,16 @@ class InversePolymorphicBelongsToTests < ActiveRecord::TestCase
# fails because Interest does have the correct inverse_of
assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Face.first.polymorphic_man = Interest.first }
end
+
+ def test_favors_has_one_associations_for_inverse_of
+ inverse_name = Post.reflect_on_association(:author).inverse_of.name
+ assert_equal :post, inverse_name
+ end
+
+ def test_finds_inverse_of_for_plural_associations
+ inverse_name = Department.reflect_on_association(:hotel).inverse_of.name
+ assert_equal :departments, inverse_name
+ end
end
# NOTE - these tests might not be meaningful, ripped as they were from the parental_control plugin
diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb
index a4345f3857..9d1c73c33b 100644
--- a/activerecord/test/cases/associations/join_model_test.rb
+++ b/activerecord/test/cases/associations/join_model_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/tag"
require "models/tagging"
@@ -19,7 +21,7 @@ require "models/car"
class AssociationsJoinModelTest < ActiveRecord::TestCase
self.use_transactional_tests = false unless supports_savepoints?
- fixtures :posts, :authors, :categories, :categorizations, :comments, :tags, :taggings, :author_favorites, :vertices, :items, :books,
+ fixtures :posts, :authors, :author_addresses, :categories, :categorizations, :comments, :tags, :taggings, :author_favorites, :vertices, :items, :books,
# Reload edges table from fixtures as otherwise repeated test was failing
:edges
@@ -42,11 +44,11 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
def test_has_many_distinct_through_count
author = authors(:mary)
- assert !authors(:mary).unique_categorized_posts.loaded?
+ assert_not_predicate 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.where(title: nil).count(:title) }
- assert !authors(:mary).unique_categorized_posts.loaded?
+ assert_not_predicate authors(:mary).unique_categorized_posts, :loaded?
end
def test_has_many_distinct_through_find
@@ -97,11 +99,11 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
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
+ post = SubAbstractStiPost.create title: "SubAbstractStiPost", body: "SubAbstractStiPost body"
+ assert_instance_of SubAbstractStiPost, post
tagging = tags(:misc).taggings.create(taggable: post)
- assert_equal "SubStiPost", tagging.taggable_type
+ assert_equal "SubAbstractStiPost", tagging.taggable_type
end
def test_polymorphic_has_many_going_through_join_model_with_inheritance
@@ -367,7 +369,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
Tag.has_many :null_taggings, -> { none }, class_name: :Tagging
Tag.has_many :null_tagged_posts, through: :null_taggings, source: "taggable", source_type: "Post"
assert_equal [], tags(:general).null_tagged_posts
- refute_equal [], tags(:general).tagged_posts
+ assert_not_equal [], tags(:general).tagged_posts
end
def test_eager_has_many_polymorphic_with_source_type
@@ -402,7 +404,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
end
def test_has_many_through_polymorphic_has_one
- assert_equal Tagging.find(1, 2).sort_by(&:id), authors(:david).taggings_2
+ assert_equal Tagging.find(1, 2).sort_by(&:id), authors(:david).taggings_2.sort_by(&:id)
end
def test_has_many_through_polymorphic_has_many
@@ -452,8 +454,8 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
def test_has_many_through_uses_conditions_specified_on_the_has_many_association
author = Author.first
- assert author.comments.present?
- assert author.nonexistent_comments.blank?
+ assert_predicate author.comments, :present?
+ assert_predicate author.nonexistent_comments, :blank?
end
def test_has_many_through_uses_correct_attributes
@@ -465,27 +467,27 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
new_tag = Tag.new(name: "new")
saved_post.tags << new_tag
- assert new_tag.persisted? #consistent with habtm!
- assert saved_post.persisted?
+ assert new_tag.persisted? # consistent with habtm!
+ assert_predicate saved_post, :persisted?
assert_includes saved_post.tags, new_tag
- assert new_tag.persisted?
+ assert_predicate new_tag, :persisted?
assert_includes saved_post.reload.tags.reload, new_tag
new_post = Post.new(title: "Association replacement works!", body: "You best believe it.")
saved_tag = tags(:general)
new_post.tags << saved_tag
- assert !new_post.persisted?
- assert saved_tag.persisted?
+ assert_not_predicate new_post, :persisted?
+ assert_predicate saved_tag, :persisted?
assert_includes new_post.tags, saved_tag
new_post.save!
- assert new_post.persisted?
+ assert_predicate new_post, :persisted?
assert_includes new_post.reload.tags.reload, saved_tag
- assert !posts(:thinking).tags.build.persisted?
- assert !posts(:thinking).tags.new.persisted?
+ assert_not_predicate posts(:thinking).tags.build, :persisted?
+ assert_not_predicate posts(:thinking).tags.new, :persisted?
end
def test_create_associate_when_adding_to_has_many_through
@@ -494,25 +496,25 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
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}.")
+ "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}.")
+ "Expected a Tagging in taggings collection, got #{wrong.class}.")
assert_equal(count + 1, post_thinking.reload.tags.size)
assert_equal(count + 1, post_thinking.tags.reload.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}.")
+ "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}.")
+ "Expected a Tagging in taggings collection, got #{wrong.class}.")
assert_equal(count + 2, post_thinking.reload.tags.size)
assert_equal(count + 2, post_thinking.tags.reload.size)
assert_nothing_raised { post_thinking.tags.concat(Tag.create!(name: "abc"), Tag.create!(name: "def")) }
assert_nil(wrong = post_thinking.tags.detect { |t| t.class != Tag },
- message = "Expected a Tag in tags collection, got #{wrong.class}.")
+ "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}.")
+ "Expected a Tagging in taggings collection, got #{wrong.class}.")
assert_equal(count + 4, post_thinking.reload.tags.size)
assert_equal(count + 4, post_thinking.tags.reload.size)
@@ -527,14 +529,14 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
def test_has_many_through_collection_size_doesnt_load_target_if_not_loaded
author = authors(:david)
assert_equal 10, author.comments.size
- assert !author.comments.loaded?
+ assert_not_predicate author.comments, :loaded?
end
def test_has_many_through_collection_size_uses_counter_cache_if_it_exists
c = categories(:general)
c.categorizations_count = 100
assert_equal 100, c.categorizations.size
- assert !c.categorizations.loaded?
+ assert_not_predicate c.categorizations, :loaded?
end
def test_adding_junk_to_has_many_through_should_raise_type_mismatch
@@ -708,7 +710,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
category = david.categories.first
assert_no_queries do
- assert david.categories.loaded?
+ assert_predicate david.categories, :loaded?
assert_includes david.categories, category
end
end
@@ -718,19 +720,19 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
category = david.categories.first
david.reload
- assert ! david.categories.loaded?
+ assert_not_predicate david.categories, :loaded?
assert_queries(1) do
assert_includes david.categories, category
end
- assert ! david.categories.loaded?
+ assert_not_predicate david.categories, :loaded?
end
def test_has_many_through_include_returns_false_for_non_matching_record_to_verify_scoping
david = authors(:david)
category = Category.create!(name: "Not Associated")
- assert ! david.categories.loaded?
- assert ! david.categories.include?(category)
+ assert_not_predicate david.categories, :loaded?
+ assert_not david.categories.include?(category)
end
def test_has_many_through_goes_through_all_sti_classes
diff --git a/activerecord/test/cases/associations/left_outer_join_association_test.rb b/activerecord/test/cases/associations/left_outer_join_association_test.rb
index 42dbbad1c8..0e54e8c1b0 100644
--- a/activerecord/test/cases/associations/left_outer_join_association_test.rb
+++ b/activerecord/test/cases/associations/left_outer_join_association_test.rb
@@ -1,13 +1,16 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/post"
require "models/comment"
require "models/author"
require "models/essay"
+require "models/category"
require "models/categorization"
require "models/person"
class LeftOuterJoinAssociationTest < ActiveRecord::TestCase
- fixtures :authors, :essays, :posts, :comments, :categorizations, :people
+ fixtures :authors, :author_addresses, :essays, :posts, :comments, :categorizations, :people
def test_construct_finder_sql_applies_aliases_tables_on_association_conditions
result = Author.left_outer_joins(:thinking_posts, :welcome_posts).to_a
@@ -67,15 +70,15 @@ class LeftOuterJoinAssociationTest < ActiveRecord::TestCase
scope = Post.left_outer_joins(:special_comments).where(id: posts(:sti_comments).id)
# The join should match SpecialComment and its subclasses only
- assert scope.where("comments.type" => "Comment").empty?
- assert !scope.where("comments.type" => "SpecialComment").empty?
- assert !scope.where("comments.type" => "SubSpecialComment").empty?
+ assert_empty scope.where("comments.type" => "Comment")
+ assert_not_empty scope.where("comments.type" => "SpecialComment")
+ assert_not_empty scope.where("comments.type" => "SubSpecialComment")
end
def test_does_not_override_select
authors = Author.select("authors.name, #{%{(authors.author_address_id || ' ' || authors.author_address_extra_id) as addr_id}}").left_outer_joins(:posts)
- assert authors.any?
- assert authors.first.respond_to?(:addr_id)
+ assert_predicate authors, :any?
+ assert_respond_to authors.first, :addr_id
end
test "the default scope of the target is applied when joining associations" do
diff --git a/activerecord/test/cases/associations/nested_through_associations_test.rb b/activerecord/test/cases/associations/nested_through_associations_test.rb
index dc26f6a383..03ed1c1d47 100644
--- a/activerecord/test/cases/associations/nested_through_associations_test.rb
+++ b/activerecord/test/cases/associations/nested_through_associations_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/author"
require "models/post"
@@ -22,9 +24,14 @@ require "models/category"
require "models/categorization"
require "models/membership"
require "models/essay"
+require "models/hotel"
+require "models/department"
+require "models/chef"
+require "models/cake_designer"
+require "models/drink_designer"
class NestedThroughAssociationsTest < ActiveRecord::TestCase
- fixtures :authors, :books, :posts, :subscriptions, :subscribers, :tags, :taggings,
+ fixtures :authors, :author_addresses, :books, :posts, :subscriptions, :subscribers, :tags, :taggings,
:people, :readers, :references, :jobs, :ratings, :comments, :members, :member_details,
:member_types, :sponsors, :clubs, :organizations, :categories, :categories_posts,
:categorizations, :memberships, :essays
@@ -71,7 +78,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase
# This ensures that the polymorphism of taggings is being observed correctly
authors = Author.joins(:tags).where("taggings.taggable_type" => "FakeModel")
- assert authors.empty?
+ assert_empty authors
end
# has_many through
@@ -170,7 +177,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase
members = Member.joins(:organization_member_details).
where("member_details.id" => 9)
- assert members.empty?
+ assert_empty members
end
# has_many through
@@ -202,7 +209,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase
members = Member.joins(:organization_member_details_2).
where("member_details.id" => 9)
- assert members.empty?
+ assert_empty members
end
# has_many through
@@ -418,9 +425,14 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase
# Check the polymorphism of taggings is being observed correctly (in both joins)
authors = Author.joins(:similar_posts).where("taggings.taggable_type" => "FakeModel")
- assert authors.empty?
+ assert_empty authors
authors = Author.joins(:similar_posts).where("taggings_authors_join.taggable_type" => "FakeModel")
- assert authors.empty?
+ assert_empty authors
+ end
+
+ def test_nested_has_many_through_with_scope_on_polymorphic_reflection
+ authors = Author.joins(:ordered_posts).where("posts.id" => posts(:misc_by_bob).id)
+ assert_equal [authors(:mary), authors(:bob)], authors.distinct.sort_by(&:id)
end
def test_has_many_through_with_foreign_key_option_on_through_reflection
@@ -444,9 +456,9 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase
# Ensure STI is respected in the join
scope = Post.joins(:special_comments_ratings).where(id: posts(:sti_comments).id)
- assert scope.where("comments.type" => "Comment").empty?
- assert !scope.where("comments.type" => "SpecialComment").empty?
- assert !scope.where("comments.type" => "SubSpecialComment").empty?
+ assert_empty scope.where("comments.type" => "Comment")
+ assert_not_empty scope.where("comments.type" => "SpecialComment")
+ assert_not_empty scope.where("comments.type" => "SubSpecialComment")
end
def test_has_many_through_with_sti_on_nested_through_reflection
@@ -454,8 +466,8 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase
assert_equal [taggings(:special_comment_rating)], taggings
scope = Post.joins(:special_comments_ratings_taggings).where(id: posts(:sti_comments).id)
- assert scope.where("comments.type" => "Comment").empty?
- assert !scope.where("comments.type" => "SpecialComment").empty?
+ assert_empty scope.where("comments.type" => "Comment")
+ assert_not_empty scope.where("comments.type" => "SpecialComment")
end
def test_nested_has_many_through_writers_should_raise_error
@@ -505,7 +517,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase
end
def test_nested_has_many_through_with_conditions_on_through_associations_preload
- assert Author.where("tags.id" => 100).joins(:misc_post_first_blue_tags).empty?
+ assert_empty Author.where("tags.id" => 100).joins(:misc_post_first_blue_tags)
authors = assert_queries(3) { Author.includes(:misc_post_first_blue_tags).to_a.sort_by(&:id) }
blue = tags(:blue)
@@ -562,9 +574,40 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase
c = Categorization.new
c.author = authors(:david)
c.post_taggings.to_a
- assert !c.post_taggings.empty?
+ assert_not_empty c.post_taggings
c.save
- assert !c.post_taggings.empty?
+ assert_not_empty c.post_taggings
+ end
+
+ def test_polymorphic_has_many_through_when_through_association_has_not_loaded
+ cake_designer = CakeDesigner.create!(chef: Chef.new)
+ drink_designer = DrinkDesigner.create!(chef: Chef.new)
+ department = Department.create!(chefs: [cake_designer.chef, drink_designer.chef])
+ Hotel.create!(departments: [department])
+ hotel = Hotel.includes(:cake_designers, :drink_designers).take
+
+ assert_equal [cake_designer], hotel.cake_designers
+ assert_equal [drink_designer], hotel.drink_designers
+ end
+
+ def test_polymorphic_has_many_through_when_through_association_has_already_loaded
+ cake_designer = CakeDesigner.create!(chef: Chef.new)
+ drink_designer = DrinkDesigner.create!(chef: Chef.new)
+ department = Department.create!(chefs: [cake_designer.chef, drink_designer.chef])
+ Hotel.create!(departments: [department])
+ hotel = Hotel.includes(:chefs, :cake_designers, :drink_designers).take
+
+ assert_equal [cake_designer], hotel.cake_designers
+ assert_equal [drink_designer], hotel.drink_designers
+ end
+
+ def test_polymorphic_has_many_through_joined_different_table_twice
+ cake_designer = CakeDesigner.create!(chef: Chef.new)
+ drink_designer = DrinkDesigner.create!(chef: Chef.new)
+ department = Department.create!(chefs: [cake_designer.chef, drink_designer.chef])
+ hotel = Hotel.create!(departments: [department])
+
+ assert_equal hotel, Hotel.joins(:cake_designers, :drink_designers).take
end
private
diff --git a/activerecord/test/cases/associations/required_test.rb b/activerecord/test/cases/associations/required_test.rb
index f8b686721e..65a3bb5efe 100644
--- a/activerecord/test/cases/associations/required_test.rb
+++ b/activerecord/test/cases/associations/required_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
class RequiredAssociationsTest < ActiveRecord::TestCase
@@ -22,14 +24,21 @@ class RequiredAssociationsTest < ActiveRecord::TestCase
@connection.drop_table "children", if_exists: true
end
- test "belongs_to associations are not required by default" do
- model = subclass_of(Child) do
- belongs_to :parent, inverse_of: false,
- class_name: "RequiredAssociationsTest::Parent"
- end
+ test "belongs_to associations can be optional by default" do
+ begin
+ original_value = ActiveRecord::Base.belongs_to_required_by_default
+ ActiveRecord::Base.belongs_to_required_by_default = false
- assert model.new.save
- assert model.new(parent: Parent.new).save
+ model = subclass_of(Child) do
+ belongs_to :parent, inverse_of: false,
+ class_name: "RequiredAssociationsTest::Parent"
+ end
+
+ assert model.new.save
+ assert model.new(parent: Parent.new).save
+ ensure
+ ActiveRecord::Base.belongs_to_required_by_default = original_value
+ end
end
test "required belongs_to associations have presence validated" do
@@ -46,6 +55,27 @@ class RequiredAssociationsTest < ActiveRecord::TestCase
assert record.save
end
+ test "belongs_to associations can be required by default" do
+ begin
+ original_value = ActiveRecord::Base.belongs_to_required_by_default
+ ActiveRecord::Base.belongs_to_required_by_default = true
+
+ model = subclass_of(Child) do
+ belongs_to :parent, inverse_of: false,
+ class_name: "RequiredAssociationsTest::Parent"
+ end
+
+ record = model.new
+ assert_not record.save
+ assert_equal ["Parent must exist"], record.errors.full_messages
+
+ record.parent = Parent.new
+ assert record.save
+ ensure
+ ActiveRecord::Base.belongs_to_required_by_default = original_value
+ end
+ end
+
test "has_one associations are not required by default" do
model = subclass_of(Parent) do
has_one :child, inverse_of: false,
diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb
index 26056f6f63..739eb02e0c 100644
--- a/activerecord/test/cases/associations_test.rb
+++ b/activerecord/test/cases/associations_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/computer"
require "models/developer"
@@ -22,7 +24,7 @@ require "models/interest"
class AssociationsTest < ActiveRecord::TestCase
fixtures :accounts, :companies, :developers, :projects, :developers_projects,
- :computers, :people, :readers, :authors, :author_favorites
+ :computers, :people, :readers, :authors, :author_addresses, :author_favorites
def test_eager_loading_should_not_change_count_of_children
liquid = Liquid.create(name: "salty")
@@ -45,7 +47,7 @@ class AssociationsTest < ActiveRecord::TestCase
ship = Ship.create!(name: "The good ship Dollypop")
part = ship.parts.create!(name: "Mast")
part.mark_for_destruction
- assert ship.parts[0].marked_for_destruction?
+ assert_predicate ship.parts[0], :marked_for_destruction?
end
def test_loading_the_association_target_should_load_most_recent_attributes_for_child_records_marked_for_destruction
@@ -90,7 +92,7 @@ class AssociationsTest < ActiveRecord::TestCase
firm.clients.reload
- assert !firm.clients.empty?, "New firm should have reloaded client objects"
+ assert_not firm.clients.empty?, "New firm should have reloaded client objects"
assert_equal 1, firm.clients.size, "New firm should have reloaded clients count"
end
@@ -100,8 +102,8 @@ class AssociationsTest < ActiveRecord::TestCase
has_many_reflections = [Tag.reflect_on_association(:taggings), Developer.reflect_on_association(:projects)]
mixed_reflections = (belongs_to_reflections + has_many_reflections).uniq
assert using_limitable_reflections.call(belongs_to_reflections), "Belong to associations are limitable"
- assert !using_limitable_reflections.call(has_many_reflections), "All has many style associations are not limitable"
- assert !using_limitable_reflections.call(mixed_reflections), "No collection associations (has many style) should pass"
+ assert_not using_limitable_reflections.call(has_many_reflections), "All has many style associations are not limitable"
+ assert_not using_limitable_reflections.call(mixed_reflections), "No collection associations (has many style) should pass"
end
def test_association_with_references
@@ -111,13 +113,13 @@ class AssociationsTest < ActiveRecord::TestCase
end
class AssociationProxyTest < ActiveRecord::TestCase
- fixtures :authors, :posts, :categorizations, :categories, :developers, :projects, :developers_projects
+ fixtures :authors, :author_addresses, :posts, :categorizations, :categories, :developers, :projects, :developers_projects
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_not_predicate david.posts, :loaded?
assert_includes david.posts, post
end
@@ -125,7 +127,7 @@ class AssociationProxyTest < ActiveRecord::TestCase
david = authors(:david)
david.categories << categories(:technology)
- assert !david.categories.loaded?
+ assert_not_predicate david.categories, :loaded?
assert_includes david.categories, categories(:technology)
end
@@ -133,23 +135,23 @@ class AssociationProxyTest < ActiveRecord::TestCase
david = authors(:david)
david.posts << (post = Post.new(title: "New on Edge", body: "More cool stuff!"))
- assert !david.posts.loaded?
+ assert_not_predicate david.posts, :loaded?
david.save
- assert !david.posts.loaded?
+ assert_not_predicate david.posts, :loaded?
assert_includes david.posts, 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_predicate josh.posts, :loaded?
assert_equal 1, josh.posts.size
end
def test_append_behaves_like_push
josh = Author.new(name: "Josh")
josh.posts.append Post.new(title: "New on Edge", body: "More cool stuff!")
- assert josh.posts.loaded?
+ assert_predicate josh.posts, :loaded?
assert_equal 1, josh.posts.size
end
@@ -161,22 +163,22 @@ class AssociationProxyTest < ActiveRecord::TestCase
def test_save_on_parent_does_not_load_target
david = developers(:david)
- assert !david.projects.loaded?
+ assert_not_predicate david.projects, :loaded?
david.update_columns(created_at: Time.now)
- assert !david.projects.loaded?
+ assert_not_predicate david.projects, :loaded?
end
def test_load_does_load_target
david = developers(:david)
- assert !david.projects.loaded?
+ assert_not_predicate david.projects, :loaded?
david.projects.load
- assert david.projects.loaded?
+ assert_predicate david.projects, :loaded?
end
def test_inspect_does_not_reload_a_not_yet_loaded_target
andreas = Developer.new name: "Andreas", log: "new developer added"
- assert !andreas.audit_logs.loaded?
+ assert_not_predicate andreas.audit_logs, :loaded?
assert_match(/message: "new developer added"/, andreas.audit_logs.inspect)
end
@@ -220,6 +222,18 @@ class AssociationProxyTest < ActiveRecord::TestCase
assert_equal david.projects, david.projects.scope
end
+ test "proxy object is cached" do
+ david = developers(:david)
+ assert_same david.projects, david.projects
+ end
+
+ test "proxy object can be stubbed" do
+ david = developers(:david)
+ david.projects.define_singleton_method(:extra_method) { 42 }
+
+ assert_equal 42, david.projects.extra_method
+ end
+
test "inverses get set of subsets of the association" do
man = Man.create
man.interests.create
@@ -233,25 +247,25 @@ class AssociationProxyTest < ActiveRecord::TestCase
test "first! works on loaded associations" do
david = authors(:david)
- assert_equal david.posts.first, david.posts.reload.first!
- assert david.posts.loaded?
- assert_no_queries { david.posts.first! }
+ assert_equal david.first_posts.first, david.first_posts.reload.first!
+ assert_predicate david.first_posts, :loaded?
+ assert_no_queries { david.first_posts.first! }
end
def test_pluck_uses_loaded_target
david = authors(:david)
- assert_equal david.posts.pluck(:title), david.posts.load.pluck(:title)
- assert david.posts.loaded?
- assert_no_queries { david.posts.pluck(:title) }
+ assert_equal david.first_posts.pluck(:title), david.first_posts.load.pluck(:title)
+ assert_predicate david.first_posts, :loaded?
+ assert_no_queries { david.first_posts.pluck(:title) }
end
def test_reset_unloads_target
david = authors(:david)
david.posts.reload
- assert david.posts.loaded?
+ assert_predicate david.posts, :loaded?
david.posts.reset
- assert !david.posts.loaded?
+ assert_not_predicate david.posts, :loaded?
end
end
diff --git a/activerecord/test/cases/attribute_decorators_test.rb b/activerecord/test/cases/attribute_decorators_test.rb
index cfa6ed1da6..42eca233ce 100644
--- a/activerecord/test/cases/attribute_decorators_test.rb
+++ b/activerecord/test/cases/attribute_decorators_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
module ActiveRecord
diff --git a/activerecord/test/cases/attribute_methods/read_test.rb b/activerecord/test/cases/attribute_methods/read_test.rb
index 1fc63a49d4..54512068ee 100644
--- a/activerecord/test/cases/attribute_methods/read_test.rb
+++ b/activerecord/test/cases/attribute_methods/read_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
module ActiveRecord
@@ -8,11 +10,10 @@ module ActiveRecord
end
def setup
- @klass = Class.new do
+ @klass = Class.new(Class.new { def self.initialize_generated_modules; end }) do
def self.superclass; Base; end
- def self.base_class; self; end
+ def self.base_class?; true; end
def self.decorate_matching_attribute_types(*); end
- def self.initialize_generated_modules; end
include ActiveRecord::DefineCallbacks
include ActiveRecord::AttributeMethods
diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb
index 4d24a980dc..0bfd46a522 100644
--- a/activerecord/test/cases/attribute_methods_test.rb
+++ b/activerecord/test/cases/attribute_methods_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/minimalistic"
require "models/developer"
@@ -61,8 +63,8 @@ class AttributeMethodsTest < ActiveRecord::TestCase
t.author_name = ""
assert t.attribute_present?("title")
assert t.attribute_present?("written_on")
- assert !t.attribute_present?("content")
- assert !t.attribute_present?("author_name")
+ assert_not t.attribute_present?("content")
+ assert_not t.attribute_present?("author_name")
end
test "attribute_present with booleans" do
@@ -75,7 +77,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
assert b2.attribute_present?(:value)
b3 = Boolean.new
- assert !b3.attribute_present?(:value)
+ assert_not b3.attribute_present?(:value)
b4 = Boolean.new
b4.value = false
@@ -97,8 +99,8 @@ class AttributeMethodsTest < ActiveRecord::TestCase
end
test "boolean attributes" do
- assert !Topic.find(1).approved?
- assert Topic.find(2).approved?
+ assert_not_predicate Topic.find(1), :approved?
+ assert_predicate Topic.find(2), :approved?
end
test "set attributes" do
@@ -140,16 +142,16 @@ class AttributeMethodsTest < ActiveRecord::TestCase
assert_respond_to topic, :title=
assert_respond_to topic, "author_name"
assert_respond_to topic, "attribute_names"
- assert !topic.respond_to?("nothingness")
- assert !topic.respond_to?(:nothingness)
+ assert_not_respond_to topic, "nothingness"
+ assert_not_respond_to topic, :nothingness
end
test "respond_to? with a custom primary key" do
keyboard = Keyboard.create
assert_not_nil keyboard.key_number
assert_equal keyboard.key_number, keyboard.id
- assert keyboard.respond_to?("key_number")
- assert keyboard.respond_to?("id")
+ assert_respond_to keyboard, "key_number"
+ assert_respond_to keyboard, "id"
end
test "id_before_type_cast with a custom primary key" do
@@ -161,19 +163,6 @@ class AttributeMethodsTest < ActiveRecord::TestCase
assert_equal "10", keyboard.read_attribute_before_type_cast(:key_number)
end
- # Syck calls respond_to? before actually calling initialize.
- test "respond_to? with an allocated object" do
- klass = Class.new(ActiveRecord::Base) do
- self.table_name = "topics"
- end
-
- topic = klass.allocate
- assert !topic.respond_to?("nothingness")
- assert !topic.respond_to?(:nothingness)
- assert_respond_to topic, "title"
- assert_respond_to topic, :title
- end
-
# IRB inspects the return value of MyModel.allocate.
test "allocated objects can be inspected" do
topic = Topic.allocate
@@ -198,12 +187,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
if current_adapter?(:Mysql2Adapter)
test "read attributes_before_type_cast on a boolean" do
bool = Boolean.create!("value" => false)
- if RUBY_PLATFORM.include?("java")
- # JRuby will return the value before typecast as string.
- assert_equal "0", bool.reload.attributes_before_type_cast["value"]
- else
- assert_equal 0, bool.reload.attributes_before_type_cast["value"]
- end
+ assert_equal 0, bool.reload.attributes_before_type_cast["value"]
end
end
@@ -357,9 +341,9 @@ class AttributeMethodsTest < ActiveRecord::TestCase
test "read_attribute when false" do
topic = topics(:first)
topic.approved = false
- assert !topic.approved?, "approved should be false"
+ assert_not topic.approved?, "approved should be false"
topic.approved = "false"
- assert !topic.approved?, "approved should be false"
+ assert_not topic.approved?, "approved should be false"
end
test "read_attribute when true" do
@@ -373,10 +357,10 @@ class AttributeMethodsTest < ActiveRecord::TestCase
test "boolean attributes writing and reading" do
topic = Topic.new
topic.approved = "false"
- assert !topic.approved?, "approved should be false"
+ assert_not topic.approved?, "approved should be false"
topic.approved = "false"
- assert !topic.approved?, "approved should be false"
+ assert_not topic.approved?, "approved should be false"
topic.approved = "true"
assert topic.approved?, "approved should be true"
@@ -460,30 +444,30 @@ class AttributeMethodsTest < ActiveRecord::TestCase
SQL
assert_equal "Firm", object.string_value
- assert object.string_value?
+ assert_predicate object, :string_value?
object.string_value = " "
- assert !object.string_value?
+ assert_not_predicate object, :string_value?
assert_equal 1, object.int_value.to_i
- assert object.int_value?
+ assert_predicate object, :int_value?
object.int_value = "0"
- assert !object.int_value?
+ assert_not_predicate object, :int_value?
end
test "non-attribute read and write" do
topic = Topic.new
- assert !topic.respond_to?("mumbo")
+ assert_not_respond_to topic, "mumbo"
assert_raise(NoMethodError) { topic.mumbo }
assert_raise(NoMethodError) { topic.mumbo = 5 }
end
test "undeclared attribute method does not affect respond_to? and method_missing" do
topic = @target.new(title: "Budget")
- assert topic.respond_to?("title")
+ assert_respond_to topic, "title"
assert_equal "Budget", topic.title
- assert !topic.respond_to?("title_hello_world")
+ assert_not_respond_to topic, "title_hello_world"
assert_raise(NoMethodError) { topic.title_hello_world }
end
@@ -494,7 +478,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
@target.attribute_method_prefix prefix
meth = "#{prefix}title"
- assert topic.respond_to?(meth)
+ assert_respond_to topic, 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)
@@ -508,7 +492,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
topic = @target.new(title: "Budget")
meth = "title#{suffix}"
- assert topic.respond_to?(meth)
+ assert_respond_to topic, 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)
@@ -522,7 +506,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
topic = @target.new(title: "Budget")
meth = "#{prefix}title#{suffix}"
- assert topic.respond_to?(meth)
+ assert_respond_to topic, 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)
@@ -544,7 +528,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
else
topic = Topic.all.merge!(select: "topics.*, 1=2 as is_test").first
end
- assert !topic.is_test?
+ assert_not_predicate topic, :is_test?
end
test "typecast attribute from select to true" do
@@ -555,7 +539,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
else
topic = Topic.all.merge!(select: "topics.*, 2=2 as is_test").first
end
- assert topic.is_test?
+ assert_predicate topic, :is_test?
end
test "raises ActiveRecord::DangerousAttributeError when defining an AR method in a model" do
@@ -739,6 +723,16 @@ class AttributeMethodsTest < ActiveRecord::TestCase
end
end
+ test "setting invalid string to a zone-aware time attribute" do
+ in_time_zone "Pacific Time (US & Canada)" do
+ record = @target.new
+ time_string = "ABC"
+
+ record.bonus_time = time_string
+ assert_nil record.bonus_time
+ end
+ end
+
test "removing time zone-aware types" do
with_time_zone_aware_types(:datetime) do
in_time_zone "Pacific Time (US & Canada)" do
@@ -746,7 +740,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
expected_time = Time.utc(2000, 01, 01, 10)
assert_equal expected_time, record.bonus_time
- assert record.bonus_time.utc?
+ assert_predicate record.bonus_time, :utc?
end
end
end
@@ -770,7 +764,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
privatize("title")
topic = @target.new(title: "The pros and cons of programming naked.")
- assert !topic.respond_to?(:title)
+ assert_not_respond_to topic, :title
exception = assert_raise(NoMethodError) { topic.title }
assert_includes exception.message, "private method"
assert_equal "I'm private", topic.send(:title)
@@ -780,7 +774,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
privatize("title=(value)")
topic = @target.new
- assert !topic.respond_to?(:title=)
+ assert_not_respond_to topic, :title=
exception = assert_raise(NoMethodError) { topic.title = "Pants" }
assert_includes exception.message, "private method"
topic.send(:title=, "Very large pants")
@@ -790,7 +784,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
privatize("title?")
topic = @target.new(title: "Isaac Newton's pants")
- assert !topic.respond_to?(:title?)
+ assert_not_respond_to topic, :title?
exception = assert_raise(NoMethodError) { topic.title? }
assert_includes exception.message, "private method"
assert topic.send(:title?)
@@ -830,7 +824,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
self.table_name = "computers"
end
- assert !klass.instance_method_already_implemented?(:system)
+ assert_not klass.instance_method_already_implemented?(:system)
computer = klass.new
assert_nil computer.system
end
@@ -844,8 +838,8 @@ class AttributeMethodsTest < ActiveRecord::TestCase
self.table_name = "computers"
end
- assert !klass.instance_method_already_implemented?(:system)
- assert !subklass.instance_method_already_implemented?(:system)
+ assert_not klass.instance_method_already_implemented?(:system)
+ assert_not subklass.instance_method_already_implemented?(:system)
computer = subklass.new
assert_nil computer.system
end
@@ -982,9 +976,9 @@ class AttributeMethodsTest < ActiveRecord::TestCase
test "came_from_user?" do
model = @target.first
- assert_not model.id_came_from_user?
+ assert_not_predicate model, :id_came_from_user?
model.id = "omg"
- assert model.id_came_from_user?
+ assert_predicate model, :id_came_from_user?
end
test "accessed_fields" do
@@ -997,6 +991,11 @@ class AttributeMethodsTest < ActiveRecord::TestCase
assert_equal ["title"], model.accessed_fields
end
+ test "generated attribute methods ancestors have correct class" do
+ mod = Topic.send(:generated_attribute_methods)
+ assert_match %r(GeneratedAttributeMethods), mod.inspect
+ end
+
private
def new_topic_like_ar_class(&block)
@@ -1005,7 +1004,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
class_eval(&block)
end
- assert_empty klass.generated_attribute_methods.instance_methods(false)
+ assert_empty klass.send(:generated_attribute_methods).instance_methods(false)
klass
end
@@ -1017,14 +1016,6 @@ class AttributeMethodsTest < ActiveRecord::TestCase
ActiveRecord::Base.time_zone_aware_types = old_types
end
- def cached_columns
- Topic.columns.map(&:name)
- end
-
- def time_related_columns_on_topic
- Topic.columns.select { |c| [:time, :date, :datetime, :timestamp].include?(c.type) }
- end
-
def privatize(method_signature)
@target.class_eval(<<-private_method, __FILE__, __LINE__ + 1)
private
diff --git a/activerecord/test/cases/attribute_set_test.rb b/activerecord/test/cases/attribute_set_test.rb
deleted file mode 100644
index bd4b200735..0000000000
--- a/activerecord/test/cases/attribute_set_test.rb
+++ /dev/null
@@ -1,253 +0,0 @@
-require "cases/helper"
-
-module ActiveRecord
- class AttributeSetTest < ActiveRecord::TestCase
- test "building a new set from raw attributes" do
- builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::Float.new)
- attributes = builder.build_from_database(foo: "1.1", bar: "2.2")
-
- assert_equal 1, attributes[:foo].value
- assert_equal 2.2, attributes[:bar].value
- assert_equal :foo, attributes[:foo].name
- assert_equal :bar, attributes[:bar].name
- end
-
- test "building with custom types" do
- builder = AttributeSet::Builder.new(foo: Type::Float.new)
- attributes = builder.build_from_database({ foo: "3.3", bar: "4.4" }, bar: Type::Integer.new)
-
- assert_equal 3.3, attributes[:foo].value
- assert_equal 4, attributes[:bar].value
- end
-
- test "[] returns a null object" do
- builder = AttributeSet::Builder.new(foo: Type::Float.new)
- attributes = builder.build_from_database(foo: "3.3")
-
- assert_equal "3.3", attributes[:foo].value_before_type_cast
- assert_nil attributes[:bar].value_before_type_cast
- assert_equal :bar, attributes[:bar].name
- end
-
- test "duping creates a new hash, but does not dup the attributes" do
- builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::String.new)
- attributes = builder.build_from_database(foo: 1, bar: "foo")
-
- # Ensure the type cast value is cached
- attributes[:foo].value
- attributes[:bar].value
-
- duped = attributes.dup
- duped.write_from_database(:foo, 2)
- duped[:bar].value << "bar"
-
- assert_equal 1, attributes[:foo].value
- assert_equal 2, duped[:foo].value
- assert_equal "foobar", attributes[:bar].value
- assert_equal "foobar", duped[:bar].value
- end
-
- test "deep_duping creates a new hash and dups each attribute" do
- builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::String.new)
- attributes = builder.build_from_database(foo: 1, bar: "foo")
-
- # Ensure the type cast value is cached
- attributes[:foo].value
- attributes[:bar].value
-
- duped = attributes.deep_dup
- duped.write_from_database(:foo, 2)
- duped[:bar].value << "bar"
-
- assert_equal 1, attributes[:foo].value
- assert_equal 2, duped[:foo].value
- assert_equal "foo", attributes[:bar].value
- assert_equal "foobar", duped[:bar].value
- end
-
- test "freezing cloned set does not freeze original" do
- attributes = AttributeSet.new({})
- clone = attributes.clone
-
- clone.freeze
-
- assert clone.frozen?
- assert_not attributes.frozen?
- end
-
- test "to_hash returns a hash of the type cast values" do
- builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::Float.new)
- attributes = builder.build_from_database(foo: "1.1", bar: "2.2")
-
- assert_equal({ foo: 1, bar: 2.2 }, attributes.to_hash)
- assert_equal({ foo: 1, bar: 2.2 }, attributes.to_h)
- end
-
- test "to_hash maintains order" do
- builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::Float.new)
- attributes = builder.build_from_database(foo: "2.2", bar: "3.3")
-
- attributes[:bar]
- hash = attributes.to_h
-
- assert_equal [[:foo, 2], [:bar, 3.3]], hash.to_a
- end
-
- test "values_before_type_cast" do
- builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::Integer.new)
- attributes = builder.build_from_database(foo: "1.1", bar: "2.2")
-
- assert_equal({ foo: "1.1", bar: "2.2" }, attributes.values_before_type_cast)
- end
-
- test "known columns are built with uninitialized attributes" do
- attributes = attributes_with_uninitialized_key
- assert attributes[:foo].initialized?
- assert_not attributes[:bar].initialized?
- end
-
- test "uninitialized attributes are not included in the attributes hash" do
- attributes = attributes_with_uninitialized_key
- assert_equal({ foo: 1 }, attributes.to_hash)
- end
-
- test "uninitialized attributes are not included in keys" do
- attributes = attributes_with_uninitialized_key
- assert_equal [:foo], attributes.keys
- end
-
- test "uninitialized attributes return false for key?" do
- attributes = attributes_with_uninitialized_key
- assert attributes.key?(:foo)
- assert_not attributes.key?(:bar)
- end
-
- test "unknown attributes return false for key?" do
- attributes = attributes_with_uninitialized_key
- assert_not attributes.key?(:wibble)
- end
-
- test "fetch_value returns the value for the given initialized attribute" do
- builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::Float.new)
- attributes = builder.build_from_database(foo: "1.1", bar: "2.2")
-
- assert_equal 1, attributes.fetch_value(:foo)
- assert_equal 2.2, attributes.fetch_value(:bar)
- end
-
- test "fetch_value returns nil for unknown attributes" do
- attributes = attributes_with_uninitialized_key
- assert_nil attributes.fetch_value(:wibble) { "hello" }
- end
-
- test "fetch_value returns nil for unknown attributes when types has a default" do
- types = Hash.new(Type::Value.new)
- builder = AttributeSet::Builder.new(types)
- attributes = builder.build_from_database
-
- assert_nil attributes.fetch_value(:wibble) { "hello" }
- end
-
- test "fetch_value uses the given block for uninitialized attributes" do
- attributes = attributes_with_uninitialized_key
- value = attributes.fetch_value(:bar) { |n| n.to_s + "!" }
- assert_equal "bar!", value
- end
-
- test "fetch_value returns nil for uninitialized attributes if no block is given" do
- attributes = attributes_with_uninitialized_key
- assert_nil attributes.fetch_value(:bar)
- end
-
- test "the primary_key is always initialized" do
- builder = AttributeSet::Builder.new({ foo: Type::Integer.new }, :foo)
- attributes = builder.build_from_database
-
- assert attributes.key?(:foo)
- assert_equal [:foo], attributes.keys
- assert attributes[:foo].initialized?
- end
-
- class MyType
- def cast(value)
- return if value.nil?
- value + " from user"
- end
-
- def deserialize(value)
- return if value.nil?
- value + " from database"
- end
-
- def assert_valid_value(*)
- end
- end
-
- test "write_from_database sets the attribute with database typecasting" do
- builder = AttributeSet::Builder.new(foo: MyType.new)
- attributes = builder.build_from_database
-
- assert_nil attributes.fetch_value(:foo)
-
- attributes.write_from_database(:foo, "value")
-
- assert_equal "value from database", attributes.fetch_value(:foo)
- end
-
- test "write_from_user sets the attribute with user typecasting" do
- builder = AttributeSet::Builder.new(foo: MyType.new)
- attributes = builder.build_from_database
-
- assert_nil attributes.fetch_value(:foo)
-
- attributes.write_from_user(:foo, "value")
-
- assert_equal "value from user", attributes.fetch_value(:foo)
- end
-
- def attributes_with_uninitialized_key
- builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::Float.new)
- builder.build_from_database(foo: "1.1")
- end
-
- test "freezing doesn't prevent the set from materializing" do
- builder = AttributeSet::Builder.new(foo: Type::String.new)
- attributes = builder.build_from_database(foo: "1")
-
- attributes.freeze
- assert_equal({ foo: "1" }, attributes.to_hash)
- end
-
- test "#accessed_attributes returns only attributes which have been read" do
- builder = AttributeSet::Builder.new(foo: Type::Value.new, bar: Type::Value.new)
- attributes = builder.build_from_database(foo: "1", bar: "2")
-
- assert_equal [], attributes.accessed
-
- attributes.fetch_value(:foo)
-
- assert_equal [:foo], attributes.accessed
- end
-
- test "#map returns a new attribute set with the changes applied" do
- builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::Integer.new)
- attributes = builder.build_from_database(foo: "1", bar: "2")
- new_attributes = attributes.map do |attr|
- attr.with_cast_value(attr.value + 1)
- end
-
- assert_equal 2, new_attributes.fetch_value(:foo)
- assert_equal 3, new_attributes.fetch_value(:bar)
- end
-
- test "comparison for equality is correctly implemented" do
- builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::Integer.new)
- attributes = builder.build_from_database(foo: "1", bar: "2")
- attributes2 = builder.build_from_database(foo: "1", bar: "2")
- attributes3 = builder.build_from_database(foo: "2", bar: "2")
-
- assert_equal attributes, attributes2
- assert_not_equal attributes2, attributes3
- end
- end
-end
diff --git a/activerecord/test/cases/attribute_test.rb b/activerecord/test/cases/attribute_test.rb
deleted file mode 100644
index 7cf6b498c9..0000000000
--- a/activerecord/test/cases/attribute_test.rb
+++ /dev/null
@@ -1,253 +0,0 @@
-require "cases/helper"
-
-module ActiveRecord
- class AttributeTest < ActiveRecord::TestCase
- setup do
- @type = Minitest::Mock.new
- end
-
- teardown do
- assert @type.verify
- end
-
- test "from_database + read type casts from database" do
- @type.expect(:deserialize, "type cast from database", ["a value"])
- attribute = Attribute.from_database(nil, "a value", @type)
-
- type_cast_value = attribute.value
-
- assert_equal "type cast from database", type_cast_value
- end
-
- test "from_user + read type casts from user" do
- @type.expect(:cast, "type cast from user", ["a value"])
- attribute = Attribute.from_user(nil, "a value", @type)
-
- type_cast_value = attribute.value
-
- assert_equal "type cast from user", type_cast_value
- end
-
- test "reading memoizes the value" do
- @type.expect(:deserialize, "from the database", ["whatever"])
- attribute = Attribute.from_database(nil, "whatever", @type)
-
- type_cast_value = attribute.value
- second_read = attribute.value
-
- assert_equal "from the database", type_cast_value
- assert_same type_cast_value, second_read
- end
-
- test "reading memoizes falsy values" do
- @type.expect(:deserialize, false, ["whatever"])
- attribute = Attribute.from_database(nil, "whatever", @type)
-
- attribute.value
- attribute.value
- end
-
- test "read_before_typecast returns the given value" do
- attribute = Attribute.from_database(nil, "raw value", @type)
-
- raw_value = attribute.value_before_type_cast
-
- assert_equal "raw value", raw_value
- end
-
- test "from_database + read_for_database type casts to and from database" do
- @type.expect(:deserialize, "read from database", ["whatever"])
- @type.expect(:serialize, "ready for database", ["read from database"])
- attribute = Attribute.from_database(nil, "whatever", @type)
-
- serialize = attribute.value_for_database
-
- assert_equal "ready for database", serialize
- end
-
- test "from_user + read_for_database type casts from the user to the database" do
- @type.expect(:cast, "read from user", ["whatever"])
- @type.expect(:serialize, "ready for database", ["read from user"])
- attribute = Attribute.from_user(nil, "whatever", @type)
-
- serialize = attribute.value_for_database
-
- assert_equal "ready for database", serialize
- end
-
- test "duping dups the value" do
- @type.expect(:deserialize, "type cast", ["a value"])
- attribute = Attribute.from_database(nil, "a value", @type)
-
- value_from_orig = attribute.value
- value_from_clone = attribute.dup.value
- value_from_orig << " foo"
-
- assert_equal "type cast foo", value_from_orig
- assert_equal "type cast", value_from_clone
- end
-
- test "duping does not dup the value if it is not dupable" do
- @type.expect(:deserialize, false, ["a value"])
- attribute = Attribute.from_database(nil, "a value", @type)
-
- assert_same attribute.value, attribute.dup.value
- end
-
- test "duping does not eagerly type cast if we have not yet type cast" do
- attribute = Attribute.from_database(nil, "a value", @type)
- attribute.dup
- end
-
- class MyType
- def cast(value)
- value + " from user"
- end
-
- def deserialize(value)
- value + " from database"
- end
-
- def assert_valid_value(*)
- end
- end
-
- test "with_value_from_user returns a new attribute with the value from the user" do
- old = Attribute.from_database(nil, "old", MyType.new)
- new = old.with_value_from_user("new")
-
- assert_equal "old from database", old.value
- assert_equal "new from user", new.value
- end
-
- test "with_value_from_database returns a new attribute with the value from the database" do
- old = Attribute.from_user(nil, "old", MyType.new)
- new = old.with_value_from_database("new")
-
- assert_equal "old from user", old.value
- assert_equal "new from database", new.value
- end
-
- test "uninitialized attributes yield their name if a block is given to value" do
- block = proc { |name| name.to_s + "!" }
- foo = Attribute.uninitialized(:foo, nil)
- bar = Attribute.uninitialized(:bar, nil)
-
- assert_equal "foo!", foo.value(&block)
- assert_equal "bar!", bar.value(&block)
- end
-
- test "uninitialized attributes have no value" do
- assert_nil Attribute.uninitialized(:foo, nil).value
- end
-
- test "attributes equal other attributes with the same constructor arguments" do
- first = Attribute.from_database(:foo, 1, Type::Integer.new)
- second = Attribute.from_database(:foo, 1, Type::Integer.new)
- assert_equal first, second
- end
-
- test "attributes do not equal attributes with different names" do
- first = Attribute.from_database(:foo, 1, Type::Integer.new)
- second = Attribute.from_database(:bar, 1, Type::Integer.new)
- assert_not_equal first, second
- end
-
- test "attributes do not equal attributes with different types" do
- first = Attribute.from_database(:foo, 1, Type::Integer.new)
- second = Attribute.from_database(:foo, 1, Type::Float.new)
- assert_not_equal first, second
- end
-
- test "attributes do not equal attributes with different values" do
- first = Attribute.from_database(:foo, 1, Type::Integer.new)
- second = Attribute.from_database(:foo, 2, Type::Integer.new)
- assert_not_equal first, second
- end
-
- test "attributes do not equal attributes of other classes" do
- first = Attribute.from_database(:foo, 1, Type::Integer.new)
- second = Attribute.from_user(:foo, 1, Type::Integer.new)
- assert_not_equal first, second
- end
-
- test "an attribute has not been read by default" do
- attribute = Attribute.from_database(:foo, 1, Type::Value.new)
- assert_not attribute.has_been_read?
- end
-
- test "an attribute has been read when its value is calculated" do
- attribute = Attribute.from_database(:foo, 1, Type::Value.new)
- attribute.value
- assert attribute.has_been_read?
- end
-
- test "an attribute is not changed if it hasn't been assigned or mutated" do
- attribute = Attribute.from_database(:foo, 1, Type::Value.new)
-
- refute attribute.changed?
- end
-
- test "an attribute is changed if it's been assigned a new value" do
- attribute = Attribute.from_database(:foo, 1, Type::Value.new)
- changed = attribute.with_value_from_user(2)
-
- assert changed.changed?
- end
-
- test "an attribute is not changed if it's assigned the same value" do
- attribute = Attribute.from_database(:foo, 1, Type::Value.new)
- unchanged = attribute.with_value_from_user(1)
-
- refute unchanged.changed?
- end
-
- test "an attribute can not be mutated if it has not been read,
- and skips expensive calculations" do
- type_which_raises_from_all_methods = Object.new
- attribute = Attribute.from_database(:foo, "bar", type_which_raises_from_all_methods)
-
- assert_not attribute.changed_in_place?
- end
-
- test "an attribute is changed if it has been mutated" do
- attribute = Attribute.from_database(:foo, "bar", Type::String.new)
- attribute.value << "!"
-
- assert attribute.changed_in_place?
- assert attribute.changed?
- end
-
- test "an attribute can forget its changes" do
- attribute = Attribute.from_database(:foo, "bar", Type::String.new)
- changed = attribute.with_value_from_user("foo")
- forgotten = changed.forgetting_assignment
-
- assert changed.changed? # sanity check
- refute forgotten.changed?
- end
-
- test "with_value_from_user validates the value" do
- type = Type::Value.new
- type.define_singleton_method(:assert_valid_value) do |value|
- if value == 1
- raise ArgumentError
- end
- end
-
- attribute = Attribute.from_database(:foo, 1, type)
- assert_equal 1, attribute.value
- assert_equal 2, attribute.with_value_from_user(2).value
- assert_raises ArgumentError do
- attribute.with_value_from_user(1)
- end
- end
-
- test "with_type preserves mutations" do
- attribute = Attribute.from_database(:foo, "", Type::Value.new)
- attribute.value << "1"
-
- assert_equal 1, attribute.with_type(Type::Integer.new).value
- end
- end
-end
diff --git a/activerecord/test/cases/attributes_test.rb b/activerecord/test/cases/attributes_test.rb
index 3705a6be89..3bc56694be 100644
--- a/activerecord/test/cases/attributes_test.rb
+++ b/activerecord/test/cases/attributes_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
class OverloadedType < ActiveRecord::Base
@@ -57,7 +59,7 @@ module ActiveRecord
test "nonexistent attribute" do
data = OverloadedType.new(non_existent_decimal: 1)
- assert_equal BigDecimal.new(1), data.non_existent_decimal
+ assert_equal BigDecimal(1), data.non_existent_decimal
assert_raise ActiveRecord::UnknownAttributeError do
UnoverloadedType.new(non_existent_decimal: 1)
end
@@ -106,12 +108,14 @@ module ActiveRecord
assert_equal 6, klass.attribute_types.length
assert_equal 6, klass.column_defaults.length
+ assert_equal 6, klass.attribute_names.length
assert_not klass.attribute_types.include?("wibble")
klass.attribute :wibble, Type::Value.new
assert_equal 7, klass.attribute_types.length
assert_equal 7, klass.column_defaults.length
+ assert_equal 7, klass.attribute_names.length
assert_includes klass.attribute_types, "wibble"
end
@@ -207,7 +211,7 @@ module ActiveRecord
end
test "attributes not backed by database columns are not dirty when unchanged" do
- refute OverloadedType.new.non_existent_decimal_changed?
+ assert_not_predicate OverloadedType.new, :non_existent_decimal_changed?
end
test "attributes not backed by database columns are always initialized" do
@@ -241,13 +245,13 @@ module ActiveRecord
model.foo << "asdf"
assert_equal "lolasdf", model.foo
- assert model.foo_changed?
+ assert_predicate model, :foo_changed?
model.reload
assert_equal "lol", model.foo
model.foo = "lol"
- refute model.changed?
+ assert_not_predicate model, :changed?
end
test "attributes not backed by database columns appear in inspect" do
diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb
index 2203aa1788..fa618735d7 100644
--- a/activerecord/test/cases/autosave_association_test.rb
+++ b/activerecord/test/cases/autosave_association_test.rb
@@ -1,8 +1,11 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/bird"
require "models/post"
require "models/comment"
require "models/company"
+require "models/contract"
require "models/customer"
require "models/developer"
require "models/computer"
@@ -10,9 +13,7 @@ require "models/invoice"
require "models/line_item"
require "models/order"
require "models/parrot"
-require "models/person"
require "models/pirate"
-require "models/reader"
require "models/ship"
require "models/ship_part"
require "models/tag"
@@ -26,6 +27,7 @@ require "models/member_detail"
require "models/organization"
require "models/guitar"
require "models/tuning_peg"
+require "models/reply"
class TestAutosaveAssociationsInGeneral < ActiveRecord::TestCase
def test_autosave_validation
@@ -49,8 +51,8 @@ class TestAutosaveAssociationsInGeneral < ActiveRecord::TestCase
}
u = person.create!(first_name: "cool")
- u.update_attributes!(first_name: "nah") # still valid because validation only applies on 'create'
- assert reference.create!(person: u).persisted?
+ u.update!(first_name: "nah") # still valid because validation only applies on 'create'
+ assert_predicate reference.create!(person: u), :persisted?
end
def test_should_not_add_the_same_callbacks_multiple_times_for_has_one
@@ -73,7 +75,7 @@ class TestAutosaveAssociationsInGeneral < ActiveRecord::TestCase
ship = ShipWithoutNestedAttributes.new
ship.prisoners.build
- assert_not ship.valid?
+ assert_not_predicate ship, :valid?
assert_equal 1, ship.errors[:name].length
end
@@ -98,35 +100,35 @@ class TestDefaultAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCas
def test_should_save_parent_but_not_invalid_child
firm = Firm.new(name: "GlobalMegaCorp")
- assert firm.valid?
+ assert_predicate firm, :valid?
firm.build_account_using_primary_key
- assert !firm.build_account_using_primary_key.valid?
+ assert_not_predicate firm.build_account_using_primary_key, :valid?
assert firm.save
- assert !firm.account_using_primary_key.persisted?
+ assert_not_predicate firm.account_using_primary_key, :persisted?
end
def test_save_fails_for_invalid_has_one
firm = Firm.first
- assert firm.valid?
+ assert_predicate firm, :valid?
firm.build_account
- assert !firm.account.valid?
- assert !firm.valid?
- assert !firm.save
+ assert_not_predicate firm.account, :valid?
+ assert_not_predicate firm, :valid?
+ assert_not firm.save
assert_equal ["is invalid"], firm.errors["account"]
end
def test_save_succeeds_for_invalid_has_one_with_validate_false
firm = Firm.first
- assert firm.valid?
+ assert_predicate firm, :valid?
firm.build_unvalidated_account
- assert !firm.unvalidated_account.valid?
- assert firm.valid?
+ assert_not_predicate firm.unvalidated_account, :valid?
+ assert_predicate firm, :valid?
assert firm.save
end
@@ -135,10 +137,10 @@ class TestDefaultAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCas
account = firm.build_account("credit_limit" => 1000)
assert_equal account, firm.account
- assert !account.persisted?
+ assert_not_predicate account, :persisted?
assert firm.save
assert_equal account, firm.account
- assert account.persisted?
+ assert_predicate account, :persisted?
end
def test_build_before_either_saved
@@ -146,16 +148,16 @@ class TestDefaultAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCas
firm.account = account = Account.new("credit_limit" => 1000)
assert_equal account, firm.account
- assert !account.persisted?
+ assert_not_predicate account, :persisted?
assert firm.save
assert_equal account, firm.account
- assert account.persisted?
+ assert_predicate account, :persisted?
end
def test_assignment_before_parent_saved
firm = Firm.new("name" => "GlobalMegaCorp")
firm.account = a = Account.find(1)
- assert !firm.persisted?
+ assert_not_predicate firm, :persisted?
assert_equal a, firm.account
assert firm.save
assert_equal a, firm.account
@@ -166,12 +168,12 @@ class TestDefaultAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCas
def test_assignment_before_either_saved
firm = Firm.new("name" => "GlobalMegaCorp")
firm.account = a = Account.new("credit_limit" => 1000)
- assert !firm.persisted?
- assert !a.persisted?
+ assert_not_predicate firm, :persisted?
+ assert_not_predicate a, :persisted?
assert_equal a, firm.account
assert firm.save
- assert firm.persisted?
- assert a.persisted?
+ assert_predicate firm, :persisted?
+ assert_predicate a, :persisted?
assert_equal a, firm.account
firm.association(:account).reload
assert_equal a, firm.account
@@ -220,13 +222,13 @@ class TestDefaultAutosaveAssociationOnABelongsToAssociation < ActiveRecord::Test
def test_should_save_parent_but_not_invalid_child
client = Client.new(name: "Joe (the Plumber)")
- assert client.valid?
+ assert_predicate client, :valid?
client.build_firm
- assert !client.firm.valid?
+ assert_not_predicate client.firm, :valid?
assert client.save
- assert !client.firm.persisted?
+ assert_not_predicate client.firm, :persisted?
end
def test_save_fails_for_invalid_belongs_to
@@ -234,9 +236,9 @@ class TestDefaultAutosaveAssociationOnABelongsToAssociation < ActiveRecord::Test
assert log = AuditLog.create(developer_id: 0, message: " ")
log.developer = Developer.new
- assert !log.developer.valid?
- assert !log.valid?
- assert !log.save
+ assert_not_predicate log.developer, :valid?
+ assert_not_predicate log, :valid?
+ assert_not log.save
assert_equal ["is invalid"], log.errors["developer"]
end
@@ -245,8 +247,8 @@ class TestDefaultAutosaveAssociationOnABelongsToAssociation < ActiveRecord::Test
assert log = AuditLog.create(developer_id: 0, message: " ")
log.unvalidated_developer = Developer.new
- assert !log.unvalidated_developer.valid?
- assert log.valid?
+ assert_not_predicate log.unvalidated_developer, :valid?
+ assert_predicate log, :valid?
assert log.save
end
@@ -255,10 +257,10 @@ class TestDefaultAutosaveAssociationOnABelongsToAssociation < ActiveRecord::Test
apple = Firm.new("name" => "Apple")
client.firm = apple
assert_equal apple, client.firm
- assert !apple.persisted?
+ assert_not_predicate apple, :persisted?
assert client.save
assert apple.save
- assert apple.persisted?
+ assert_predicate apple, :persisted?
assert_equal apple, client.firm
client.association(:firm).reload
assert_equal apple, client.firm
@@ -268,11 +270,11 @@ class TestDefaultAutosaveAssociationOnABelongsToAssociation < ActiveRecord::Test
final_cut = Client.new("name" => "Final Cut")
apple = Firm.new("name" => "Apple")
final_cut.firm = apple
- assert !final_cut.persisted?
- assert !apple.persisted?
+ assert_not_predicate final_cut, :persisted?
+ assert_not_predicate apple, :persisted?
assert final_cut.save
- assert final_cut.persisted?
- assert apple.persisted?
+ assert_predicate final_cut, :persisted?
+ assert_predicate apple, :persisted?
assert_equal apple, final_cut.firm
final_cut.association(:firm).reload
assert_equal apple, final_cut.firm
@@ -381,7 +383,7 @@ class TestDefaultAutosaveAssociationOnABelongsToAssociation < ActiveRecord::Test
auditlog.developer = invalid_developer
auditlog.developer_id = valid_developer.id
- assert auditlog.valid?
+ assert_predicate auditlog, :valid?
end
end
@@ -394,8 +396,8 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociationWithAcceptsNestedAttrib
molecule.electrons = [valid_electron, invalid_electron]
molecule.save
- assert_not invalid_electron.valid?
- assert valid_electron.valid?
+ assert_not_predicate invalid_electron, :valid?
+ assert_predicate valid_electron, :valid?
assert_not molecule.persisted?, "Molecule should not be persisted when its electrons are invalid"
end
@@ -407,9 +409,9 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociationWithAcceptsNestedAttrib
guitar.tuning_pegs = [tuning_peg_valid, tuning_peg_invalid]
- assert_not tuning_peg_invalid.valid?
- assert tuning_peg_valid.valid?
- assert_not guitar.valid?
+ assert_not_predicate tuning_peg_invalid, :valid?
+ assert_predicate tuning_peg_valid, :valid?
+ assert_not_predicate guitar, :valid?
assert_equal ["is not a number"], guitar.errors["tuning_pegs[1].pitch"]
assert_not_equal ["is not a number"], guitar.errors["tuning_pegs.pitch"]
end
@@ -424,9 +426,9 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociationWithAcceptsNestedAttrib
molecule.electrons = [valid_electron, invalid_electron]
- assert_not invalid_electron.valid?
- assert valid_electron.valid?
- assert_not molecule.valid?
+ assert_not_predicate invalid_electron, :valid?
+ assert_predicate valid_electron, :valid?
+ assert_not_predicate molecule, :valid?
assert_equal ["can't be blank"], molecule.errors["electrons[1].name"]
assert_not_equal ["can't be blank"], molecule.errors["electrons.name"]
ensure
@@ -440,9 +442,9 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociationWithAcceptsNestedAttrib
molecule.electrons = [valid_electron, invalid_electron]
- assert_not invalid_electron.valid?
- assert valid_electron.valid?
- assert_not molecule.valid?
+ assert_not_predicate invalid_electron, :valid?
+ assert_predicate valid_electron, :valid?
+ assert_not_predicate molecule, :valid?
assert_equal [{ error: :blank }], molecule.errors.details[:"electrons.name"]
end
@@ -454,9 +456,9 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociationWithAcceptsNestedAttrib
guitar.tuning_pegs = [tuning_peg_valid, tuning_peg_invalid]
- assert_not tuning_peg_invalid.valid?
- assert tuning_peg_valid.valid?
- assert_not guitar.valid?
+ assert_not_predicate tuning_peg_invalid, :valid?
+ assert_predicate tuning_peg_valid, :valid?
+ assert_not_predicate guitar, :valid?
assert_equal [{ error: :not_a_number, value: nil }], guitar.errors.details[:"tuning_pegs[1].pitch"]
assert_equal [], guitar.errors.details[:"tuning_pegs.pitch"]
end
@@ -471,9 +473,9 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociationWithAcceptsNestedAttrib
molecule.electrons = [valid_electron, invalid_electron]
- assert_not invalid_electron.valid?
- assert valid_electron.valid?
- assert_not molecule.valid?
+ assert_not_predicate invalid_electron, :valid?
+ assert_predicate valid_electron, :valid?
+ assert_not_predicate molecule, :valid?
assert_equal [{ error: :blank }], molecule.errors.details[:"electrons[1].name"]
assert_equal [], molecule.errors.details[:"electrons.name"]
ensure
@@ -487,33 +489,45 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociationWithAcceptsNestedAttrib
molecule.electrons = [valid_electron]
molecule.save
- assert valid_electron.valid?
- assert molecule.persisted?
+ assert_predicate valid_electron, :valid?
+ assert_predicate molecule, :persisted?
assert_equal 1, molecule.electrons.count
end
end
class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCase
- fixtures :companies, :people
+ fixtures :companies, :developers
def test_invalid_adding
firm = Firm.find(1)
- assert !(firm.clients_of_firm << c = Client.new)
- assert !c.persisted?
- assert !firm.valid?
- assert !firm.save
- assert !c.persisted?
+ assert_not (firm.clients_of_firm << c = Client.new)
+ assert_not_predicate c, :persisted?
+ assert_not_predicate firm, :valid?
+ assert_not firm.save
+ assert_not_predicate c, :persisted?
end
def test_invalid_adding_before_save
new_firm = Firm.new("name" => "A New Firm, Inc")
new_firm.clients_of_firm.concat([c = Client.new, Client.new("name" => "Apple")])
- assert !c.persisted?
- assert !c.valid?
- assert !new_firm.valid?
- assert !new_firm.save
- assert !c.persisted?
- assert !new_firm.persisted?
+ assert_not_predicate c, :persisted?
+ assert_not_predicate c, :valid?
+ assert_not_predicate new_firm, :valid?
+ assert_not new_firm.save
+ assert_not_predicate c, :persisted?
+ assert_not_predicate new_firm, :persisted?
+ end
+
+ def test_adding_unsavable_association
+ new_firm = Firm.new("name" => "A New Firm, Inc")
+ client = new_firm.clients.new("name" => "Apple")
+ client.throw_on_save = true
+
+ assert_predicate client, :valid?
+ assert_predicate new_firm, :valid?
+ assert_not new_firm.save
+ assert_not_predicate new_firm, :persisted?
+ assert_not_predicate client, :persisted?
end
def test_invalid_adding_with_validate_false
@@ -521,10 +535,10 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa
client = Client.new
firm.unvalidated_clients_of_firm << client
- assert firm.valid?
- assert !client.valid?
+ assert_predicate firm, :valid?
+ assert_not_predicate client, :valid?
assert firm.save
- assert !client.persisted?
+ assert_not_predicate client, :persisted?
end
def test_valid_adding_with_validate_false
@@ -533,24 +547,52 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa
firm = Firm.first
client = Client.new("name" => "Apple")
- assert firm.valid?
- assert client.valid?
- assert !client.persisted?
+ assert_predicate firm, :valid?
+ assert_predicate client, :valid?
+ assert_not_predicate client, :persisted?
firm.unvalidated_clients_of_firm << client
assert firm.save
- assert client.persisted?
+ assert_predicate client, :persisted?
assert_equal no_of_clients + 1, Client.count
end
+ def test_parent_should_save_children_record_with_foreign_key_validation_set_in_before_save_callback
+ company = NewlyContractedCompany.new(name: "test")
+
+ assert company.save
+ assert_not_empty company.reload.new_contracts
+ end
+
+ def test_parent_should_not_get_saved_with_duplicate_children_records
+ assert_no_difference "Reply.count" do
+ assert_no_difference "SillyUniqueReply.count" do
+ reply = Reply.new
+ reply.silly_unique_replies.build([
+ { content: "Best content" },
+ { content: "Best content" }
+ ])
+
+ assert_not reply.save
+ assert_equal ["is invalid"], reply.errors[:silly_unique_replies]
+ assert_empty reply.silly_unique_replies.first.errors
+
+ assert_equal(
+ ["has already been taken"],
+ reply.silly_unique_replies.last.errors[:content]
+ )
+ end
+ end
+ end
+
def test_invalid_build
new_client = companies(:first_firm).clients_of_firm.build
- assert !new_client.persisted?
- assert !new_client.valid?
+ assert_not_predicate new_client, :persisted?
+ assert_not_predicate new_client, :valid?
assert_equal new_client, companies(:first_firm).clients_of_firm.last
- assert !companies(:first_firm).save
- assert !new_client.persisted?
+ assert_not companies(:first_firm).save
+ assert_not_predicate new_client, :persisted?
assert_equal 2, companies(:first_firm).clients_of_firm.reload.size
end
@@ -569,8 +611,8 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa
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.persisted?
- assert c.persisted?
+ assert_predicate new_firm, :persisted?
+ assert_predicate c, :persisted?
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.
@@ -589,22 +631,22 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa
end
def test_assign_ids_for_through_a_belongs_to
- post = Post.new(title: "Assigning IDs works!", body: "You heard it here first, folks!")
- post.person_ids = [people(:david).id, people(:michael).id]
- post.save
- post.reload
- assert_equal 2, post.people.length
- assert_includes post.people, people(:david)
+ firm = Firm.new("name" => "Apple")
+ firm.developer_ids = [developers(:david).id, developers(:jamis).id]
+ firm.save
+ firm.reload
+ assert_equal 2, firm.developers.length
+ assert_includes firm.developers, developers(:david)
end
def test_build_before_save
company = companies(:first_firm)
new_client = assert_no_queries(ignore_none: false) { company.clients_of_firm.build("name" => "Another Client") }
- assert !company.clients_of_firm.loaded?
+ assert_not_predicate company.clients_of_firm, :loaded?
company.name += "-changed"
assert_queries(2) { assert company.save }
- assert new_client.persisted?
+ assert_predicate new_client, :persisted?
assert_equal 3, company.clients_of_firm.reload.size
end
@@ -620,11 +662,11 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa
def test_build_via_block_before_save
company = companies(:first_firm)
new_client = assert_no_queries(ignore_none: false) { company.clients_of_firm.build { |client| client.name = "Another Client" } }
- assert !company.clients_of_firm.loaded?
+ assert_not_predicate company.clients_of_firm, :loaded?
company.name += "-changed"
assert_queries(2) { assert company.save }
- assert new_client.persisted?
+ assert_predicate new_client, :persisted?
assert_equal 3, company.clients_of_firm.reload.size
end
@@ -656,62 +698,62 @@ class TestDefaultAutosaveAssociationOnNewRecord < ActiveRecord::TestCase
new_account = Account.new("credit_limit" => 1000)
new_firm = Firm.new("name" => "some firm")
- assert !new_firm.persisted?
+ assert_not_predicate new_firm, :persisted?
new_account.firm = new_firm
new_account.save!
- assert new_firm.persisted?
+ assert_predicate new_firm, :persisted?
new_account = Account.new("credit_limit" => 1000)
new_autosaved_firm = Firm.new("name" => "some firm")
- assert !new_autosaved_firm.persisted?
+ assert_not_predicate new_autosaved_firm, :persisted?
new_account.unautosaved_firm = new_autosaved_firm
new_account.save!
- assert !new_autosaved_firm.persisted?
+ assert_not_predicate new_autosaved_firm, :persisted?
end
def test_autosave_new_record_on_has_one_can_be_disabled_per_relationship
firm = Firm.new("name" => "some firm")
account = Account.new("credit_limit" => 1000)
- assert !account.persisted?
+ assert_not_predicate account, :persisted?
firm.account = account
firm.save!
- assert account.persisted?
+ assert_predicate account, :persisted?
firm = Firm.new("name" => "some firm")
account = Account.new("credit_limit" => 1000)
firm.unautosaved_account = account
- assert !account.persisted?
+ assert_not_predicate account, :persisted?
firm.unautosaved_account = account
firm.save!
- assert !account.persisted?
+ assert_not_predicate account, :persisted?
end
def test_autosave_new_record_on_has_many_can_be_disabled_per_relationship
firm = Firm.new("name" => "some firm")
account = Account.new("credit_limit" => 1000)
- assert !account.persisted?
+ assert_not_predicate account, :persisted?
firm.accounts << account
firm.save!
- assert account.persisted?
+ assert_predicate account, :persisted?
firm = Firm.new("name" => "some firm")
account = Account.new("credit_limit" => 1000)
- assert !account.persisted?
+ assert_not_predicate account, :persisted?
firm.unautosaved_accounts << account
firm.save!
- assert !account.persisted?
+ assert_not_predicate account, :persisted?
end
def test_autosave_new_record_with_after_create_callback
@@ -744,18 +786,18 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
@pirate.mark_for_destruction
@pirate.ship.mark_for_destruction
- assert !@pirate.reload.marked_for_destruction?
- assert !@pirate.ship.reload.marked_for_destruction?
+ assert_not_predicate @pirate.reload, :marked_for_destruction?
+ assert_not_predicate @pirate.ship.reload, :marked_for_destruction?
end
# has_one
def test_should_destroy_a_child_association_as_part_of_the_save_transaction_if_it_was_marked_for_destruction
- assert !@pirate.ship.marked_for_destruction?
+ assert_not_predicate @pirate.ship, :marked_for_destruction?
@pirate.ship.mark_for_destruction
id = @pirate.ship.id
- assert @pirate.ship.marked_for_destruction?
+ assert_predicate @pirate.ship, :marked_for_destruction?
assert Ship.find_by_id(id)
@pirate.save
@@ -765,11 +807,12 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
def test_should_skip_validation_on_a_child_association_if_marked_for_destruction
@pirate.ship.name = ""
- assert !@pirate.valid?
+ assert_not_predicate @pirate, :valid?
@pirate.ship.mark_for_destruction
- @pirate.ship.expects(:valid?).never
- assert_difference("Ship.count", -1) { @pirate.save! }
+ assert_not_called(@pirate.ship, :valid?) do
+ assert_difference("Ship.count", -1) { @pirate.save! }
+ end
end
def test_a_child_marked_for_destruction_should_not_be_destroyed_twice
@@ -794,7 +837,7 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
@ship.pirate.catchphrase = "Changed Catchphrase"
@ship.name_will_change!
- assert_raise(RuntimeError) { assert !@pirate.save }
+ assert_raise(RuntimeError) { assert_not @pirate.save }
assert_not_nil @pirate.reload.ship
end
@@ -805,18 +848,19 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
end
def test_should_not_save_changed_has_one_unchanged_object_if_child_is_saved
- @pirate.ship.expects(:save).never
- assert @pirate.save
+ assert_not_called(@pirate.ship, :save) do
+ assert @pirate.save
+ end
end
# belongs_to
def test_should_destroy_a_parent_association_as_part_of_the_save_transaction_if_it_was_marked_for_destruction
- assert !@ship.pirate.marked_for_destruction?
+ assert_not_predicate @ship.pirate, :marked_for_destruction?
@ship.pirate.mark_for_destruction
id = @ship.pirate.id
- assert @ship.pirate.marked_for_destruction?
+ assert_predicate @ship.pirate, :marked_for_destruction?
assert Pirate.find_by_id(id)
@ship.save
@@ -826,11 +870,12 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
def test_should_skip_validation_on_a_parent_association_if_marked_for_destruction
@ship.pirate.catchphrase = ""
- assert !@ship.valid?
+ assert_not_predicate @ship, :valid?
@ship.pirate.mark_for_destruction
- @ship.pirate.expects(:valid?).never
- assert_difference("Pirate.count", -1) { @ship.save! }
+ assert_not_called(@ship.pirate, :valid?) do
+ assert_difference("Pirate.count", -1) { @ship.save! }
+ end
end
def test_a_parent_marked_for_destruction_should_not_be_destroyed_twice
@@ -854,7 +899,7 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
@ship.pirate.catchphrase = "Changed Catchphrase"
- assert_raise(RuntimeError) { assert !@ship.save }
+ assert_raise(RuntimeError) { assert_not @ship.save }
assert_not_nil @ship.reload.pirate
end
@@ -870,7 +915,7 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
def test_should_destroy_has_many_as_part_of_the_save_transaction_if_they_were_marked_for_destruction
2.times { |i| @pirate.birds.create!(name: "birds_#{i}") }
- assert !@pirate.birds.any?(&:marked_for_destruction?)
+ assert_not @pirate.birds.any?(&:marked_for_destruction?)
@pirate.birds.each(&:mark_for_destruction)
klass = @pirate.birds.first.class
@@ -880,7 +925,7 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
ids.each { |id| assert klass.find_by_id(id) }
@pirate.save
- assert @pirate.reload.birds.empty?
+ assert_empty @pirate.reload.birds
ids.each { |id| assert_nil klass.find_by_id(id) }
end
@@ -888,30 +933,32 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
@pirate.birds.create!(name: :parrot)
@pirate.birds.first.destroy
@pirate.save!
- assert @pirate.reload.birds.empty?
+ assert_empty @pirate.reload.birds
end
def test_should_skip_validation_on_has_many_if_marked_for_destruction
2.times { |i| @pirate.birds.create!(name: "birds_#{i}") }
@pirate.birds.each { |bird| bird.name = "" }
- assert !@pirate.valid?
+ assert_not_predicate @pirate, :valid?
- @pirate.birds.each do |bird|
- bird.mark_for_destruction
- bird.expects(:valid?).never
+ @pirate.birds.each(&:mark_for_destruction)
+
+ assert_not_called(@pirate.birds.first, :valid?) do
+ assert_not_called(@pirate.birds.last, :valid?) do
+ assert_difference("Bird.count", -2) { @pirate.save! }
+ end
end
- assert_difference("Bird.count", -2) { @pirate.save! }
end
def test_should_skip_validation_on_has_many_if_destroyed
@pirate.birds.create!(name: "birds_1")
@pirate.birds.each { |bird| bird.name = "" }
- assert !@pirate.valid?
+ assert_not_predicate @pirate, :valid?
@pirate.birds.each(&:destroy)
- assert @pirate.valid?
+ assert_predicate @pirate, :valid?
end
def test_a_child_marked_for_destruction_should_not_be_destroyed_twice_while_saving_has_many
@@ -920,8 +967,11 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
@pirate.birds.each(&:mark_for_destruction)
assert @pirate.save
- @pirate.birds.each { |bird| bird.expects(:destroy).never }
- assert @pirate.save
+ @pirate.birds.each do |bird|
+ assert_not_called(bird, :destroy) do
+ assert @pirate.save
+ end
+ end
end
def test_should_rollback_destructions_if_an_exception_occurred_while_saving_has_many
@@ -936,7 +986,7 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
end
end
- assert_raise(RuntimeError) { assert !@pirate.save }
+ assert_raise(RuntimeError) { assert_not @pirate.save }
assert_equal before, @pirate.reload.birds
end
@@ -1002,42 +1052,44 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
def test_should_destroy_habtm_as_part_of_the_save_transaction_if_they_were_marked_for_destruction
2.times { |i| @pirate.parrots.create!(name: "parrots_#{i}") }
- assert !@pirate.parrots.any?(&:marked_for_destruction?)
+ assert_not @pirate.parrots.any?(&:marked_for_destruction?)
@pirate.parrots.each(&:mark_for_destruction)
assert_no_difference "Parrot.count" do
@pirate.save
end
- assert @pirate.reload.parrots.empty?
+ assert_empty @pirate.reload.parrots
join_records = Pirate.connection.select_all("SELECT * FROM parrots_pirates WHERE pirate_id = #{@pirate.id}")
- assert join_records.empty?
+ assert_empty join_records
end
def test_should_skip_validation_on_habtm_if_marked_for_destruction
2.times { |i| @pirate.parrots.create!(name: "parrots_#{i}") }
@pirate.parrots.each { |parrot| parrot.name = "" }
- assert !@pirate.valid?
+ assert_not_predicate @pirate, :valid?
- @pirate.parrots.each do |parrot|
- parrot.mark_for_destruction
- parrot.expects(:valid?).never
+ @pirate.parrots.each { |parrot| parrot.mark_for_destruction }
+
+ assert_not_called(@pirate.parrots.first, :valid?) do
+ assert_not_called(@pirate.parrots.last, :valid?) do
+ @pirate.save!
+ end
end
- @pirate.save!
- assert @pirate.reload.parrots.empty?
+ assert_empty @pirate.reload.parrots
end
def test_should_skip_validation_on_habtm_if_destroyed
@pirate.parrots.create!(name: "parrots_1")
@pirate.parrots.each { |parrot| parrot.name = "" }
- assert !@pirate.valid?
+ assert_not_predicate @pirate, :valid?
@pirate.parrots.each(&:destroy)
- assert @pirate.valid?
+ assert_predicate @pirate, :valid?
end
def test_a_child_marked_for_destruction_should_not_be_destroyed_twice_while_saving_habtm
@@ -1064,7 +1116,7 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
end
end
- assert_raise(RuntimeError) { assert !@pirate.save }
+ assert_raise(RuntimeError) { assert_not @pirate.save }
assert_equal before, @pirate.reload.parrots
end
@@ -1144,16 +1196,16 @@ class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase
def test_should_automatically_validate_the_associated_model
@pirate.ship.name = ""
- assert @pirate.invalid?
- assert @pirate.errors[:"ship.name"].any?
+ assert_predicate @pirate, :invalid?
+ assert_predicate @pirate.errors[:"ship.name"], :any?
end
def test_should_merge_errors_on_the_associated_models_onto_the_parent_even_if_it_is_not_valid
@pirate.ship.name = nil
@pirate.catchphrase = nil
- assert @pirate.invalid?
- assert @pirate.errors[:"ship.name"].any?
- assert @pirate.errors[:catchphrase].any?
+ assert_predicate @pirate, :invalid?
+ assert_predicate @pirate.errors[:"ship.name"], :any?
+ assert_predicate @pirate.errors[:catchphrase], :any?
end
def test_should_not_ignore_different_error_messages_on_the_same_attribute
@@ -1162,7 +1214,7 @@ class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase
Ship.validates_format_of :name, with: /\w/
@pirate.ship.name = ""
@pirate.catchphrase = nil
- assert @pirate.invalid?
+ assert_predicate @pirate, :invalid?
assert_equal ["can't be blank", "is invalid"], @pirate.errors[:"ship.name"]
ensure
Ship._validators = old_validators if old_validators
@@ -1212,7 +1264,7 @@ class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase
assert_no_difference "Pirate.count" do
assert_no_difference "Ship.count" do
- assert !pirate.save
+ assert_not pirate.save
end
end
end
@@ -1231,7 +1283,7 @@ class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase
end
end
- assert_raise(RuntimeError) { assert !@pirate.save }
+ assert_raise(RuntimeError) { assert_not @pirate.save }
assert_equal before, [@pirate.reload.catchphrase, @pirate.ship.name]
end
@@ -1243,7 +1295,7 @@ class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase
ship = ShipWithoutNestedAttributes.new(name: "The Black Flag")
ship.parts.build.mark_for_destruction
- assert_not ship.valid?
+ assert_not_predicate ship, :valid?
end
end
@@ -1298,16 +1350,16 @@ class TestAutosaveAssociationOnABelongsToAssociation < ActiveRecord::TestCase
def test_should_automatically_validate_the_associated_model
@ship.pirate.catchphrase = ""
- assert @ship.invalid?
- assert @ship.errors[:"pirate.catchphrase"].any?
+ assert_predicate @ship, :invalid?
+ assert_predicate @ship.errors[:"pirate.catchphrase"], :any?
end
def test_should_merge_errors_on_the_associated_model_onto_the_parent_even_if_it_is_not_valid
@ship.name = nil
@ship.pirate.catchphrase = nil
- assert @ship.invalid?
- assert @ship.errors[:name].any?
- assert @ship.errors[:"pirate.catchphrase"].any?
+ assert_predicate @ship, :invalid?
+ assert_predicate @ship.errors[:name], :any?
+ assert_predicate @ship.errors[:"pirate.catchphrase"], :any?
end
def test_should_still_allow_to_bypass_validations_on_the_associated_model
@@ -1336,7 +1388,7 @@ class TestAutosaveAssociationOnABelongsToAssociation < ActiveRecord::TestCase
assert_no_difference "Ship.count" do
assert_no_difference "Pirate.count" do
- assert !ship.save
+ assert_not ship.save
end
end
end
@@ -1355,7 +1407,7 @@ class TestAutosaveAssociationOnABelongsToAssociation < ActiveRecord::TestCase
end
end
- assert_raise(RuntimeError) { assert !@ship.save }
+ assert_raise(RuntimeError) { assert_not @ship.save }
assert_equal before, [@ship.pirate.reload.catchphrase, @ship.reload.name]
end
@@ -1370,7 +1422,7 @@ module AutosaveAssociationOnACollectionAssociationTests
@pirate.send(@association_name).each_with_index { |child, i| child.name = new_names[i] }
@pirate.save
- assert_equal new_names, @pirate.reload.send(@association_name).map(&:name)
+ assert_equal new_names.sort, @pirate.reload.send(@association_name).map(&:name).sort
end
def test_should_automatically_save_bang_the_associated_models
@@ -1378,7 +1430,7 @@ module AutosaveAssociationOnACollectionAssociationTests
@pirate.send(@association_name).each_with_index { |child, i| child.name = new_names[i] }
@pirate.save!
- assert_equal new_names, @pirate.reload.send(@association_name).map(&:name)
+ assert_equal new_names.sort, @pirate.reload.send(@association_name).map(&:name).sort
end
def test_should_update_children_when_autosave_is_true_and_parent_is_new_but_child_is_not
@@ -1402,17 +1454,17 @@ module AutosaveAssociationOnACollectionAssociationTests
def test_should_automatically_validate_the_associated_models
@pirate.send(@association_name).each { |child| child.name = "" }
- assert !@pirate.valid?
+ assert_not_predicate @pirate, :valid?
assert_equal ["can't be blank"], @pirate.errors["#{@association_name}.name"]
- assert @pirate.errors[@association_name].empty?
+ assert_empty @pirate.errors[@association_name]
end
def test_should_not_use_default_invalid_error_on_associated_models
@pirate.send(@association_name).build(name: "")
- assert !@pirate.valid?
+ assert_not_predicate @pirate, :valid?
assert_equal ["can't be blank"], @pirate.errors["#{@association_name}.name"]
- assert @pirate.errors[@association_name].empty?
+ assert_empty @pirate.errors[@association_name]
end
def test_should_default_invalid_error_from_i18n
@@ -1422,10 +1474,10 @@ module AutosaveAssociationOnACollectionAssociationTests
@pirate.send(@association_name).build(name: "")
- assert !@pirate.valid?
+ assert_not_predicate @pirate, :valid?
assert_equal ["cannot be blank"], @pirate.errors["#{@association_name}.name"]
assert_equal ["#{@association_name.to_s.humanize} name cannot be blank"], @pirate.errors.full_messages
- assert @pirate.errors[@association_name].empty?
+ assert_empty @pirate.errors[@association_name]
ensure
I18n.backend = I18n::Backend::Simple.new
end
@@ -1434,9 +1486,9 @@ module AutosaveAssociationOnACollectionAssociationTests
@pirate.send(@association_name).each { |child| child.name = "" }
@pirate.catchphrase = nil
- assert !@pirate.valid?
+ assert_not_predicate @pirate, :valid?
assert_equal ["can't be blank"], @pirate.errors["#{@association_name}.name"]
- assert @pirate.errors[:catchphrase].any?
+ assert_predicate @pirate.errors[:catchphrase], :any?
end
def test_should_allow_to_bypass_validations_on_the_associated_models_on_update
@@ -1479,7 +1531,7 @@ module AutosaveAssociationOnACollectionAssociationTests
@child_1.name = "Changed"
@child_1.cancel_save_from_callback = true
- assert !@pirate.save
+ assert_not @pirate.save
assert_equal "Don' botharrr talkin' like one, savvy?", @pirate.reload.catchphrase
assert_equal "Posideons Killer", @child_1.reload.name
@@ -1489,7 +1541,7 @@ module AutosaveAssociationOnACollectionAssociationTests
assert_no_difference "Pirate.count" do
assert_no_difference "#{new_child.class.name}.count" do
- assert !new_pirate.save
+ assert_not new_pirate.save
end
end
end
@@ -1509,7 +1561,7 @@ module AutosaveAssociationOnACollectionAssociationTests
end
end
- assert_raise(RuntimeError) { assert !@pirate.save }
+ assert_raise(RuntimeError) { assert_not @pirate.save }
assert_equal before, [@pirate.reload.catchphrase, *@pirate.send(@association_name).map(&:name)]
end
@@ -1594,10 +1646,10 @@ class TestAutosaveAssociationValidationsOnAHasManyAssociation < ActiveRecord::Te
end
test "should automatically validate associations" do
- assert @pirate.valid?
+ assert_predicate @pirate, :valid?
@pirate.birds.each { |bird| bird.name = "" }
- assert !@pirate.valid?
+ assert_not_predicate @pirate, :valid?
end
end
@@ -1612,15 +1664,15 @@ class TestAutosaveAssociationValidationsOnAHasOneAssociation < ActiveRecord::Tes
end
test "should automatically validate associations with :validate => true" do
- assert @pirate.valid?
+ assert_predicate @pirate, :valid?
@pirate.ship.name = ""
- assert !@pirate.valid?
+ assert_not_predicate @pirate, :valid?
end
test "should not automatically add validate associations without :validate => true" do
- assert @pirate.valid?
+ assert_predicate @pirate, :valid?
@pirate.non_validated_ship.name = ""
- assert @pirate.valid?
+ assert_predicate @pirate, :valid?
end
end
@@ -1633,15 +1685,15 @@ class TestAutosaveAssociationValidationsOnABelongsToAssociation < ActiveRecord::
end
test "should automatically validate associations with :validate => true" do
- assert @pirate.valid?
+ assert_predicate @pirate, :valid?
@pirate.parrot = Parrot.new(name: "")
- assert !@pirate.valid?
+ assert_not_predicate @pirate, :valid?
end
test "should not automatically validate associations without :validate => true" do
- assert @pirate.valid?
+ assert_predicate @pirate, :valid?
@pirate.non_validated_parrot = Parrot.new(name: "")
- assert @pirate.valid?
+ assert_predicate @pirate, :valid?
end
end
@@ -1654,17 +1706,17 @@ class TestAutosaveAssociationValidationsOnAHABTMAssociation < ActiveRecord::Test
end
test "should automatically validate associations with :validate => true" do
- assert @pirate.valid?
+ assert_predicate @pirate, :valid?
@pirate.parrots = [ Parrot.new(name: "popuga") ]
@pirate.parrots.each { |parrot| parrot.name = "" }
- assert !@pirate.valid?
+ assert_not_predicate @pirate, :valid?
end
test "should not automatically validate associations without :validate => true" do
- assert @pirate.valid?
+ assert_predicate @pirate, :valid?
@pirate.non_validated_parrots = [ Parrot.new(name: "popuga") ]
@pirate.non_validated_parrots.each { |parrot| parrot.name = "" }
- assert @pirate.valid?
+ assert_predicate @pirate, :valid?
end
end
@@ -1685,7 +1737,7 @@ class TestAutosaveAssociationValidationMethodsGeneration < ActiveRecord::TestCas
end
test "should not generate validation methods for has_one associations without :validate => true" do
- assert !@pirate.respond_to?(:validate_associated_records_for_non_validated_ship)
+ assert_not_respond_to @pirate, :validate_associated_records_for_non_validated_ship
end
test "should generate validation methods for belongs_to associations with :validate => true" do
@@ -1693,7 +1745,7 @@ class TestAutosaveAssociationValidationMethodsGeneration < ActiveRecord::TestCas
end
test "should not generate validation methods for belongs_to associations without :validate => true" do
- assert !@pirate.respond_to?(:validate_associated_records_for_non_validated_parrot)
+ assert_not_respond_to @pirate, :validate_associated_records_for_non_validated_parrot
end
test "should generate validation methods for HABTM associations with :validate => true" do
@@ -1722,6 +1774,10 @@ class TestAutosaveAssociationOnAHasManyAssociationWithInverse < ActiveRecord::Te
end
end
+ def setup
+ Comment.delete_all
+ end
+
def test_after_save_callback_with_autosave
post = Post.new(title: "Test", body: "...")
comment = post.comments.build(body: "...")
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index 979a59f566..f6311f9256 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/post"
require "models/author"
@@ -12,7 +14,6 @@ require "models/computer"
require "models/project"
require "models/default"
require "models/auto_id"
-require "models/boolean"
require "models/column_name"
require "models/subscriber"
require "models/comment"
@@ -64,7 +65,7 @@ class LintTest < ActiveRecord::TestCase
end
class BasicsTest < ActiveRecord::TestCase
- fixtures :topics, :companies, :developers, :projects, :computers, :accounts, :minimalistics, "warehouse-things", :authors, :categorizations, :categories, :posts
+ fixtures :topics, :companies, :developers, :projects, :computers, :accounts, :minimalistics, "warehouse-things", :authors, :author_addresses, :categorizations, :categories, :posts
def test_column_names_are_escaped
conn = ActiveRecord::Base.connection
@@ -102,7 +103,7 @@ class BasicsTest < ActiveRecord::TestCase
pk = Author.columns_hash["id"]
ref = Post.columns_hash["author_id"]
- assert_equal pk.bigint?, ref.bigint?
+ assert_equal pk.sql_type, ref.sql_type
end
def test_many_mutations
@@ -145,8 +146,8 @@ class BasicsTest < ActiveRecord::TestCase
end
def test_table_exists
- assert !NonExistentTable.table_exists?
- assert Topic.table_exists?
+ assert_not_predicate NonExistentTable, :table_exists?
+ assert_predicate Topic, :table_exists?
end
def test_preserving_date_objects
@@ -305,7 +306,7 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal "Dude", cbs[0].name
assert_equal "Bob", cbs[1].name
assert cbs[0].frickinawesome
- assert !cbs[1].frickinawesome
+ assert_not cbs[1].frickinawesome
end
def test_load
@@ -448,7 +449,7 @@ class BasicsTest < ActiveRecord::TestCase
def test_default_values
topic = Topic.new
- assert topic.approved?
+ assert_predicate topic, :approved?
assert_nil topic.written_on
assert_nil topic.bonus_time
assert_nil topic.last_read
@@ -456,7 +457,7 @@ class BasicsTest < ActiveRecord::TestCase
topic.save
topic = Topic.find(topic.id)
- assert topic.approved?
+ assert_predicate topic, :approved?
assert_nil topic.last_read
# Oracle has some funky default handling, so it requires a bit of
@@ -627,7 +628,7 @@ class BasicsTest < ActiveRecord::TestCase
end
def test_readonly_attributes
- assert_equal Set.new([ "title" , "comments_count" ]), ReadonlyTitlePost.readonly_attributes
+ assert_equal Set.new([ "title", "comments_count" ]), ReadonlyTitlePost.readonly_attributes
post = ReadonlyTitlePost.create(title: "cannot change this", body: "changeable")
post.reload
@@ -703,46 +704,15 @@ class BasicsTest < ActiveRecord::TestCase
assert_nil topic.bonus_time
end
- def test_boolean
- b_nil = Boolean.create("value" => nil)
- nil_id = b_nil.id
- b_false = Boolean.create("value" => false)
- false_id = b_false.id
- b_true = Boolean.create("value" => true)
- true_id = b_true.id
-
- b_nil = Boolean.find(nil_id)
- assert_nil b_nil.value
- b_false = Boolean.find(false_id)
- assert !b_false.value?
- b_true = Boolean.find(true_id)
- assert b_true.value?
- end
-
- def test_boolean_without_questionmark
- b_true = Boolean.create("value" => true)
- true_id = b_true.id
+ def test_attributes
+ category = Category.new(name: "Ruby")
- subclass = Class.new(Boolean).find true_id
- superclass = Boolean.find true_id
+ expected_attributes = category.attribute_names.map do |attribute_name|
+ [attribute_name, category.public_send(attribute_name)]
+ end.to_h
- assert_equal superclass.read_attribute(:has_fun), subclass.read_attribute(:has_fun)
- end
-
- def test_boolean_cast_from_string
- b_blank = Boolean.create("value" => "")
- blank_id = b_blank.id
- b_false = Boolean.create("value" => "0")
- false_id = b_false.id
- b_true = Boolean.create("value" => "1")
- true_id = b_true.id
-
- b_blank = Boolean.find(blank_id)
- assert_nil b_blank.value
- b_false = Boolean.find(false_id)
- assert !b_false.value?
- b_true = Boolean.find(true_id)
- assert b_true.value?
+ assert_instance_of Hash, category.attributes
+ assert_equal expected_attributes, category.attributes
end
def test_new_record_returns_boolean
@@ -755,7 +725,7 @@ class BasicsTest < ActiveRecord::TestCase
duped_topic = nil
assert_nothing_raised { duped_topic = topic.dup }
assert_equal topic.title, duped_topic.title
- assert !duped_topic.persisted?
+ assert_not_predicate duped_topic, :persisted?
# test if the attributes have been duped
topic.title = "a"
@@ -773,7 +743,7 @@ class BasicsTest < ActiveRecord::TestCase
# test if saved clone object differs from original
duped_topic.save
- assert duped_topic.persisted?
+ assert_predicate duped_topic, :persisted?
assert_not_equal duped_topic.id, topic.id
duped_topic.reload
@@ -794,7 +764,7 @@ class BasicsTest < ActiveRecord::TestCase
assert_nothing_raised { dup = dev.dup }
assert_kind_of DeveloperSalary, dup.salary
assert_equal dev.salary.amount, dup.salary.amount
- assert !dup.persisted?
+ assert_not_predicate dup, :persisted?
# test if the attributes have been duped
original_amount = dup.salary.amount
@@ -802,7 +772,7 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal original_amount, dup.salary.amount
assert dup.save
- assert dup.persisted?
+ assert_predicate dup, :persisted?
assert_not_equal dup.id, dev.id
end
@@ -822,64 +792,68 @@ class BasicsTest < ActiveRecord::TestCase
def test_clone_of_new_object_with_defaults
developer = Developer.new
- assert !developer.name_changed?
- assert !developer.salary_changed?
+ assert_not_predicate developer, :name_changed?
+ assert_not_predicate developer, :salary_changed?
cloned_developer = developer.clone
- assert !cloned_developer.name_changed?
- assert !cloned_developer.salary_changed?
+ assert_not_predicate cloned_developer, :name_changed?
+ assert_not_predicate cloned_developer, :salary_changed?
end
def test_clone_of_new_object_marks_attributes_as_dirty
developer = Developer.new name: "Bjorn", salary: 100000
- assert developer.name_changed?
- assert developer.salary_changed?
+ assert_predicate developer, :name_changed?
+ assert_predicate developer, :salary_changed?
cloned_developer = developer.clone
- assert cloned_developer.name_changed?
- assert cloned_developer.salary_changed?
+ assert_predicate cloned_developer, :name_changed?
+ assert_predicate cloned_developer, :salary_changed?
end
def test_clone_of_new_object_marks_as_dirty_only_changed_attributes
developer = Developer.new name: "Bjorn"
assert developer.name_changed? # obviously
- assert !developer.salary_changed? # attribute has non-nil default value, so treated as not changed
+ assert_not developer.salary_changed? # attribute has non-nil default value, so treated as not changed
cloned_developer = developer.clone
- assert cloned_developer.name_changed?
- assert !cloned_developer.salary_changed? # ... and cloned instance should behave same
+ assert_predicate cloned_developer, :name_changed?
+ assert_not cloned_developer.salary_changed? # ... and cloned instance should behave same
end
def test_dup_of_saved_object_marks_attributes_as_dirty
developer = Developer.create! name: "Bjorn", salary: 100000
- assert !developer.name_changed?
- assert !developer.salary_changed?
+ assert_not_predicate developer, :name_changed?
+ assert_not_predicate developer, :salary_changed?
cloned_developer = developer.dup
assert cloned_developer.name_changed? # both attributes differ from defaults
- assert cloned_developer.salary_changed?
+ assert_predicate cloned_developer, :salary_changed?
end
def test_dup_of_saved_object_marks_as_dirty_only_changed_attributes
developer = Developer.create! name: "Bjorn"
- assert !developer.name_changed? # both attributes of saved object should be treated as not changed
- assert !developer.salary_changed?
+ assert_not developer.name_changed? # both attributes of saved object should be treated as not changed
+ assert_not_predicate developer, :salary_changed?
cloned_developer = developer.dup
assert cloned_developer.name_changed? # ... but on cloned object should be
- assert !cloned_developer.salary_changed? # ... BUT salary has non-nil default which should be treated as not changed on cloned instance
+ assert_not cloned_developer.salary_changed? # ... BUT salary has non-nil default which should be treated as not changed on cloned instance
end
def test_bignum
company = Company.find(1)
- company.rating = 2147483647
+ company.rating = 2147483648
company.save
company = Company.find(1)
- assert_equal 2147483647, company.rating
+ assert_equal 2147483648, company.rating
+ end
+
+ def test_bignum_pk
+ company = Company.create!(id: 2147483648, name: "foo")
+ assert_equal company, Company.find(company.id)
end
- # TODO: extend defaults tests to other databases!
- if current_adapter?(:PostgreSQLAdapter)
+ if current_adapter?(:PostgreSQLAdapter, :Mysql2Adapter, :SQLite3Adapter)
def test_default
with_timezone_config default: :local do
default = Default.new
@@ -891,85 +865,14 @@ class BasicsTest < ActiveRecord::TestCase
# char types
assert_equal "Y", default.char1
assert_equal "a varchar field", default.char2
- assert_equal "a text field", default.char3
+ # Mysql text type can't have default value
+ unless current_adapter?(:Mysql2Adapter)
+ assert_equal "a text field", default.char3
+ end
end
end
end
- class NumericData < ActiveRecord::Base
- self.table_name = "numeric_data"
-
- attribute :my_house_population, :integer
- attribute :atoms_in_universe, :integer
- end
-
- def test_big_decimal_conditions
- m = NumericData.new(
- bank_balance: 1586.43,
- big_bank_balance: BigDecimal("1000234000567.95"),
- world_population: 6000000000,
- my_house_population: 3
- )
- assert m.save
- assert_equal 0, NumericData.where("bank_balance > ?", 2000.0).count
- 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 Integer, 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_numeric_fields_with_scale
- m = NumericData.new(
- bank_balance: 1586.43122334,
- big_bank_balance: BigDecimal("234000567.952344"),
- world_population: 6000000000,
- my_house_population: 3
- )
- assert m.save
-
- m1 = NumericData.find(m.id)
- assert_not_nil m1
-
- # As with migration_test.rb, we should make world_population >= 2**62
- # to cover 64-bit platforms and test it is a Bignum, but the main thing
- # is that it's an Integer.
- assert_kind_of Integer, m1.world_population
- assert_equal 6000000000, m1.world_population
-
- assert_kind_of Integer, m1.my_house_population
- assert_equal 3, m1.my_house_population
-
- assert_kind_of BigDecimal, m1.bank_balance
- assert_equal BigDecimal("1586.43"), m1.bank_balance
-
- assert_kind_of BigDecimal, m1.big_bank_balance
- assert_equal BigDecimal("234000567.95"), m1.big_bank_balance
- end
-
def test_auto_id
auto = AutoId.new
auto.save
@@ -1007,14 +910,14 @@ class BasicsTest < ActiveRecord::TestCase
end
def test_toggle_attribute
- assert !topics(:first).approved?
+ assert_not_predicate topics(:first), :approved?
topics(:first).toggle!(:approved)
- assert topics(:first).approved?
+ assert_predicate topics(:first), :approved?
topic = topics(:first)
topic.toggle(:approved)
- assert !topic.approved?
+ assert_not_predicate topic, :approved?
topic.reload
- assert topic.approved?
+ assert_predicate topic, :approved?
end
def test_reload
@@ -1038,7 +941,7 @@ class BasicsTest < ActiveRecord::TestCase
end
end
- def test_clear_cash_when_setting_table_name
+ def test_clear_cache_when_setting_table_name
original_table_name = Joke.table_name
Joke.table_name = "funny_jokes"
@@ -1115,29 +1018,15 @@ class BasicsTest < ActiveRecord::TestCase
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.where("posts.#{QUOTED_TYPE} = 'Post'").joins("LEFT JOIN comments ON posts.id=comments.post_id").count
assert_equal res, res2
- res3 = nil
- assert_nothing_raised do
- res3 = Post.where("posts.#{QUOTED_TYPE} = 'Post'").joins("LEFT JOIN comments ON posts.id=comments.post_id").count
- end
- assert_equal res, res3
-
res4 = Post.count_by_sql "SELECT COUNT(p.id) FROM posts p, comments co WHERE p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id"
- res5 = nil
- assert_nothing_raised do
- res5 = Post.where("p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id").joins("p, comments co").select("p.id").count
- end
-
+ res5 = Post.where("p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id").joins("p, comments co").select("p.id").count
assert_equal res4, res5
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.where("p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id").joins("p, comments co").select("p.id").distinct.count
- end
+ res7 = Post.where("p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id").joins("p, comments co").select("p.id").distinct.count
assert_equal res6, res7
end
@@ -1501,28 +1390,92 @@ class BasicsTest < ActiveRecord::TestCase
test "resetting column information doesn't remove attribute methods" do
topic = topics(:first)
- assert_not topic.id_changed?
+ assert_not_predicate topic, :id_changed?
Topic.reset_column_information
- assert_not topic.id_changed?
+ assert_not_predicate topic, :id_changed?
end
test "ignored columns are not present in columns_hash" do
cache_columns = Developer.connection.schema_cache.columns_hash(Developer.table_name)
assert_includes cache_columns.keys, "first_name"
assert_not_includes Developer.columns_hash.keys, "first_name"
+ assert_not_includes SubDeveloper.columns_hash.keys, "first_name"
+ assert_not_includes SymbolIgnoredDeveloper.columns_hash.keys, "first_name"
end
test "ignored columns have no attribute methods" do
- refute Developer.new.respond_to?(:first_name)
- refute Developer.new.respond_to?(:first_name=)
- refute Developer.new.respond_to?(:first_name?)
+ assert_not_respond_to Developer.new, :first_name
+ assert_not_respond_to Developer.new, :first_name=
+ assert_not_respond_to Developer.new, :first_name?
+ assert_not_respond_to SubDeveloper.new, :first_name
+ assert_not_respond_to SubDeveloper.new, :first_name=
+ assert_not_respond_to SubDeveloper.new, :first_name?
+ assert_not_respond_to SymbolIgnoredDeveloper.new, :first_name
+ assert_not_respond_to SymbolIgnoredDeveloper.new, :first_name=
+ assert_not_respond_to SymbolIgnoredDeveloper.new, :first_name?
end
test "ignored columns don't prevent explicit declaration of attribute methods" do
- assert Developer.new.respond_to?(:last_name)
- assert Developer.new.respond_to?(:last_name=)
- assert Developer.new.respond_to?(:last_name?)
+ assert_respond_to Developer.new, :last_name
+ assert_respond_to Developer.new, :last_name=
+ assert_respond_to Developer.new, :last_name?
+ assert_respond_to SubDeveloper.new, :last_name
+ assert_respond_to SubDeveloper.new, :last_name=
+ assert_respond_to SubDeveloper.new, :last_name?
+ assert_respond_to SymbolIgnoredDeveloper.new, :last_name
+ assert_respond_to SymbolIgnoredDeveloper.new, :last_name=
+ assert_respond_to SymbolIgnoredDeveloper.new, :last_name?
+ end
+
+ test "ignored columns are stored as an array of string" do
+ assert_equal(%w(first_name last_name), Developer.ignored_columns)
+ assert_equal(%w(first_name last_name), SymbolIgnoredDeveloper.ignored_columns)
+ end
+
+ test "when #reload called, ignored columns' attribute methods are not defined" do
+ developer = Developer.create!(name: "Developer")
+ assert_not_respond_to developer, :first_name
+ assert_not_respond_to developer, :first_name=
+
+ developer.reload
+
+ assert_not_respond_to developer, :first_name
+ assert_not_respond_to developer, :first_name=
+ end
+
+ test "ignored columns not included in SELECT" do
+ query = Developer.all.to_sql.downcase
+
+ # ignored column
+ assert_not query.include?("first_name")
+
+ # regular column
+ assert query.include?("name")
+ end
+
+ test "column names are quoted when using #from clause and model has ignored columns" do
+ assert_not_empty Developer.ignored_columns
+ query = Developer.from("developers").to_sql
+ quoted_id = "#{Developer.quoted_table_name}.#{Developer.quoted_primary_key}"
+
+ assert_match(/SELECT #{Regexp.escape(quoted_id)}.* FROM developers/, query)
+ end
+
+ test "using table name qualified column names unless having SELECT list explicitly" do
+ assert_equal developers(:david), Developer.from("developers").joins(:shared_computers).take
+ end
+
+ test "protected environments by default is an array with production" do
+ assert_equal ["production"], ActiveRecord::Base.protected_environments
+ end
+
+ def test_protected_environments_are_stored_as_an_array_of_string
+ previous_protected_environments = ActiveRecord::Base.protected_environments
+ ActiveRecord::Base.protected_environments = [:staging, "production"]
+ assert_equal ["staging", "production"], ActiveRecord::Base.protected_environments
+ ensure
+ ActiveRecord::Base.protected_environments = previous_protected_environments
end
end
diff --git a/activerecord/test/cases/batches_test.rb b/activerecord/test/cases/batches_test.rb
index f7e21faf0f..c8163901c6 100644
--- a/activerecord/test/cases/batches_test.rb
+++ b/activerecord/test/cases/batches_test.rb
@@ -1,4 +1,7 @@
+# frozen_string_literal: true
+
require "cases/helper"
+require "models/comment"
require "models/post"
require "models/subscriber"
@@ -35,12 +38,10 @@ class EachTest < ActiveRecord::TestCase
end
end
- if Enumerator.method_defined? :size
- def test_each_should_return_a_sized_enumerator
- assert_equal 11, Post.find_each(batch_size: 1).size
- assert_equal 5, Post.find_each(batch_size: 2, start: 7).size
- assert_equal 11, Post.find_each(batch_size: 10_000).size
- end
+ def test_each_should_return_a_sized_enumerator
+ assert_equal 11, Post.find_each(batch_size: 1).size
+ assert_equal 5, Post.find_each(batch_size: 2, start: 7).size
+ assert_equal 11, Post.find_each(batch_size: 10_000).size
end
def test_each_enumerator_should_execute_one_query_per_batch
@@ -145,7 +146,7 @@ class EachTest < ActiveRecord::TestCase
def test_find_in_batches_should_quote_batch_order
c = Post.connection
- assert_sql(/ORDER BY #{c.quote_table_name('posts')}.#{c.quote_column_name('id')}/) do
+ assert_sql(/ORDER BY #{c.quote_table_name('posts')}\.#{c.quote_column_name('id')}/) do
Post.find_in_batches(batch_size: 1) do |batch|
assert_kind_of Array, batch
assert_kind_of Post, batch.first
@@ -154,7 +155,7 @@ class EachTest < ActiveRecord::TestCase
end
def test_find_in_batches_should_not_use_records_after_yielding_them_in_case_original_array_is_modified
- not_a_post = "not a post"
+ not_a_post = "not a post".dup
def not_a_post.id; end
not_a_post.stub(:id, -> { raise StandardError.new("not_a_post had #id called on it") }) do
assert_nothing_raised do
@@ -312,7 +313,7 @@ class EachTest < ActiveRecord::TestCase
def test_in_batches_each_record_should_yield_record_if_block_is_given
assert_queries(6) do
Post.in_batches(of: 2).each_record do |post|
- assert post.title.present?
+ assert_predicate post.title, :present?
assert_kind_of Post, post
end
end
@@ -321,7 +322,7 @@ class EachTest < ActiveRecord::TestCase
def test_in_batches_each_record_should_return_enumerator_if_no_block_given
assert_queries(6) do
Post.in_batches(of: 2).each_record.with_index do |post, i|
- assert post.title.present?
+ assert_predicate post.title, :present?
assert_kind_of Post, post
end
end
@@ -352,24 +353,24 @@ class EachTest < ActiveRecord::TestCase
def test_in_batches_should_not_be_loaded
Post.in_batches(of: 1) do |relation|
- assert_not relation.loaded?
+ assert_not_predicate relation, :loaded?
end
Post.in_batches(of: 1, load: false) do |relation|
- assert_not relation.loaded?
+ assert_not_predicate relation, :loaded?
end
end
def test_in_batches_should_be_loaded
Post.in_batches(of: 1, load: true) do |relation|
- assert relation.loaded?
+ assert_predicate relation, :loaded?
end
end
def test_in_batches_if_not_loaded_executes_more_queries
assert_queries(@total + 1) do
Post.in_batches(of: 1, load: false) do |relation|
- assert_not relation.loaded?
+ assert_not_predicate relation, :loaded?
end
end
end
@@ -410,7 +411,7 @@ class EachTest < ActiveRecord::TestCase
def test_in_batches_should_quote_batch_order
c = Post.connection
- assert_sql(/ORDER BY #{c.quote_table_name('posts')}.#{c.quote_column_name('id')}/) do
+ assert_sql(/ORDER BY #{c.quote_table_name('posts')}\.#{c.quote_column_name('id')}/) do
Post.in_batches(of: 1) do |relation|
assert_kind_of ActiveRecord::Relation, relation
assert_kind_of Post, relation.first
@@ -419,7 +420,7 @@ class EachTest < ActiveRecord::TestCase
end
def test_in_batches_should_not_use_records_after_yielding_them_in_case_original_array_is_modified
- not_a_post = "not a post"
+ not_a_post = "not a post".dup
def not_a_post.id
raise StandardError.new("not_a_post had #id called on it")
end
@@ -507,7 +508,7 @@ class EachTest < ActiveRecord::TestCase
def test_in_batches_relations_update_all_should_not_affect_matching_records_in_other_batches
Post.update_all(author_id: 0)
person = Post.last
- person.update_attributes(author_id: 1)
+ person.update(author_id: 1)
Post.in_batches(of: 2) do |batch|
batch.where("author_id >= 1").update_all("author_id = author_id + 1")
@@ -515,14 +516,12 @@ class EachTest < ActiveRecord::TestCase
assert_equal 2, person.reload.author_id # incremented only once
end
- if Enumerator.method_defined? :size
- def test_find_in_batches_should_return_a_sized_enumerator
- assert_equal 11, Post.find_in_batches(batch_size: 1).size
- assert_equal 6, Post.find_in_batches(batch_size: 2).size
- assert_equal 4, Post.find_in_batches(batch_size: 2, start: 4).size
- assert_equal 4, Post.find_in_batches(batch_size: 3).size
- assert_equal 1, Post.find_in_batches(batch_size: 10_000).size
- end
+ def test_find_in_batches_should_return_a_sized_enumerator
+ assert_equal 11, Post.find_in_batches(batch_size: 1).size
+ assert_equal 6, Post.find_in_batches(batch_size: 2).size
+ assert_equal 4, Post.find_in_batches(batch_size: 2, start: 4).size
+ assert_equal 4, Post.find_in_batches(batch_size: 3).size
+ assert_equal 1, Post.find_in_batches(batch_size: 10_000).size
end
[true, false].each do |load|
@@ -587,31 +586,78 @@ class EachTest < ActiveRecord::TestCase
end
end
- test ".error_on_ignored_order_or_limit= is deprecated" do
- begin
- prev = ActiveRecord::Base.error_on_ignored_order
- assert_deprecated "Please use error_on_ignored_order= instead." do
- ActiveRecord::Base.error_on_ignored_order_or_limit = true
+ test ".find_each respects table alias" do
+ assert_queries(1) do
+ table_alias = Post.arel_table.alias("omg_posts")
+ table_metadata = ActiveRecord::TableMetadata.new(Post, table_alias)
+ predicate_builder = ActiveRecord::PredicateBuilder.new(table_metadata)
+
+ posts = ActiveRecord::Relation.create(
+ Post,
+ table: table_alias,
+ predicate_builder: predicate_builder
+ )
+ posts.find_each {}
+ end
+ end
+
+ test ".find_each bypasses the query cache for its own queries" do
+ Post.cache do
+ assert_queries(2) do
+ Post.find_each {}
+ Post.find_each {}
end
- assert ActiveRecord::Base.error_on_ignored_order
- ensure
- ActiveRecord::Base.error_on_ignored_order = prev
end
end
- test ".error_on_ignored_order_or_limit is deprecated" do
- expected = ActiveRecord::Base.error_on_ignored_order
- actual = assert_deprecated "Please use error_on_ignored_order instead." do
- ActiveRecord::Base.error_on_ignored_order_or_limit
+ test ".find_each does not disable the query cache inside the given block" do
+ Post.cache do
+ Post.find_each(start: 1, finish: 1) do |post|
+ assert_queries(1) do
+ post.comments.count
+ post.comments.count
+ end
+ end
end
- assert_equal expected, actual
end
- test "#error_on_ignored_order_or_limit is deprecated" do
- expected = ActiveRecord::Base.error_on_ignored_order
- actual = assert_deprecated "Please use error_on_ignored_order instead." do
- Post.new.error_on_ignored_order_or_limit
+ test ".find_in_batches bypasses the query cache for its own queries" do
+ Post.cache do
+ assert_queries(2) do
+ Post.find_in_batches {}
+ Post.find_in_batches {}
+ end
+ end
+ end
+
+ test ".find_in_batches does not disable the query cache inside the given block" do
+ Post.cache do
+ Post.find_in_batches(start: 1, finish: 1) do |batch|
+ assert_queries(1) do
+ batch.first.comments.count
+ batch.first.comments.count
+ end
+ end
+ end
+ end
+
+ test ".in_batches bypasses the query cache for its own queries" do
+ Post.cache do
+ assert_queries(2) do
+ Post.in_batches {}
+ Post.in_batches {}
+ end
+ end
+ end
+
+ test ".in_batches does not disable the query cache inside the given block" do
+ Post.cache do
+ Post.in_batches(start: 1, finish: 1) do |relation|
+ assert_queries(1) do
+ relation.count
+ relation.count
+ end
+ end
end
- assert_equal expected, actual
end
end
diff --git a/activerecord/test/cases/binary_test.rb b/activerecord/test/cases/binary_test.rb
index 1fc30e24d2..d5376ece69 100644
--- a/activerecord/test/cases/binary_test.rb
+++ b/activerecord/test/cases/binary_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
# Without using prepared statements, it makes no sense to test
@@ -10,7 +12,7 @@ unless current_adapter?(:DB2Adapter)
FIXTURES = %w(flowers.jpg example.log test.txt)
def test_mixed_encoding
- str = "\x80"
+ str = "\x80".dup
str.force_encoding("ASCII-8BIT")
binary = Binary.new name: "いただきます!", data: str
diff --git a/activerecord/test/cases/bind_parameter_test.rb b/activerecord/test/cases/bind_parameter_test.rb
index 98d202dd79..91cc49385c 100644
--- a/activerecord/test/cases/bind_parameter_test.rb
+++ b/activerecord/test/cases/bind_parameter_test.rb
@@ -1,38 +1,39 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/topic"
require "models/author"
require "models/post"
-module ActiveRecord
- class BindParameterTest < ActiveRecord::TestCase
- fixtures :topics, :authors, :posts
+if ActiveRecord::Base.connection.prepared_statements
+ module ActiveRecord
+ class BindParameterTest < ActiveRecord::TestCase
+ fixtures :topics, :authors, :author_addresses, :posts
- class LogListener
- attr_accessor :calls
+ class LogListener
+ attr_accessor :calls
- def initialize
- @calls = []
- end
+ def initialize
+ @calls = []
+ end
- def call(*args)
- calls << args
+ def call(*args)
+ calls << args
+ end
end
- end
- def setup
- super
- @connection = ActiveRecord::Base.connection
- @subscriber = LogListener.new
- @pk = Topic.columns_hash[Topic.primary_key]
- @subscription = ActiveSupport::Notifications.subscribe("sql.active_record", @subscriber)
- end
+ def setup
+ super
+ @connection = ActiveRecord::Base.connection
+ @subscriber = LogListener.new
+ @pk = Topic.columns_hash[Topic.primary_key]
+ @subscription = ActiveSupport::Notifications.subscribe("sql.active_record", @subscriber)
+ end
- teardown do
- ActiveSupport::Notifications.unsubscribe(@subscription)
- end
+ def teardown
+ ActiveSupport::Notifications.unsubscribe(@subscription)
+ end
- if ActiveRecord::Base.connection.supports_statement_cache? &&
- ActiveRecord::Base.connection.prepared_statements
def test_bind_from_join_in_subquery
subquery = Author.joins(:thinking_posts).where(name: "David")
scope = Author.from(subquery, "authors").where(id: 1)
@@ -40,7 +41,7 @@ module ActiveRecord
end
def test_binds_are_logged
- sub = Arel::Nodes::BindParam.new
+ sub = Arel::Nodes::BindParam.new(1)
binds = [Relation::QueryAttribute.new("id", 1, Type::Value.new)]
sql = "select * from topics where id = #{sub.to_sql}"
@@ -56,43 +57,52 @@ module ActiveRecord
assert message, "expected a message with binds"
end
- def test_logs_bind_vars_after_type_cast
+ def test_logs_binds_after_type_cast
binds = [Relation::QueryAttribute.new("id", "10", Type::Integer.new)]
- type_casted_binds = binds.map { |attr| type_cast(attr.value_for_database) }
- payload = {
- name: "SQL",
- sql: "select * from topics where id = ?",
- binds: binds,
- type_casted_binds: type_casted_binds
- }
- event = ActiveSupport::Notifications::Event.new(
- "foo",
- Time.now,
- Time.now,
- 123,
- payload)
-
- logger = Class.new(ActiveRecord::LogSubscriber) {
- attr_reader :debugs
- def initialize
- super
- @debugs = []
- end
-
- def debug(str)
- @debugs << str
- end
- }.new
-
- logger.sql event
- assert_match([[@pk.name, 10]].inspect, logger.debugs.first)
+ assert_logs_binds(binds)
end
- private
+ def test_logs_legacy_binds_after_type_cast
+ binds = [[@pk, "10"]]
+ assert_logs_binds(binds)
+ end
- def type_cast(value)
- ActiveRecord::Base.connection.type_cast(value)
+ def test_deprecate_supports_statement_cache
+ assert_deprecated { ActiveRecord::Base.connection.supports_statement_cache? }
end
+
+ private
+ def assert_logs_binds(binds)
+ payload = {
+ name: "SQL",
+ sql: "select * from topics where id = ?",
+ binds: binds,
+ type_casted_binds: @connection.type_casted_binds(binds)
+ }
+
+ event = ActiveSupport::Notifications::Event.new(
+ "foo",
+ Time.now,
+ Time.now,
+ 123,
+ payload)
+
+ logger = Class.new(ActiveRecord::LogSubscriber) {
+ attr_reader :debugs
+
+ def initialize
+ super
+ @debugs = []
+ end
+
+ def debug(str)
+ @debugs << str
+ end
+ }.new
+
+ logger.sql(event)
+ assert_match([[@pk.name, 10]].inspect, logger.debugs.first)
+ end
end
end
end
diff --git a/activerecord/test/cases/boolean_test.rb b/activerecord/test/cases/boolean_test.rb
new file mode 100644
index 0000000000..ab9f974e2c
--- /dev/null
+++ b/activerecord/test/cases/boolean_test.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+require "cases/helper"
+require "models/boolean"
+
+class BooleanTest < ActiveRecord::TestCase
+ def test_boolean
+ b_nil = Boolean.create!(value: nil)
+ b_false = Boolean.create!(value: false)
+ b_true = Boolean.create!(value: true)
+
+ assert_nil Boolean.find(b_nil.id).value
+ assert_not_predicate Boolean.find(b_false.id), :value?
+ assert_predicate Boolean.find(b_true.id), :value?
+ end
+
+ def test_boolean_without_questionmark
+ b_true = Boolean.create!(value: true)
+
+ subclass = Class.new(Boolean).find(b_true.id)
+ superclass = Boolean.find(b_true.id)
+
+ assert_equal superclass.read_attribute(:has_fun), subclass.read_attribute(:has_fun)
+ end
+
+ def test_boolean_cast_from_string
+ b_blank = Boolean.create!(value: "")
+ b_false = Boolean.create!(value: "0")
+ b_true = Boolean.create!(value: "1")
+
+ assert_nil Boolean.find(b_blank.id).value
+ assert_not_predicate Boolean.find(b_false.id), :value?
+ assert_predicate Boolean.find(b_true.id), :value?
+ end
+
+ def test_find_by_boolean_string
+ b_false = Boolean.create!(value: "false")
+ b_true = Boolean.create!(value: "true")
+
+ assert_equal b_false, Boolean.find_by(value: "false")
+ assert_equal b_true, Boolean.find_by(value: "true")
+ end
+end
diff --git a/activerecord/test/cases/cache_key_test.rb b/activerecord/test/cases/cache_key_test.rb
index bb2829b3c1..3a569f226e 100644
--- a/activerecord/test/cases/cache_key_test.rb
+++ b/activerecord/test/cases/cache_key_test.rb
@@ -1,25 +1,53 @@
+# frozen_string_literal: true
+
require "cases/helper"
module ActiveRecord
class CacheKeyTest < ActiveRecord::TestCase
self.use_transactional_tests = false
- class CacheMe < ActiveRecord::Base; end
+ class CacheMe < ActiveRecord::Base
+ self.cache_versioning = false
+ end
+
+ class CacheMeWithVersion < ActiveRecord::Base
+ self.cache_versioning = true
+ end
setup do
@connection = ActiveRecord::Base.connection
- @connection.create_table(:cache_mes) { |t| t.timestamps }
+ @connection.create_table(:cache_mes, force: true) { |t| t.timestamps }
+ @connection.create_table(:cache_me_with_versions, force: true) { |t| t.timestamps }
end
teardown do
@connection.drop_table :cache_mes, if_exists: true
+ @connection.drop_table :cache_me_with_versions, if_exists: true
end
- test "test_cache_key_format_is_not_too_precise" do
+ test "cache_key format is not too precise" do
record = CacheMe.create
key = record.cache_key
assert_equal key, record.reload.cache_key
end
+
+ test "cache_key has no version when versioning is on" do
+ record = CacheMeWithVersion.create
+ assert_equal "active_record/cache_key_test/cache_me_with_versions/#{record.id}", record.cache_key
+ end
+
+ test "cache_version is only there when versioning is on" do
+ assert_predicate CacheMeWithVersion.create.cache_version, :present?
+ assert_not_predicate CacheMe.create.cache_version, :present?
+ end
+
+ test "cache_key_with_version always has both key and version" do
+ r1 = CacheMeWithVersion.create
+ assert_equal "active_record/cache_key_test/cache_me_with_versions/#{r1.id}-#{r1.updated_at.to_s(:usec)}", r1.cache_key_with_version
+
+ r2 = CacheMe.create
+ assert_equal "active_record/cache_key_test/cache_mes/#{r2.id}-#{r2.updated_at.to_s(:usec)}", r2.cache_key_with_version
+ end
end
end
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index edf7ce0bca..5c9ed42173 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/book"
require "models/club"
@@ -8,6 +10,7 @@ require "models/organization"
require "models/possession"
require "models/topic"
require "models/reply"
+require "models/numeric_data"
require "models/minivan"
require "models/speedometer"
require "models/ship_part"
@@ -17,16 +20,8 @@ require "models/post"
require "models/comment"
require "models/rating"
-class NumericData < ActiveRecord::Base
- self.table_name = "numeric_data"
-
- attribute :world_population, :integer
- attribute :my_house_population, :integer
- attribute :atoms_in_universe, :integer
-end
-
class CalculationsTest < ActiveRecord::TestCase
- fixtures :companies, :accounts, :topics, :speedometers, :minivans, :books
+ fixtures :companies, :accounts, :topics, :speedometers, :minivans, :books, :posts, :comments
def test_should_sum_field
assert_equal 318, Account.sum(:credit_limit)
@@ -241,6 +236,52 @@ class CalculationsTest < ActiveRecord::TestCase
end
end
+ def test_count_with_eager_loading_and_custom_order
+ posts = Post.includes(:comments).order("comments.id")
+ assert_queries(1) { assert_equal 11, posts.count }
+ assert_queries(1) { assert_equal 11, posts.count(:all) }
+ end
+
+ def test_count_with_eager_loading_and_custom_order_and_distinct
+ posts = Post.includes(:comments).order("comments.id").distinct
+ assert_queries(1) { assert_equal 11, posts.count }
+ assert_queries(1) { assert_equal 11, posts.count(:all) }
+ end
+
+ def test_distinct_count_all_with_custom_select_and_order
+ accounts = Account.distinct.select("credit_limit % 10").order(Arel.sql("credit_limit % 10"))
+ assert_queries(1) { assert_equal 3, accounts.count(:all) }
+ assert_queries(1) { assert_equal 3, accounts.load.size }
+ end
+
+ def test_distinct_count_with_order_and_limit
+ assert_equal 4, Account.distinct.order(:firm_id).limit(4).count
+ end
+
+ def test_distinct_count_with_order_and_offset
+ assert_equal 4, Account.distinct.order(:firm_id).offset(2).count
+ end
+
+ def test_distinct_count_with_order_and_limit_and_offset
+ assert_equal 4, Account.distinct.order(:firm_id).limit(4).offset(2).count
+ end
+
+ def test_distinct_joins_count_with_order_and_limit
+ assert_equal 3, Account.joins(:firm).distinct.order(:firm_id).limit(3).count
+ end
+
+ def test_distinct_joins_count_with_order_and_offset
+ assert_equal 3, Account.joins(:firm).distinct.order(:firm_id).offset(2).count
+ end
+
+ def test_distinct_joins_count_with_order_and_limit_and_offset
+ assert_equal 3, Account.joins(:firm).distinct.order(:firm_id).limit(3).offset(2).count
+ end
+
+ def test_distinct_count_with_group_by_and_order_and_limit
+ assert_equal({ 6 => 2 }, Account.group(:firm_id).distinct.order("1 DESC").limit(1).count)
+ end
+
def test_should_group_by_summed_field_having_condition
c = Account.group(:firm_id).having("sum(credit_limit) > 50").sum(:credit_limit)
assert_nil c[1]
@@ -249,7 +290,7 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_should_group_by_summed_field_having_condition_from_select
- skip if current_adapter?(:PostgreSQLAdapter)
+ skip unless current_adapter?(:Mysql2Adapter, :SQLite3Adapter)
c = Account.select("MIN(credit_limit) AS min_credit_limit").group(:firm_id).having("min_credit_limit > 50").sum(:credit_limit)
assert_nil c[1]
assert_equal 60, c[2]
@@ -499,8 +540,7 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_should_sum_expression
- # Oracle adapter returns floating point value 636.0 after SUM
- if current_adapter?(:OracleAdapter)
+ if current_adapter?(:SQLite3Adapter, :Mysql2Adapter, :PostgreSQLAdapter, :OracleAdapter)
assert_equal 636, Account.sum("2 * credit_limit")
else
assert_equal 636, Account.sum("2 * credit_limit").to_i
@@ -587,8 +627,11 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_pluck_without_column_names
- assert_equal [[1, "Firm", 1, nil, "37signals", nil, 1, nil, ""]],
- Company.order(:id).limit(1).pluck
+ if current_adapter?(:OracleAdapter)
+ assert_equal [[1, "Firm", 1, nil, "37signals", nil, 1, nil, nil]], Company.order(:id).limit(1).pluck
+ else
+ assert_equal [[1, "Firm", 1, nil, "37signals", nil, 1, nil, ""]], Company.order(:id).limit(1).pluck
+ end
end
def test_pluck_type_cast
@@ -599,6 +642,18 @@ class CalculationsTest < ActiveRecord::TestCase
assert_equal [ topic.written_on ], relation.pluck(:written_on)
end
+ def test_pluck_with_type_cast_does_not_corrupt_the_query_cache
+ topic = topics(:first)
+ relation = Topic.where(id: topic.id)
+ assert_queries 1 do
+ Topic.cache do
+ kind = relation.select(:written_on).load.first.read_attribute_before_type_cast(:written_on).class
+ relation.pluck(:written_on)
+ assert_kind_of kind, relation.select(:written_on).load.first.read_attribute_before_type_cast(:written_on)
+ end
+ end
+ end
+
def test_pluck_and_distinct
assert_equal [50, 53, 55, 60], Account.order(:credit_limit).distinct.pluck(:credit_limit)
end
@@ -638,14 +693,14 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_pluck_with_selection_clause
- assert_equal [50, 53, 55, 60], Account.pluck("DISTINCT credit_limit").sort
- assert_equal [50, 53, 55, 60], Account.pluck("DISTINCT accounts.credit_limit").sort
- assert_equal [50, 53, 55, 60], Account.pluck("DISTINCT(credit_limit)").sort
+ assert_equal [50, 53, 55, 60], Account.pluck(Arel.sql("DISTINCT credit_limit")).sort
+ assert_equal [50, 53, 55, 60], Account.pluck(Arel.sql("DISTINCT accounts.credit_limit")).sort
+ assert_equal [50, 53, 55, 60], Account.pluck(Arel.sql("DISTINCT(credit_limit)")).sort
# MySQL returns "SUM(DISTINCT(credit_limit))" as the column name unless
# an alias is provided. Without the alias, the column cannot be found
# and properly typecast.
- assert_equal [50 + 53 + 55 + 60], Account.pluck("SUM(DISTINCT(credit_limit)) as credit_limit")
+ assert_equal [50 + 53 + 55 + 60], Account.pluck(Arel.sql("SUM(DISTINCT(credit_limit)) as credit_limit"))
end
def test_plucks_with_ids
@@ -657,6 +712,29 @@ class CalculationsTest < ActiveRecord::TestCase
assert_equal [], Topic.includes(:replies).limit(1).where("0 = 1").pluck(:id)
end
+ def test_pluck_with_includes_offset
+ assert_equal [5], Topic.includes(:replies).order(:id).offset(4).pluck(:id)
+ assert_equal [], Topic.includes(:replies).order(:id).offset(5).pluck(:id)
+ end
+
+ def test_group_by_with_limit
+ expected = { "Post" => 8, "SpecialPost" => 1 }
+ actual = Post.includes(:comments).group(:type).order(:type).limit(2).count("comments.id")
+ assert_equal expected, actual
+ end
+
+ def test_group_by_with_offset
+ expected = { "SpecialPost" => 1, "StiPost" => 2 }
+ actual = Post.includes(:comments).group(:type).order(:type).offset(1).count("comments.id")
+ assert_equal expected, actual
+ end
+
+ def test_group_by_with_limit_and_offset
+ expected = { "SpecialPost" => 1 }
+ actual = Post.includes(:comments).group(:type).order(:type).offset(1).limit(1).count("comments.id")
+ assert_equal expected, actual
+ end
+
def test_pluck_not_auto_table_name_prefix_if_column_included
Company.create!(name: "test", contracts: [Contract.new(developer_id: 7)])
ids = Company.includes(:contracts).pluck(:developer_id)
@@ -725,26 +803,49 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_pluck_loaded_relation
+ Company.attribute_names # Load schema information so we don't query below
companies = Company.order(:id).limit(3).load
+
assert_no_queries do
assert_equal ["37signals", "Summit", "Microsoft"], companies.pluck(:name)
end
end
def test_pluck_loaded_relation_multiple_columns
+ Company.attribute_names # Load schema information so we don't query below
companies = Company.order(:id).limit(3).load
+
assert_no_queries do
assert_equal [[1, "37signals"], [2, "Summit"], [3, "Microsoft"]], companies.pluck(:id, :name)
end
end
def test_pluck_loaded_relation_sql_fragment
+ Company.attribute_names # Load schema information so we don't query below
companies = Company.order(:name).limit(3).load
+
assert_queries 1 do
- assert_equal ["37signals", "Apex", "Ex Nihilo"], companies.pluck("DISTINCT name")
+ assert_equal ["37signals", "Apex", "Ex Nihilo"], companies.pluck(Arel.sql("DISTINCT name"))
end
end
+ def test_pick_one
+ assert_equal "The First Topic", Topic.order(:id).pick(:heading)
+ assert_nil Topic.none.pick(:heading)
+ assert_nil Topic.where("1=0").pick(:heading)
+ end
+
+ def test_pick_two
+ assert_equal ["David", "david@loudthinking.com"], Topic.order(:id).pick(:author_name, :author_email_address)
+ assert_nil Topic.none.pick(:author_name, :author_email_address)
+ assert_nil Topic.where("1=0").pick(:author_name, :author_email_address)
+ end
+
+ def test_pick_delegate_to_all
+ cool_first = minivans(:cool_first)
+ assert_equal cool_first.color, Minivan.pick(:color)
+ end
+
def test_grouped_calculation_with_polymorphic_relation
part = ShipPart.create!(name: "has trinket")
part.trinkets.create!
@@ -809,4 +910,58 @@ class CalculationsTest < ActiveRecord::TestCase
def test_group_by_attribute_with_custom_type
assert_equal({ "proposed" => 2, "published" => 2 }, Book.group(:status).count)
end
+
+ def test_deprecate_count_with_block_and_column_name
+ assert_deprecated do
+ assert_equal 6, Account.count(:firm_id) { true }
+ end
+ end
+
+ def test_deprecate_sum_with_block_and_column_name
+ assert_deprecated do
+ assert_equal 6, Account.sum(:firm_id) { 1 }
+ end
+ end
+
+ test "#skip_query_cache! for #pluck" do
+ Account.cache do
+ assert_queries(1) do
+ Account.pluck(:credit_limit)
+ Account.pluck(:credit_limit)
+ end
+
+ assert_queries(2) do
+ Account.all.skip_query_cache!.pluck(:credit_limit)
+ Account.all.skip_query_cache!.pluck(:credit_limit)
+ end
+ end
+ end
+
+ test "#skip_query_cache! for a simple calculation" do
+ Account.cache do
+ assert_queries(1) do
+ Account.calculate(:sum, :credit_limit)
+ Account.calculate(:sum, :credit_limit)
+ end
+
+ assert_queries(2) do
+ Account.all.skip_query_cache!.calculate(:sum, :credit_limit)
+ Account.all.skip_query_cache!.calculate(:sum, :credit_limit)
+ end
+ end
+ end
+
+ test "#skip_query_cache! for a grouped calculation" do
+ Account.cache do
+ assert_queries(1) do
+ Account.group(:firm_id).calculate(:sum, :credit_limit)
+ Account.group(:firm_id).calculate(:sum, :credit_limit)
+ end
+
+ assert_queries(2) do
+ Account.all.skip_query_cache!.group(:firm_id).calculate(:sum, :credit_limit)
+ Account.all.skip_query_cache!.group(:firm_id).calculate(:sum, :credit_limit)
+ end
+ end
+ end
end
diff --git a/activerecord/test/cases/callbacks_test.rb b/activerecord/test/cases/callbacks_test.rb
index b3c86586d0..253c3099d6 100644
--- a/activerecord/test/cases/callbacks_test.rb
+++ b/activerecord/test/cases/callbacks_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/developer"
require "models/computer"
@@ -383,60 +385,60 @@ class CallbacksTest < ActiveRecord::TestCase
end
def assert_save_callbacks_not_called(someone)
- assert !someone.after_save_called
- assert !someone.after_create_called
- assert !someone.after_update_called
+ assert_not someone.after_save_called
+ assert_not someone.after_create_called
+ assert_not someone.after_update_called
end
private :assert_save_callbacks_not_called
def test_before_create_throwing_abort
someone = CallbackHaltedDeveloper.new
someone.cancel_before_create = true
- assert someone.valid?
- assert !someone.save
+ assert_predicate someone, :valid?
+ assert_not someone.save
assert_save_callbacks_not_called(someone)
end
def test_before_save_throwing_abort
david = DeveloperWithCanceledCallbacks.find(1)
- assert david.valid?
- assert !david.save
+ assert_predicate david, :valid?
+ assert_not david.save
exc = assert_raise(ActiveRecord::RecordNotSaved) { david.save! }
assert_equal david, exc.record
david = DeveloperWithCanceledCallbacks.find(1)
david.salary = 10_000_000
- assert !david.valid?
- assert !david.save
+ assert_not_predicate david, :valid?
+ assert_not david.save
assert_raise(ActiveRecord::RecordInvalid) { david.save! }
someone = CallbackHaltedDeveloper.find(1)
someone.cancel_before_save = true
- assert someone.valid?
- assert !someone.save
+ assert_predicate someone, :valid?
+ assert_not someone.save
assert_save_callbacks_not_called(someone)
end
def test_before_update_throwing_abort
someone = CallbackHaltedDeveloper.find(1)
someone.cancel_before_update = true
- assert someone.valid?
- assert !someone.save
+ assert_predicate someone, :valid?
+ assert_not someone.save
assert_save_callbacks_not_called(someone)
end
def test_before_destroy_throwing_abort
david = DeveloperWithCanceledCallbacks.find(1)
- assert !david.destroy
+ assert_not david.destroy
exc = assert_raise(ActiveRecord::RecordNotDestroyed) { david.destroy! }
assert_equal david, exc.record
assert_not_nil ImmutableDeveloper.find_by_id(1)
someone = CallbackHaltedDeveloper.find(1)
someone.cancel_before_destroy = true
- assert !someone.destroy
+ assert_not someone.destroy
assert_raise(ActiveRecord::RecordNotDestroyed) { someone.destroy! }
- assert !someone.after_destroy_called
+ assert_not someone.after_destroy_called
end
def test_callback_throwing_abort
@@ -465,13 +467,40 @@ class CallbacksTest < ActiveRecord::TestCase
def test_inheritance_of_callbacks
parent = ParentDeveloper.new
- assert !parent.after_save_called
+ assert_not parent.after_save_called
parent.save
assert parent.after_save_called
child = ChildDeveloper.new
- assert !child.after_save_called
+ assert_not child.after_save_called
child.save
assert child.after_save_called
end
+
+ def test_before_save_doesnt_allow_on_option
+ exception = assert_raises ArgumentError do
+ Class.new(ActiveRecord::Base) do
+ before_save(on: :create) {}
+ end
+ end
+ assert_equal "Unknown key: :on. Valid keys are: :if, :unless, :prepend", exception.message
+ end
+
+ def test_around_save_doesnt_allow_on_option
+ exception = assert_raises ArgumentError do
+ Class.new(ActiveRecord::Base) do
+ around_save(on: :create) {}
+ end
+ end
+ assert_equal "Unknown key: :on. Valid keys are: :if, :unless, :prepend", exception.message
+ end
+
+ def test_after_save_doesnt_allow_on_option
+ exception = assert_raises ArgumentError do
+ Class.new(ActiveRecord::Base) do
+ after_save(on: :create) {}
+ end
+ end
+ assert_equal "Unknown key: :on. Valid keys are: :if, :unless, :prepend", exception.message
+ end
end
diff --git a/activerecord/test/cases/clone_test.rb b/activerecord/test/cases/clone_test.rb
index b89294c094..eea36ee736 100644
--- a/activerecord/test/cases/clone_test.rb
+++ b/activerecord/test/cases/clone_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/topic"
@@ -10,7 +12,7 @@ module ActiveRecord
cloned = topic.clone
assert topic.persisted?, "topic persisted"
assert cloned.persisted?, "topic persisted"
- assert !cloned.new_record?, "topic is not new"
+ assert_not cloned.new_record?, "topic is not new"
end
def test_stays_frozen
@@ -19,7 +21,7 @@ module ActiveRecord
cloned = topic.clone
assert cloned.persisted?, "topic persisted"
- assert !cloned.new_record?, "topic is not new"
+ assert_not cloned.new_record?, "topic is not new"
assert cloned.frozen?, "topic should be frozen"
end
@@ -34,7 +36,7 @@ module ActiveRecord
cloned = Topic.new
clone = cloned.clone
cloned.freeze
- assert_not clone.frozen?
+ assert_not_predicate clone, :frozen?
end
end
end
diff --git a/activerecord/test/cases/coders/json_test.rb b/activerecord/test/cases/coders/json_test.rb
index d22d93d129..e40d576b39 100644
--- a/activerecord/test/cases/coders/json_test.rb
+++ b/activerecord/test/cases/coders/json_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
module ActiveRecord
diff --git a/activerecord/test/cases/coders/yaml_column_test.rb b/activerecord/test/cases/coders/yaml_column_test.rb
index 59ef389326..4a5559c62f 100644
--- a/activerecord/test/cases/coders/yaml_column_test.rb
+++ b/activerecord/test/cases/coders/yaml_column_test.rb
@@ -1,3 +1,4 @@
+# frozen_string_literal: true
require "cases/helper"
diff --git a/activerecord/test/cases/collection_cache_key_test.rb b/activerecord/test/cases/collection_cache_key_test.rb
index 381a78a8e2..a5d908344a 100644
--- a/activerecord/test/cases/collection_cache_key_test.rb
+++ b/activerecord/test/cases/collection_cache_key_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/computer"
require "models/developer"
@@ -11,22 +13,80 @@ module ActiveRecord
fixtures :developers, :projects, :developers_projects, :topics, :comments, :posts
test "collection_cache_key on model" do
- assert_match(/\Adevelopers\/query-(\h+)-(\d+)-(\d+)\Z/, Developer.collection_cache_key)
+ assert_match(/\Adevelopers\/query-(\h+)-(\d+)-(\d+)\z/, Developer.collection_cache_key)
end
test "cache_key for relation" do
- developers = Developer.where(name: "David")
- last_developer_timestamp = developers.order(updated_at: :desc).first.updated_at
+ developers = Developer.where(salary: 100000).order(updated_at: :desc)
+ last_developer_timestamp = developers.first.updated_at
+
+ assert_match(/\Adevelopers\/query-(\h+)-(\d+)-(\d+)\z/, developers.cache_key)
+
+ /\Adevelopers\/query-(\h+)-(\d+)-(\d+)\z/ =~ developers.cache_key
+
+ assert_equal ActiveSupport::Digest.hexdigest(developers.to_sql), $1
+ assert_equal developers.count.to_s, $2
+ assert_equal last_developer_timestamp.to_s(ActiveRecord::Base.cache_timestamp_format), $3
+ end
+
+ test "cache_key for relation with limit" do
+ developers = Developer.where(salary: 100000).order(updated_at: :desc).limit(5)
+ last_developer_timestamp = developers.first.updated_at
+
+ assert_match(/\Adevelopers\/query-(\h+)-(\d+)-(\d+)\z/, developers.cache_key)
+
+ /\Adevelopers\/query-(\h+)-(\d+)-(\d+)\z/ =~ developers.cache_key
+
+ assert_equal ActiveSupport::Digest.hexdigest(developers.to_sql), $1
+ assert_equal developers.count.to_s, $2
+ assert_equal last_developer_timestamp.to_s(ActiveRecord::Base.cache_timestamp_format), $3
+ end
+
+ test "cache_key for loaded relation" do
+ developers = Developer.where(salary: 100000).order(updated_at: :desc).limit(5).load
+ last_developer_timestamp = developers.first.updated_at
+
+ assert_match(/\Adevelopers\/query-(\h+)-(\d+)-(\d+)\z/, developers.cache_key)
- assert_match(/\Adevelopers\/query-(\h+)-(\d+)-(\d+)\Z/, developers.cache_key)
+ /\Adevelopers\/query-(\h+)-(\d+)-(\d+)\z/ =~ developers.cache_key
- /\Adevelopers\/query-(\h+)-(\d+)-(\d+)\Z/ =~ developers.cache_key
+ assert_equal ActiveSupport::Digest.hexdigest(developers.to_sql), $1
+ assert_equal developers.count.to_s, $2
+ assert_equal last_developer_timestamp.to_s(ActiveRecord::Base.cache_timestamp_format), $3
+ end
+
+ test "cache_key for relation with table alias" do
+ table_alias = Developer.arel_table.alias("omg_developers")
+ table_metadata = ActiveRecord::TableMetadata.new(Developer, table_alias)
+ predicate_builder = ActiveRecord::PredicateBuilder.new(table_metadata)
+
+ developers = ActiveRecord::Relation.create(
+ Developer,
+ table: table_alias,
+ predicate_builder: predicate_builder
+ )
+ developers = developers.where(salary: 100000).order(updated_at: :desc)
+ last_developer_timestamp = developers.first.updated_at
- assert_equal Digest::MD5.hexdigest(developers.to_sql), $1
+ assert_match(/\Adevelopers\/query-(\h+)-(\d+)-(\d+)\z/, developers.cache_key)
+
+ /\Adevelopers\/query-(\h+)-(\d+)-(\d+)\z/ =~ developers.cache_key
+
+ assert_equal ActiveSupport::Digest.hexdigest(developers.to_sql), $1
assert_equal developers.count.to_s, $2
assert_equal last_developer_timestamp.to_s(ActiveRecord::Base.cache_timestamp_format), $3
end
+ test "cache_key for relation with includes" do
+ comments = Comment.includes(:post).where("posts.type": "Post")
+ assert_match(/\Acomments\/query-(\h+)-(\d+)-(\d+)\z/, comments.cache_key)
+ end
+
+ test "cache_key for loaded relation with includes" do
+ comments = Comment.includes(:post).where("posts.type": "Post").load
+ assert_match(/\Acomments\/query-(\h+)-(\d+)-(\d+)\z/, comments.cache_key)
+ end
+
test "it triggers at most one query" do
developers = Developer.where(name: "David")
@@ -48,7 +108,7 @@ module ActiveRecord
test "cache_key for empty relation" do
developers = Developer.where(name: "Non Existent Developer")
- assert_match(/\Adevelopers\/query-(\h+)-0\Z/, developers.cache_key)
+ assert_match(/\Adevelopers\/query-(\h+)-0\z/, developers.cache_key)
end
test "cache_key with custom timestamp column" do
@@ -64,7 +124,7 @@ module ActiveRecord
test "collection proxy provides a cache_key" do
developers = projects(:active_record).developers
- assert_match(/\Adevelopers\/query-(\h+)-(\d+)-(\d+)\Z/, developers.cache_key)
+ assert_match(/\Adevelopers\/query-(\h+)-(\d+)-(\d+)\z/, developers.cache_key)
end
test "cache_key for loaded collection with zero size" do
@@ -72,18 +132,30 @@ module ActiveRecord
posts = Post.includes(:comments)
empty_loaded_collection = posts.first.comments
- assert_match(/\Acomments\/query-(\h+)-0\Z/, empty_loaded_collection.cache_key)
+ assert_match(/\Acomments\/query-(\h+)-0\z/, empty_loaded_collection.cache_key)
end
test "cache_key for queries with offset which return 0 rows" do
developers = Developer.offset(20)
- assert_match(/\Adevelopers\/query-(\h+)-0\Z/, developers.cache_key)
+ assert_match(/\Adevelopers\/query-(\h+)-0\z/, developers.cache_key)
end
test "cache_key with a relation having selected columns" do
developers = Developer.select(:salary)
- assert_match(/\Adevelopers\/query-(\h+)-(\d+)-(\d+)\Z/, developers.cache_key)
+ assert_match(/\Adevelopers\/query-(\h+)-(\d+)-(\d+)\z/, developers.cache_key)
+ end
+
+ test "cache_key with a relation having distinct and order" do
+ developers = Developer.distinct.order(:salary).limit(5)
+
+ assert_match(/\Adevelopers\/query-(\h+)-(\d+)-(\d+)\z/, developers.cache_key)
+ end
+
+ test "cache_key with a relation having custom select and order" do
+ developers = Developer.select("name AS dev_name").order("dev_name DESC").limit(5)
+
+ assert_match(/\Adevelopers\/query-(\h+)-(\d+)-(\d+)\z/, developers.cache_key)
end
end
end
diff --git a/activerecord/test/cases/column_alias_test.rb b/activerecord/test/cases/column_alias_test.rb
index 9893ba9580..a883d21fb8 100644
--- a/activerecord/test/cases/column_alias_test.rb
+++ b/activerecord/test/cases/column_alias_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/topic"
diff --git a/activerecord/test/cases/column_definition_test.rb b/activerecord/test/cases/column_definition_test.rb
index d230700119..cbd2b44589 100644
--- a/activerecord/test/cases/column_definition_test.rb
+++ b/activerecord/test/cases/column_definition_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
module ActiveRecord
@@ -8,7 +10,7 @@ module ActiveRecord
def @adapter.native_database_types
{ string: "varchar" }
end
- @viz = @adapter.schema_creation
+ @viz = @adapter.send(:schema_creation)
end
# Avoid column definitions in create table statements like:
diff --git a/activerecord/test/cases/comment_test.rb b/activerecord/test/cases/comment_test.rb
index 63f67a9a16..584e03d196 100644
--- a/activerecord/test/cases/comment_test.rb
+++ b/activerecord/test/cases/comment_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "support/schema_dumping_helper"
@@ -72,9 +74,11 @@ if ActiveRecord::Base.connection.supports_comments?
end
def test_add_index_with_comment_later
- @connection.add_index :commenteds, :obvious, name: "idx_obvious", comment: "We need to see obvious comments"
- index = @connection.indexes("commenteds").find { |idef| idef.name == "idx_obvious" }
- assert_equal "We need to see obvious comments", index.comment
+ unless current_adapter?(:OracleAdapter)
+ @connection.add_index :commenteds, :obvious, name: "idx_obvious", comment: "We need to see obvious comments"
+ index = @connection.indexes("commenteds").find { |idef| idef.name == "idx_obvious" }
+ assert_equal "We need to see obvious comments", index.comment
+ end
end
def test_add_comment_to_column
@@ -107,13 +111,17 @@ if ActiveRecord::Base.connection.supports_comments?
# And check that these changes are reflected in dump
output = dump_table_schema "commenteds"
- assert_match %r[create_table "commenteds",.+\s+comment: "A table with comment"], output
+ assert_match %r[create_table "commenteds",.*\s+comment: "A table with comment"], output
assert_match %r[t\.string\s+"name",\s+comment: "Comment should help clarify the column purpose"], output
assert_match %r[t\.string\s+"obvious"\n], output
assert_match %r[t\.string\s+"content",\s+comment: "Whoa, content describes itself!"], output
- assert_match %r[t\.integer\s+"rating",\s+comment: "I am running out of imagination"], output
- assert_match %r[t\.index\s+.+\s+comment: "\\\"Very important\\\" index that powers all the performance.\\nAnd it's fun!"], output
- assert_match %r[t\.index\s+.+\s+name: "idx_obvious",\s+comment: "We need to see obvious comments"], output
+ if current_adapter?(:OracleAdapter)
+ assert_match %r[t\.integer\s+"rating",\s+precision: 38,\s+comment: "I am running out of imagination"], output
+ else
+ assert_match %r[t\.integer\s+"rating",\s+comment: "I am running out of imagination"], output
+ assert_match %r[t\.index\s+.+\s+comment: "\\\"Very important\\\" index that powers all the performance.\\nAnd it's fun!"], output
+ assert_match %r[t\.index\s+.+\s+name: "idx_obvious",\s+comment: "We need to see obvious comments"], output
+ end
end
def test_schema_dump_omits_blank_comments
@@ -134,5 +142,27 @@ if ActiveRecord::Base.connection.supports_comments?
assert_match %r[t\.string\s+"absent_comment"\n], output
assert_no_match %r[t\.string\s+"absent_comment", comment:\n], output
end
+
+ def test_change_table_comment
+ @connection.change_table_comment :commenteds, "Edited table comment"
+ assert_equal "Edited table comment", @connection.table_comment("commenteds")
+ end
+
+ def test_change_table_comment_to_nil
+ @connection.change_table_comment :commenteds, nil
+ assert_nil @connection.table_comment("commenteds")
+ end
+
+ def test_change_column_comment
+ @connection.change_column_comment :commenteds, :name, "Edited column comment"
+ column = Commented.columns_hash["name"]
+ assert_equal "Edited column comment", column.comment
+ end
+
+ def test_change_column_comment_to_nil
+ @connection.change_column_comment :commenteds, :name, nil
+ column = Commented.columns_hash["name"]
+ assert_nil column.comment
+ end
end
end
diff --git a/activerecord/test/cases/connection_adapters/adapter_leasing_test.rb b/activerecord/test/cases/connection_adapters/adapter_leasing_test.rb
index 64189381cb..72838ff56b 100644
--- a/activerecord/test/cases/connection_adapters/adapter_leasing_test.rb
+++ b/activerecord/test/cases/connection_adapters/adapter_leasing_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
module ActiveRecord
@@ -43,11 +45,11 @@ module ActiveRecord
# Make sure the pool marks the connection in use
assert_equal @adapter, pool.connection
- assert @adapter.in_use?
+ assert_predicate @adapter, :in_use?
# Close should put the adapter back in the pool
@adapter.close
- assert_not @adapter.in_use?
+ assert_not_predicate @adapter, :in_use?
assert_equal @adapter, pool.connection
end
diff --git a/activerecord/test/cases/connection_adapters/connection_handler_test.rb b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
index 681399c8bb..8c204a2692 100644
--- a/activerecord/test/cases/connection_adapters/connection_handler_test.rb
+++ b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
@@ -1,22 +1,43 @@
+# frozen_string_literal: true
+
require "cases/helper"
+require "models/person"
module ActiveRecord
module ConnectionAdapters
class ConnectionHandlerTest < ActiveRecord::TestCase
+ self.use_transactional_tests = false
+
+ fixtures :people
+
def setup
@handler = ConnectionHandler.new
@spec_name = "primary"
@pool = @handler.establish_connection(ActiveRecord::Base.configurations["arunit"])
end
+ def test_default_env_fall_back_to_default_env_when_rails_env_or_rack_env_is_empty_string
+ original_rails_env = ENV["RAILS_ENV"]
+ original_rack_env = ENV["RACK_ENV"]
+ ENV["RAILS_ENV"] = ENV["RACK_ENV"] = ""
+
+ assert_equal "default_env", ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
+ ensure
+ ENV["RAILS_ENV"] = original_rails_env
+ ENV["RACK_ENV"] = original_rack_env
+ end
+
def test_establish_connection_uses_spec_name
+ old_config = ActiveRecord::Base.configurations
config = { "readonly" => { "adapter" => "sqlite3" } }
- resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(config)
+ ActiveRecord::Base.configurations = config
+ resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(ActiveRecord::Base.configurations)
spec = resolver.spec(:readonly)
@handler.establish_connection(spec.to_hash)
assert_not_nil @handler.retrieve_connection_pool("readonly")
ensure
+ ActiveRecord::Base.configurations = old_config
@handler.remove_connection("readonly")
end
@@ -53,6 +74,56 @@ module ActiveRecord
ENV["RAILS_ENV"] = previous_env
end
+ unless in_memory_db?
+ def test_establish_connection_using_3_level_config_defaults_to_default_env_primary_db
+ previous_env, ENV["RAILS_ENV"] = ENV["RAILS_ENV"], "default_env"
+
+ config = {
+ "default_env" => {
+ "primary" => { "adapter" => "sqlite3", "database" => "db/primary.sqlite3" },
+ "readonly" => { "adapter" => "sqlite3", "database" => "db/readonly.sqlite3" }
+ },
+ "another_env" => {
+ "primary" => { "adapter" => "sqlite3", "database" => "db/another-primary.sqlite3" },
+ "readonly" => { "adapter" => "sqlite3", "database" => "db/another-readonly.sqlite3" }
+ }
+ }
+ @prev_configs, ActiveRecord::Base.configurations = ActiveRecord::Base.configurations, config
+
+ ActiveRecord::Base.establish_connection
+
+ assert_match "db/primary.sqlite3", ActiveRecord::Base.connection.pool.spec.config[:database]
+ ensure
+ ActiveRecord::Base.configurations = @prev_configs
+ ENV["RAILS_ENV"] = previous_env
+ ActiveRecord::Base.establish_connection(:arunit)
+ FileUtils.rm_rf "db"
+ end
+
+ def test_establish_connection_using_2_level_config_defaults_to_default_env_primary_db
+ previous_env, ENV["RAILS_ENV"] = ENV["RAILS_ENV"], "default_env"
+
+ config = {
+ "default_env" => {
+ "adapter" => "sqlite3", "database" => "db/primary.sqlite3"
+ },
+ "another_env" => {
+ "adapter" => "sqlite3", "database" => "db/bad-primary.sqlite3"
+ }
+ }
+ @prev_configs, ActiveRecord::Base.configurations = ActiveRecord::Base.configurations, config
+
+ ActiveRecord::Base.establish_connection
+
+ assert_match "db/primary.sqlite3", ActiveRecord::Base.connection.pool.spec.config[:database]
+ ensure
+ ActiveRecord::Base.configurations = @prev_configs
+ ENV["RAILS_ENV"] = previous_env
+ ActiveRecord::Base.establish_connection(:arunit)
+ FileUtils.rm_rf "db"
+ end
+ end
+
def test_establish_connection_using_two_level_configurations
config = { "development" => { "adapter" => "sqlite3", "database" => "db/primary.sqlite3" } }
@prev_configs, ActiveRecord::Base.configurations = ActiveRecord::Base.configurations, config
@@ -85,11 +156,11 @@ module ActiveRecord
end
def test_active_connections?
- assert !@handler.active_connections?
+ assert_not_predicate @handler, :active_connections?
assert @handler.retrieve_connection(@spec_name)
- assert @handler.active_connections?
+ assert_predicate @handler, :active_connections?
@handler.clear_active_connections!
- assert !@handler.active_connections?
+ assert_not_predicate @handler, :active_connections?
end
def test_retrieve_connection_pool
@@ -126,6 +197,75 @@ module ActiveRecord
rd.close
end
+ def test_forked_child_doesnt_mangle_parent_connection
+ object_id = ActiveRecord::Base.connection.object_id
+ assert_predicate ActiveRecord::Base.connection, :active?
+
+ rd, wr = IO.pipe
+ rd.binmode
+ wr.binmode
+
+ pid = fork {
+ rd.close
+ if ActiveRecord::Base.connection.active?
+ wr.write Marshal.dump ActiveRecord::Base.connection.object_id
+ end
+ wr.close
+
+ exit # allow finalizers to run
+ }
+
+ wr.close
+
+ Process.waitpid pid
+ assert_not_equal object_id, Marshal.load(rd.read)
+ rd.close
+
+ assert_equal 3, ActiveRecord::Base.connection.select_value("SELECT COUNT(*) FROM people")
+ end
+
+ unless in_memory_db?
+ def test_forked_child_recovers_from_disconnected_parent
+ object_id = ActiveRecord::Base.connection.object_id
+ assert_predicate ActiveRecord::Base.connection, :active?
+
+ rd, wr = IO.pipe
+ rd.binmode
+ wr.binmode
+
+ outer_pid = fork {
+ ActiveRecord::Base.connection.disconnect!
+
+ pid = fork {
+ rd.close
+ if ActiveRecord::Base.connection.active?
+ pair = [ActiveRecord::Base.connection.object_id,
+ ActiveRecord::Base.connection.select_value("SELECT COUNT(*) FROM people")]
+ wr.write Marshal.dump pair
+ end
+ wr.close
+
+ exit # allow finalizers to run
+ }
+
+ Process.waitpid pid
+ }
+
+ wr.close
+
+ Process.waitpid outer_pid
+ child_id, child_count = Marshal.load(rd.read)
+
+ assert_not_equal object_id, child_id
+ rd.close
+
+ assert_equal 3, child_count
+
+ # Outer connection is unaffected
+ assert_equal 6, ActiveRecord::Base.connection.select_value("SELECT 2 * COUNT(*) FROM people")
+ end
+ end
+
def test_retrieve_connection_pool_copies_schema_cache_from_ancestor_pool
@pool.schema_cache = @pool.connection.schema_cache
@pool.schema_cache.add("posts")
@@ -187,15 +327,15 @@ module ActiveRecord
def test_a_class_using_custom_pool_and_switching_back_to_primary
klass2 = Class.new(Base) { def self.name; "klass2"; end }
- assert_equal klass2.connection.object_id, ActiveRecord::Base.connection.object_id
+ assert_same klass2.connection, ActiveRecord::Base.connection
pool = klass2.establish_connection(ActiveRecord::Base.connection_pool.spec.config)
- assert_equal klass2.connection.object_id, pool.connection.object_id
- refute_equal klass2.connection.object_id, ActiveRecord::Base.connection.object_id
+ assert_same klass2.connection, pool.connection
+ assert_not_same klass2.connection, ActiveRecord::Base.connection
klass2.remove_connection
- assert_equal klass2.connection.object_id, ActiveRecord::Base.connection.object_id
+ assert_same klass2.connection, ActiveRecord::Base.connection
end
def test_connection_specification_name_should_fallback_to_parent
@@ -210,8 +350,8 @@ module ActiveRecord
def test_remove_connection_should_not_remove_parent
klass2 = Class.new(Base) { def self.name; "klass2"; end }
klass2.remove_connection
- refute_nil ActiveRecord::Base.connection.object_id
- assert_equal klass2.connection.object_id, ActiveRecord::Base.connection.object_id
+ assert_not_nil ActiveRecord::Base.connection
+ assert_same klass2.connection, ActiveRecord::Base.connection
end
end
end
diff --git a/activerecord/test/cases/connection_adapters/connection_specification_test.rb b/activerecord/test/cases/connection_adapters/connection_specification_test.rb
index 10a3521c79..f81b73c344 100644
--- a/activerecord/test/cases/connection_adapters/connection_specification_test.rb
+++ b/activerecord/test/cases/connection_adapters/connection_specification_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
module ActiveRecord
diff --git a/activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb b/activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb
index 8faa67255d..06c1c51724 100644
--- a/activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb
+++ b/activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
module ActiveRecord
@@ -16,11 +18,14 @@ module ActiveRecord
end
def resolve_config(config)
- ActiveRecord::ConnectionHandling::MergeAndResolveDefaultUrlConfig.new(config).resolve
+ configs = ActiveRecord::DatabaseConfigurations.new(config)
+ configs.to_h
end
def resolve_spec(spec, config)
- ConnectionSpecification::Resolver.new(resolve_config(config)).resolve(spec)
+ configs = ActiveRecord::DatabaseConfigurations.new(config)
+ resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(configs)
+ resolver.resolve(spec, spec)
end
def test_resolver_with_database_uri_and_current_env_symbol_key
diff --git a/activerecord/test/cases/connection_adapters/mysql_type_lookup_test.rb b/activerecord/test/cases/connection_adapters/mysql_type_lookup_test.rb
index 3657b8340d..02e76ce146 100644
--- a/activerecord/test/cases/connection_adapters/mysql_type_lookup_test.rb
+++ b/activerecord/test/cases/connection_adapters/mysql_type_lookup_test.rb
@@ -1,13 +1,22 @@
+# frozen_string_literal: true
+
require "cases/helper"
+require "support/connection_helper"
if current_adapter?(:Mysql2Adapter)
module ActiveRecord
module ConnectionAdapters
class MysqlTypeLookupTest < ActiveRecord::TestCase
+ include ConnectionHelper
+
setup do
@connection = ActiveRecord::Base.connection
end
+ def teardown
+ reset_connection
+ end
+
def test_boolean_types
emulate_booleans(true) do
assert_lookup_type :boolean, "tinyint(1)"
@@ -47,7 +56,7 @@ if current_adapter?(:Mysql2Adapter)
private
def assert_lookup_type(type, lookup)
- cast_type = @connection.type_map.lookup(lookup)
+ cast_type = @connection.send(:type_map).lookup(lookup)
assert_equal type, cast_type.type
end
diff --git a/activerecord/test/cases/connection_adapters/schema_cache_test.rb b/activerecord/test/cases/connection_adapters/schema_cache_test.rb
index 106323ccc9..67496381d1 100644
--- a/activerecord/test/cases/connection_adapters/schema_cache_test.rb
+++ b/activerecord/test/cases/connection_adapters/schema_cache_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
module ActiveRecord
@@ -20,8 +22,8 @@ module ActiveRecord
new_cache = YAML.load(YAML.dump(@cache))
assert_no_queries do
- assert_equal 11, new_cache.columns("posts").size
- assert_equal 11, new_cache.columns_hash("posts").size
+ assert_equal 12, new_cache.columns("posts").size
+ assert_equal 12, new_cache.columns_hash("posts").size
assert new_cache.data_sources("posts")
assert_equal "id", new_cache.primary_keys("posts")
end
@@ -73,8 +75,8 @@ module ActiveRecord
@cache = Marshal.load(Marshal.dump(@cache))
assert_no_queries do
- assert_equal 11, @cache.columns("posts").size
- assert_equal 11, @cache.columns_hash("posts").size
+ assert_equal 12, @cache.columns("posts").size
+ assert_equal 12, @cache.columns_hash("posts").size
assert @cache.data_sources("posts")
assert_equal "id", @cache.primary_keys("posts")
end
diff --git a/activerecord/test/cases/connection_adapters/type_lookup_test.rb b/activerecord/test/cases/connection_adapters/type_lookup_test.rb
index a348c2d783..1c79d776f0 100644
--- a/activerecord/test/cases/connection_adapters/type_lookup_test.rb
+++ b/activerecord/test/cases/connection_adapters/type_lookup_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
unless current_adapter?(:PostgreSQLAdapter) # PostgreSQL does not use type strings for lookup
@@ -80,11 +82,11 @@ unless current_adapter?(:PostgreSQLAdapter) # PostgreSQL does not use type strin
end
def test_bigint_limit
- cast_type = @connection.type_map.lookup("bigint")
+ limit = @connection.send(:type_map).lookup("bigint").send(:_limit)
if current_adapter?(:OracleAdapter)
- assert_equal 19, cast_type.limit
+ assert_equal 19, limit
else
- assert_equal 8, cast_type.limit
+ assert_equal 8, limit
end
end
@@ -98,7 +100,7 @@ unless current_adapter?(:PostgreSQLAdapter) # PostgreSQL does not use type strin
{ decimal: %w{decimal(2) decimal(2,0) numeric(2) numeric(2,0) number(2) number(2,0)} }
end.each do |expected_type, types|
types.each do |type|
- cast_type = @connection.type_map.lookup(type)
+ cast_type = @connection.send(:type_map).lookup(type)
assert_equal expected_type, cast_type.type
assert_equal 2, cast_type.cast(2.1)
@@ -109,7 +111,7 @@ unless current_adapter?(:PostgreSQLAdapter) # PostgreSQL does not use type strin
private
def assert_lookup_type(type, lookup)
- cast_type = @connection.type_map.lookup(lookup)
+ cast_type = @connection.send(:type_map).lookup(lookup)
assert_equal type, cast_type.type
end
end
diff --git a/activerecord/test/cases/connection_management_test.rb b/activerecord/test/cases/connection_management_test.rb
index d1e946d401..0941ee3309 100644
--- a/activerecord/test/cases/connection_management_test.rb
+++ b/activerecord/test/cases/connection_management_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "rack"
@@ -25,7 +27,7 @@ module ActiveRecord
# make sure we have an active connection
assert ActiveRecord::Base.connection
- assert ActiveRecord::Base.connection_handler.active_connections?
+ assert_predicate ActiveRecord::Base.connection_handler, :active_connections?
end
def test_app_delegation
@@ -45,14 +47,14 @@ module ActiveRecord
def test_connections_are_cleared_after_body_close
_, _, body = @management.call(@env)
body.close
- assert !ActiveRecord::Base.connection_handler.active_connections?
+ assert_not_predicate ActiveRecord::Base.connection_handler, :active_connections?
end
def test_active_connections_are_not_cleared_on_body_close_during_transaction
ActiveRecord::Base.transaction do
_, _, body = @management.call(@env)
body.close
- assert ActiveRecord::Base.connection_handler.active_connections?
+ assert_predicate ActiveRecord::Base.connection_handler, :active_connections?
end
end
@@ -60,7 +62,7 @@ module ActiveRecord
app = Class.new(App) { def call(env); raise NotImplementedError; end }.new
explosive = middleware(app)
assert_raises(NotImplementedError) { explosive.call(@env) }
- assert !ActiveRecord::Base.connection_handler.active_connections?
+ assert_not_predicate ActiveRecord::Base.connection_handler, :active_connections?
end
def test_connections_not_closed_if_exception_inside_transaction
@@ -68,14 +70,14 @@ module ActiveRecord
app = Class.new(App) { def call(env); raise RuntimeError; end }.new
explosive = middleware(app)
assert_raises(RuntimeError) { explosive.call(@env) }
- assert ActiveRecord::Base.connection_handler.active_connections?
+ assert_predicate ActiveRecord::Base.connection_handler, :active_connections?
end
end
test "doesn't clear active connections when running in a test case" do
executor.wrap do
@management.call(@env)
- assert ActiveRecord::Base.connection_handler.active_connections?
+ assert_predicate ActiveRecord::Base.connection_handler, :active_connections?
end
end
@@ -83,7 +85,7 @@ module ActiveRecord
body = Class.new(String) { def to_path; "/path"; end }.new
app = lambda { |_| [200, {}, body] }
response_body = middleware(app).call(@env)[2]
- assert response_body.respond_to?(:to_path)
+ assert_respond_to response_body, :to_path
assert_equal "/path", response_body.to_path
end
diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb
index 7e88c9cf7a..06869eeab0 100644
--- a/activerecord/test/cases/connection_pool_test.rb
+++ b/activerecord/test/cases/connection_pool_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "concurrent/atomic/count_down_latch"
@@ -33,12 +35,12 @@ module ActiveRecord
def test_checkout_after_close
connection = pool.connection
- assert connection.in_use?
+ assert_predicate connection, :in_use?
connection.close
- assert !connection.in_use?
+ assert_not_predicate connection, :in_use?
- assert pool.connection.in_use?
+ assert_predicate pool.connection, :in_use?
end
def test_released_connection_moves_between_threads
@@ -78,18 +80,20 @@ module ActiveRecord
end
def test_active_connection_in_use
- assert !pool.active_connection?
+ assert_not_predicate pool, :active_connection?
main_thread = pool.connection
- assert pool.active_connection?
+ assert_predicate pool, :active_connection?
main_thread.close
- assert !pool.active_connection?
+ assert_not_predicate pool, :active_connection?
end
def test_full_pool_exception
- @pool.size.times { @pool.checkout }
+ @pool.checkout_timeout = 0.001 # no need to delay test suite by waiting the whole full default timeout
+ @pool.size.times { assert @pool.checkout }
+
assert_raises(ConnectionTimeoutError) do
@pool.checkout
end
@@ -107,6 +111,44 @@ module ActiveRecord
assert_equal connection, t.join.value
end
+ def test_full_pool_blocking_shares_load_interlock
+ @pool.instance_variable_set(:@size, 1)
+
+ load_interlock_latch = Concurrent::CountDownLatch.new
+ connection_latch = Concurrent::CountDownLatch.new
+
+ able_to_get_connection = false
+ able_to_load = false
+
+ thread_with_load_interlock = Thread.new do
+ ActiveSupport::Dependencies.interlock.running do
+ load_interlock_latch.count_down
+ connection_latch.wait
+
+ @pool.with_connection do
+ able_to_get_connection = true
+ end
+ end
+ end
+
+ thread_with_last_connection = Thread.new do
+ @pool.with_connection do
+ connection_latch.count_down
+ load_interlock_latch.wait
+
+ ActiveSupport::Dependencies.interlock.loading do
+ able_to_load = true
+ end
+ end
+ end
+
+ thread_with_load_interlock.join
+ thread_with_last_connection.join
+
+ assert able_to_get_connection
+ assert able_to_load
+ end
+
def test_removing_releases_latch
cs = @pool.size.times.map { @pool.checkout }
t = Thread.new { @pool.checkout }
@@ -154,13 +196,103 @@ module ActiveRecord
@pool.connections.each { |conn| conn.close if conn.in_use? }
end
+ def test_idle_timeout_configuration
+ @pool.disconnect!
+ spec = ActiveRecord::Base.connection_pool.spec
+ spec.config.merge!(idle_timeout: "0.02")
+ @pool = ConnectionPool.new(spec)
+ idle_conn = @pool.checkout
+ @pool.checkin(idle_conn)
+
+ idle_conn.instance_variable_set(
+ :@idle_since,
+ Concurrent.monotonic_time - 0.01
+ )
+
+ @pool.flush
+ assert_equal 1, @pool.connections.length
+
+ idle_conn.instance_variable_set(
+ :@idle_since,
+ Concurrent.monotonic_time - 0.02
+ )
+
+ @pool.flush
+ assert_equal 0, @pool.connections.length
+ end
+
+ def test_disable_flush
+ @pool.disconnect!
+ spec = ActiveRecord::Base.connection_pool.spec
+ spec.config.merge!(idle_timeout: -5)
+ @pool = ConnectionPool.new(spec)
+ idle_conn = @pool.checkout
+ @pool.checkin(idle_conn)
+
+ idle_conn.instance_variable_set(
+ :@idle_since,
+ Concurrent.monotonic_time - 1
+ )
+
+ @pool.flush
+ assert_equal 1, @pool.connections.length
+ end
+
+ def test_flush
+ idle_conn = @pool.checkout
+ recent_conn = @pool.checkout
+ active_conn = @pool.checkout
+
+ @pool.checkin idle_conn
+ @pool.checkin recent_conn
+
+ assert_equal 3, @pool.connections.length
+
+ idle_conn.instance_variable_set(
+ :@idle_since,
+ Concurrent.monotonic_time - 1000
+ )
+
+ @pool.flush(30)
+
+ assert_equal 2, @pool.connections.length
+
+ assert_equal [recent_conn, active_conn].sort_by(&:__id__), @pool.connections.sort_by(&:__id__)
+ ensure
+ @pool.checkin active_conn
+ end
+
+ def test_flush_bang
+ idle_conn = @pool.checkout
+ recent_conn = @pool.checkout
+ active_conn = @pool.checkout
+ _dead_conn = Thread.new { @pool.checkout }.join
+
+ @pool.checkin idle_conn
+ @pool.checkin recent_conn
+
+ assert_equal 4, @pool.connections.length
+
+ def idle_conn.seconds_idle
+ 1000
+ end
+
+ @pool.flush!
+
+ assert_equal 1, @pool.connections.length
+
+ assert_equal [active_conn].sort_by(&:__id__), @pool.connections.sort_by(&:__id__)
+ ensure
+ @pool.checkin active_conn
+ end
+
def test_remove_connection
conn = @pool.checkout
- assert conn.in_use?
+ assert_predicate conn, :in_use?
length = @pool.connections.length
@pool.remove conn
- assert conn.in_use?
+ assert_predicate conn, :in_use?
assert_equal(length - 1, @pool.connections.length)
ensure
conn.close
@@ -175,11 +307,11 @@ module ActiveRecord
end
def test_active_connection?
- assert !@pool.active_connection?
+ assert_not_predicate @pool, :active_connection?
assert @pool.connection
- assert @pool.active_connection?
+ assert_predicate @pool, :active_connection?
@pool.release_connection
- assert !@pool.active_connection?
+ assert_not_predicate @pool, :active_connection?
end
def test_checkout_behaviour
@@ -203,6 +335,14 @@ module ActiveRecord
end.join
end
+ def test_checkout_order_is_lifo
+ conn1 = @pool.checkout
+ conn2 = @pool.checkout
+ @pool.checkin conn1
+ @pool.checkin conn2
+ assert_equal [conn2, conn1], 2.times.map { @pool.checkout }
+ end
+
# The connection pool is "fair" if threads waiting for
# connections receive them in the order in which they began
# waiting. This ensures that we don't timeout one HTTP request
@@ -412,6 +552,7 @@ module ActiveRecord
end
def test_non_bang_disconnect_and_clear_reloadable_connections_throw_exception_if_threads_dont_return_their_conns
+ Thread.report_on_exception, original_report_on_exception = false, Thread.report_on_exception
@pool.checkout_timeout = 0.001 # no need to delay test suite by waiting the whole full default timeout
[:disconnect, :clear_reloadable_connections].each do |group_action_method|
@pool.with_connection do |connection|
@@ -420,6 +561,8 @@ module ActiveRecord
end
end
end
+ ensure
+ Thread.report_on_exception = original_report_on_exception
end
def test_disconnect_and_clear_reloadable_connections_attempt_to_wait_for_threads_to_return_their_conns
@@ -436,7 +579,7 @@ module ActiveRecord
assert_nil timed_join_result
# assert that since this is within default timeout our connection hasn't been forcefully taken away from us
- assert @pool.active_connection?
+ assert_predicate @pool, :active_connection?
end
ensure
thread.join if thread && !timed_join_result # clean up the other thread
@@ -450,7 +593,7 @@ module ActiveRecord
@pool.with_connection do |connection|
Thread.new { @pool.send(group_action_method) }.join
# assert connection has been forcefully taken away from us
- assert_not @pool.active_connection?
+ assert_not_predicate @pool, :active_connection?
# make a new connection for with_connection to clean up
@pool.connection
@@ -499,21 +642,8 @@ module ActiveRecord
if failed
second_thread_done.set
- puts
- puts ">>> test_disconnect_and_clear_reloadable_connections_are_able_to_preempt_other_waiting_threads / #{group_action_method}"
- p [first_thread, second_thread]
- p pool.stat
- p pool.connections.map(&:owner)
-
first_thread.join(2)
second_thread.join(2)
-
- puts "---"
- p [first_thread, second_thread]
- p pool.stat
- p pool.connections.map(&:owner)
- puts "<<<"
- puts
end
first_thread.join(10) || raise("first_thread got stuck")
diff --git a/activerecord/test/cases/connection_specification/resolver_test.rb b/activerecord/test/cases/connection_specification/resolver_test.rb
index 13b5bae13c..72be14f507 100644
--- a/activerecord/test/cases/connection_specification/resolver_test.rb
+++ b/activerecord/test/cases/connection_specification/resolver_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
module ActiveRecord
@@ -5,11 +7,15 @@ module ActiveRecord
class ConnectionSpecification
class ResolverTest < ActiveRecord::TestCase
def resolve(spec, config = {})
- Resolver.new(config).resolve(spec)
+ configs = ActiveRecord::DatabaseConfigurations.new(config)
+ resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(configs)
+ resolver.resolve(spec, spec)
end
def spec(spec, config = {})
- Resolver.new(config).spec(spec)
+ configs = ActiveRecord::DatabaseConfigurations.new(config)
+ resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(configs)
+ resolver.spec(spec)
end
def test_url_invalid_adapter
@@ -17,7 +23,7 @@ module ActiveRecord
spec "ridiculous://foo?encoding=utf8"
end
- assert_match "Could not load 'active_record/connection_adapters/ridiculous_adapter'", error.message
+ assert_match "Could not load the 'ridiculous' Active Record adapter. Ensure that the adapter is spelled correctly in config/database.yml and that you've added the necessary adapter gem to your Gemfile.", error.message
end
# The abstract adapter is used simply to bypass the bit of code that
diff --git a/activerecord/test/cases/core_test.rb b/activerecord/test/cases/core_test.rb
index 3735572898..6e7ae2efb4 100644
--- a/activerecord/test/cases/core_test.rb
+++ b/activerecord/test/cases/core_test.rb
@@ -1,8 +1,9 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/person"
require "models/topic"
require "pp"
-require "active_support/core_ext/string/strip"
class NonExistentTable < ActiveRecord::Base; end
@@ -35,28 +36,28 @@ class CoreTest < ActiveRecord::TestCase
def test_pretty_print_new
topic = Topic.new
- actual = ""
+ actual = "".dup
PP.pp(topic, StringIO.new(actual))
- expected = <<-PRETTY.strip_heredoc
- #<Topic:0xXXXXXX
- id: nil,
- title: nil,
- author_name: nil,
- author_email_address: "test@test.com",
- written_on: nil,
- bonus_time: nil,
- last_read: nil,
- content: nil,
- important: nil,
- approved: true,
- replies_count: 0,
- unique_replies_count: 0,
- parent_id: nil,
- parent_title: nil,
- type: nil,
- group: nil,
- created_at: nil,
- updated_at: nil>
+ expected = <<~PRETTY
+ #<Topic:0xXXXXXX
+ id: nil,
+ title: nil,
+ author_name: nil,
+ author_email_address: "test@test.com",
+ written_on: nil,
+ bonus_time: nil,
+ last_read: nil,
+ content: nil,
+ important: nil,
+ approved: true,
+ replies_count: 0,
+ unique_replies_count: 0,
+ parent_id: nil,
+ parent_title: nil,
+ type: nil,
+ group: nil,
+ created_at: nil,
+ updated_at: nil>
PRETTY
assert actual.start_with?(expected.split("XXXXXX").first)
assert actual.end_with?(expected.split("XXXXXX").last)
@@ -64,35 +65,35 @@ class CoreTest < ActiveRecord::TestCase
def test_pretty_print_persisted
topic = topics(:first)
- actual = ""
+ actual = "".dup
PP.pp(topic, StringIO.new(actual))
- expected = <<-PRETTY.strip_heredoc
- #<Topic:0x\\w+
- id: 1,
- title: "The First Topic",
- author_name: "David",
- author_email_address: "david@loudthinking.com",
- written_on: 2003-07-16 14:28:11 UTC,
- bonus_time: 2000-01-01 14:28:00 UTC,
- last_read: Thu, 15 Apr 2004,
- content: "Have a nice day",
- important: nil,
- approved: false,
- replies_count: 1,
- unique_replies_count: 0,
- parent_id: nil,
- parent_title: nil,
- type: nil,
- group: nil,
- created_at: [^,]+,
- updated_at: [^,>]+>
+ expected = <<~PRETTY
+ #<Topic:0x\\w+
+ id: 1,
+ title: "The First Topic",
+ author_name: "David",
+ author_email_address: "david@loudthinking.com",
+ written_on: 2003-07-16 14:28:11 UTC,
+ bonus_time: 2000-01-01 14:28:00 UTC,
+ last_read: Thu, 15 Apr 2004,
+ content: "Have a nice day",
+ important: nil,
+ approved: false,
+ replies_count: 1,
+ unique_replies_count: 0,
+ parent_id: nil,
+ parent_title: nil,
+ type: nil,
+ group: nil,
+ created_at: [^,]+,
+ updated_at: [^,>]+>
PRETTY
assert_match(/\A#{expected}\z/, actual)
end
def test_pretty_print_uninitialized
topic = Topic.allocate
- actual = ""
+ actual = "".dup
PP.pp(topic, StringIO.new(actual))
expected = "#<Topic:XXXXXX not initialized>\n"
assert actual.start_with?(expected.split("XXXXXX").first)
@@ -105,7 +106,7 @@ class CoreTest < ActiveRecord::TestCase
"inspecting topic"
end
end
- actual = ""
+ actual = "".dup
PP.pp(subtopic.new, StringIO.new(actual))
assert_equal "inspecting topic\n", actual
end
diff --git a/activerecord/test/cases/counter_cache_test.rb b/activerecord/test/cases/counter_cache_test.rb
index 46d7526cc0..99d286dc52 100644
--- a/activerecord/test/cases/counter_cache_test.rb
+++ b/activerecord/test/cases/counter_cache_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/topic"
require "models/car"
@@ -278,38 +280,38 @@ class CounterCacheTest < ActiveRecord::TestCase
end
test "update counters with touch: :written_on" do
- assert_touching @topic, :written_on do
+ assert_touching @topic, :updated_at, :written_on do
Topic.update_counters(@topic.id, replies_count: -1, touch: :written_on)
end
end
test "update multiple counters with touch: :written_on" do
- assert_touching @topic, :written_on do
+ assert_touching @topic, :updated_at, :written_on do
Topic.update_counters(@topic.id, replies_count: 2, unique_replies_count: 2, touch: :written_on)
end
end
test "reset counters with touch: :written_on" do
- assert_touching @topic, :written_on do
+ assert_touching @topic, :updated_at, :written_on do
Topic.reset_counters(@topic.id, :replies, touch: :written_on)
end
end
test "reset multiple counters with touch: :written_on" do
- assert_touching @topic, :written_on do
+ assert_touching @topic, :updated_at, :written_on do
Topic.update_counters(@topic.id, replies_count: 1, unique_replies_count: 1)
Topic.reset_counters(@topic.id, :replies, :unique_replies, touch: :written_on)
end
end
test "increment counters with touch: :written_on" do
- assert_touching @topic, :written_on do
+ assert_touching @topic, :updated_at, :written_on do
Topic.increment_counter(:replies_count, @topic.id, touch: :written_on)
end
end
test "decrement counters with touch: :written_on" do
- assert_touching @topic, :written_on do
+ assert_touching @topic, :updated_at, :written_on do
Topic.decrement_counter(:replies_count, @topic.id, touch: :written_on)
end
end
diff --git a/activerecord/test/cases/custom_locking_test.rb b/activerecord/test/cases/custom_locking_test.rb
index 15c8b684e4..f52b26e9ec 100644
--- a/activerecord/test/cases/custom_locking_test.rb
+++ b/activerecord/test/cases/custom_locking_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/person"
diff --git a/activerecord/test/cases/database_statements_test.rb b/activerecord/test/cases/database_statements_test.rb
index 66035865be..1c934602ec 100644
--- a/activerecord/test/cases/database_statements_test.rb
+++ b/activerecord/test/cases/database_statements_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
class DatabaseStatementsTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/date_test.rb b/activerecord/test/cases/date_test.rb
index 2edc0415cd..9f412cdb63 100644
--- a/activerecord/test/cases/date_test.rb
+++ b/activerecord/test/cases/date_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/topic"
diff --git a/activerecord/test/cases/date_time_precision_test.rb b/activerecord/test/cases/date_time_precision_test.rb
index e4a2f9ee17..e64a8372d0 100644
--- a/activerecord/test/cases/date_time_precision_test.rb
+++ b/activerecord/test/cases/date_time_precision_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "support/schema_dumping_helper"
@@ -25,6 +27,24 @@ if subsecond_precision_supported?
assert_equal 5, Foo.columns_hash["updated_at"].precision
end
+ def test_datetime_precision_is_truncated_on_assignment
+ @connection.create_table(:foos, force: true)
+ @connection.add_column :foos, :created_at, :datetime, precision: 0
+ @connection.add_column :foos, :updated_at, :datetime, precision: 6
+
+ time = ::Time.now.change(nsec: 123456789)
+ foo = Foo.new(created_at: time, updated_at: time)
+
+ assert_equal 0, foo.created_at.nsec
+ assert_equal 123456000, foo.updated_at.nsec
+
+ foo.save!
+ foo.reload
+
+ assert_equal 0, foo.created_at.nsec
+ assert_equal 123456000, foo.updated_at.nsec
+ end
+
def test_timestamps_helper_with_custom_precision
@connection.create_table(:foos, force: true) do |t|
t.timestamps precision: 4
diff --git a/activerecord/test/cases/date_time_test.rb b/activerecord/test/cases/date_time_test.rb
index 3bc08f80ec..b5f35aff0e 100644
--- a/activerecord/test/cases/date_time_test.rb
+++ b/activerecord/test/cases/date_time_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/topic"
require "models/task"
@@ -52,10 +54,23 @@ class DateTimeTest < ActiveRecord::TestCase
end
def test_assign_in_local_timezone
- now = DateTime.now
+ now = DateTime.civil(2017, 3, 1, 12, 0, 0)
with_timezone_config default: :local do
task = Task.new starting: now
assert_equal now, task.starting
end
end
+
+ def test_date_time_with_string_value_with_subsecond_precision
+ skip unless subsecond_precision_supported?
+ string_value = "2017-07-04 14:19:00.5"
+ topic = Topic.create(written_on: string_value)
+ assert_equal topic, Topic.find_by(written_on: string_value)
+ end
+
+ def test_date_time_with_string_value_with_non_iso_format
+ string_value = "04/07/2017 2:19pm"
+ topic = Topic.create(written_on: string_value)
+ assert_equal topic, Topic.find_by(written_on: string_value)
+ end
end
diff --git a/activerecord/test/cases/defaults_test.rb b/activerecord/test/cases/defaults_test.rb
index a6297673c9..0f957d41cf 100644
--- a/activerecord/test/cases/defaults_test.rb
+++ b/activerecord/test/cases/defaults_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "support/schema_dumping_helper"
require "models/default"
@@ -7,7 +9,7 @@ class DefaultTest < ActiveRecord::TestCase
def test_nil_defaults_for_not_null_columns
%w(id name course_id).each do |name|
column = Entrant.columns_hash[name]
- assert !column.null, "#{name} column should be NOT NULL"
+ assert_not column.null, "#{name} column should be NOT NULL"
assert_not column.default, "#{name} column should be DEFAULT 'nil'"
end
end
@@ -51,7 +53,7 @@ class DefaultNumbersTest < ActiveRecord::TestCase
def test_default_decimal_number
record = DefaultNumber.new
- assert_equal BigDecimal.new("2.78"), record.decimal_number
+ assert_equal BigDecimal("2.78"), record.decimal_number
assert_equal "2.78", record.decimal_number_before_type_cast
end
end
@@ -87,9 +89,14 @@ if current_adapter?(:PostgreSQLAdapter)
test "schema dump includes default expression" do
output = dump_table_schema("defaults")
- assert_match %r/t\.date\s+"modified_date",\s+default: -> { "\('now'::text\)::date" }/, output
+ if ActiveRecord::Base.connection.postgresql_version >= 100000
+ assert_match %r/t\.date\s+"modified_date",\s+default: -> { "CURRENT_DATE" }/, output
+ assert_match %r/t\.datetime\s+"modified_time",\s+default: -> { "CURRENT_TIMESTAMP" }/, output
+ else
+ assert_match %r/t\.date\s+"modified_date",\s+default: -> { "\('now'::text\)::date" }/, output
+ assert_match %r/t\.datetime\s+"modified_time",\s+default: -> { "now\(\)" }/, output
+ end
assert_match %r/t\.date\s+"modified_date_function",\s+default: -> { "now\(\)" }/, output
- assert_match %r/t\.datetime\s+"modified_time",\s+default: -> { "now\(\)" }/, output
assert_match %r/t\.datetime\s+"modified_time_function",\s+default: -> { "now\(\)" }/, output
end
end
@@ -99,21 +106,31 @@ if current_adapter?(:Mysql2Adapter)
class MysqlDefaultExpressionTest < ActiveRecord::TestCase
include SchemaDumpingHelper
- if ActiveRecord::Base.connection.version >= "5.6.0"
+ if subsecond_precision_supported?
test "schema dump datetime includes default expression" do
output = dump_table_schema("datetime_defaults")
- assert_match %r/t\.datetime\s+"modified_datetime",\s+default: -> { "CURRENT_TIMESTAMP" }/, output
+ assert_match %r/t\.datetime\s+"modified_datetime",\s+default: -> { "CURRENT_TIMESTAMP(?:\(\))?" }/i, output
end
- end
- test "schema dump timestamp includes default expression" do
- output = dump_table_schema("timestamp_defaults")
- assert_match %r/t\.timestamp\s+"modified_timestamp",\s+default: -> { "CURRENT_TIMESTAMP" }/, output
- end
+ test "schema dump datetime includes precise default expression" do
+ output = dump_table_schema("datetime_defaults")
+ assert_match %r/t\.datetime\s+"precise_datetime",.+default: -> { "CURRENT_TIMESTAMP\(6\)" }/i, output
+ end
- test "schema dump timestamp without default expression" do
- output = dump_table_schema("timestamp_defaults")
- assert_match %r/t\.timestamp\s+"nullable_timestamp"$/, output
+ test "schema dump timestamp includes default expression" do
+ output = dump_table_schema("timestamp_defaults")
+ assert_match %r/t\.timestamp\s+"modified_timestamp",\s+default: -> { "CURRENT_TIMESTAMP(?:\(\))?" }/i, output
+ end
+
+ test "schema dump timestamp includes precise default expression" do
+ output = dump_table_schema("timestamp_defaults")
+ assert_match %r/t\.timestamp\s+"precise_timestamp",.+default: -> { "CURRENT_TIMESTAMP\(6\)" }/i, output
+ end
+
+ test "schema dump timestamp without default expression" do
+ output = dump_table_schema("timestamp_defaults")
+ assert_match %r/t\.timestamp\s+"nullable_timestamp"$/, output
+ end
end
end
diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb
index c13a962e3e..b1ebd20d6b 100644
--- a/activerecord/test/cases/dirty_test.rb
+++ b/activerecord/test/cases/dirty_test.rb
@@ -1,13 +1,12 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/topic" # For booleans
require "models/pirate" # For timestamps
require "models/parrot"
require "models/person" # For optimistic locking
require "models/aircraft"
-
-class NumericData < ActiveRecord::Base
- self.table_name = "numeric_data"
-end
+require "models/numeric_data"
class DirtyTest < ActiveRecord::TestCase
include InTimeZone
@@ -25,18 +24,18 @@ class DirtyTest < ActiveRecord::TestCase
# Change catchphrase.
pirate.catchphrase = "arrr"
- assert pirate.catchphrase_changed?
+ assert_predicate pirate, :catchphrase_changed?
assert_nil pirate.catchphrase_was
assert_equal [nil, "arrr"], pirate.catchphrase_change
# Saved - no changes.
pirate.save!
- assert !pirate.catchphrase_changed?
+ assert_not_predicate pirate, :catchphrase_changed?
assert_nil pirate.catchphrase_change
# Same value - no changes.
pirate.catchphrase = "arrr"
- assert !pirate.catchphrase_changed?
+ assert_not_predicate pirate, :catchphrase_changed?
assert_nil pirate.catchphrase_change
end
@@ -47,23 +46,23 @@ class DirtyTest < ActiveRecord::TestCase
# New record - no changes.
pirate = target.new
- assert !pirate.created_on_changed?
+ assert_not_predicate pirate, :created_on_changed?
assert_nil pirate.created_on_change
# Saved - no changes.
pirate.catchphrase = "arrrr, time zone!!"
pirate.save!
- assert !pirate.created_on_changed?
+ assert_not_predicate pirate, :created_on_changed?
assert_nil pirate.created_on_change
# Change created_on.
old_created_on = pirate.created_on
pirate.created_on = Time.now - 1.day
- assert pirate.created_on_changed?
+ assert_predicate pirate, :created_on_changed?
assert_kind_of ActiveSupport::TimeWithZone, pirate.created_on_was
assert_equal old_created_on, pirate.created_on_was
pirate.created_on = old_created_on
- assert !pirate.created_on_changed?
+ assert_not_predicate pirate, :created_on_changed?
end
end
@@ -74,7 +73,7 @@ class DirtyTest < ActiveRecord::TestCase
pirate = target.create!
pirate.created_on = pirate.created_on
- assert !pirate.created_on_changed?
+ assert_not_predicate pirate, :created_on_changed?
end
end
@@ -87,19 +86,19 @@ class DirtyTest < ActiveRecord::TestCase
# New record - no changes.
pirate = target.new
- assert !pirate.created_on_changed?
+ assert_not_predicate pirate, :created_on_changed?
assert_nil pirate.created_on_change
# Saved - no changes.
pirate.catchphrase = "arrrr, time zone!!"
pirate.save!
- assert !pirate.created_on_changed?
+ assert_not_predicate pirate, :created_on_changed?
assert_nil pirate.created_on_change
# Change created_on.
old_created_on = pirate.created_on
pirate.created_on = Time.now + 1.day
- assert pirate.created_on_changed?
+ assert_predicate pirate, :created_on_changed?
# kind_of does not work because
# ActiveSupport::TimeWithZone.name == 'Time'
assert_instance_of Time, pirate.created_on_was
@@ -114,19 +113,19 @@ class DirtyTest < ActiveRecord::TestCase
# New record - no changes.
pirate = target.new
- assert !pirate.created_on_changed?
+ assert_not_predicate pirate, :created_on_changed?
assert_nil pirate.created_on_change
# Saved - no changes.
pirate.catchphrase = "arrrr, time zone!!"
pirate.save!
- assert !pirate.created_on_changed?
+ assert_not_predicate pirate, :created_on_changed?
assert_nil pirate.created_on_change
# Change created_on.
old_created_on = pirate.created_on
pirate.created_on = Time.now + 1.day
- assert pirate.created_on_changed?
+ assert_predicate pirate, :created_on_changed?
# kind_of does not work because
# ActiveSupport::TimeWithZone.name == 'Time'
assert_instance_of Time, pirate.created_on_was
@@ -138,11 +137,11 @@ class DirtyTest < ActiveRecord::TestCase
# the actual attribute here is name, title is an
# alias setup via alias_attribute
parrot = Parrot.new
- assert !parrot.title_changed?
+ assert_not_predicate parrot, :title_changed?
assert_nil parrot.title_change
parrot.name = "Sam"
- assert parrot.title_changed?
+ assert_predicate parrot, :title_changed?
assert_nil parrot.title_was
assert_equal parrot.name_change, parrot.title_change
end
@@ -154,7 +153,7 @@ class DirtyTest < ActiveRecord::TestCase
pirate.restore_catchphrase!
assert_equal "Yar!", pirate.catchphrase
assert_equal Hash.new, pirate.changes
- assert !pirate.catchphrase_changed?
+ assert_not_predicate pirate, :catchphrase_changed?
end
def test_nullable_number_not_marked_as_changed_if_new_value_is_blank
@@ -162,7 +161,7 @@ class DirtyTest < ActiveRecord::TestCase
["", nil].each do |value|
pirate.parrot_id = value
- assert !pirate.parrot_id_changed?
+ assert_not_predicate pirate, :parrot_id_changed?
assert_nil pirate.parrot_id_change
end
end
@@ -172,7 +171,7 @@ class DirtyTest < ActiveRecord::TestCase
["", nil].each do |value|
numeric_data.bank_balance = value
- assert !numeric_data.bank_balance_changed?
+ assert_not_predicate numeric_data, :bank_balance_changed?
assert_nil numeric_data.bank_balance_change
end
end
@@ -182,7 +181,7 @@ class DirtyTest < ActiveRecord::TestCase
["", nil].each do |value|
numeric_data.temperature = value
- assert !numeric_data.temperature_changed?
+ assert_not_predicate numeric_data, :temperature_changed?
assert_nil numeric_data.temperature_change
end
end
@@ -198,7 +197,7 @@ class DirtyTest < ActiveRecord::TestCase
["", nil].each do |value|
topic.written_on = value
assert_nil topic.written_on
- assert !topic.written_on_changed?
+ assert_not_predicate topic, :written_on_changed?
end
end
end
@@ -209,10 +208,10 @@ class DirtyTest < ActiveRecord::TestCase
pirate.catchphrase = "arrr"
assert pirate.save!
- assert !pirate.changed?
+ assert_not_predicate pirate, :changed?
pirate.parrot_id = "0"
- assert !pirate.changed?
+ assert_not_predicate pirate, :changed?
end
def test_integer_zero_to_integer_zero_not_marked_as_changed
@@ -221,17 +220,17 @@ class DirtyTest < ActiveRecord::TestCase
pirate.catchphrase = "arrr"
assert pirate.save!
- assert !pirate.changed?
+ assert_not_predicate pirate, :changed?
pirate.parrot_id = 0
- assert !pirate.changed?
+ assert_not_predicate pirate, :changed?
end
def test_float_zero_to_string_zero_not_marked_as_changed
data = NumericData.new temperature: 0.0
data.save!
- assert_not data.changed?
+ assert_not_predicate data, :changed?
data.temperature = "0"
assert_empty data.changes
@@ -252,38 +251,38 @@ class DirtyTest < ActiveRecord::TestCase
# check the change from 1 to ''
pirate = Pirate.find_by_catchphrase("Yarrrr, me hearties")
pirate.parrot_id = ""
- assert pirate.parrot_id_changed?
+ assert_predicate pirate, :parrot_id_changed?
assert_equal([1, nil], pirate.parrot_id_change)
pirate.save
# check the change from nil to 0
pirate = Pirate.find_by_catchphrase("Yarrrr, me hearties")
pirate.parrot_id = 0
- assert pirate.parrot_id_changed?
+ assert_predicate pirate, :parrot_id_changed?
assert_equal([nil, 0], pirate.parrot_id_change)
pirate.save
# check the change from 0 to ''
pirate = Pirate.find_by_catchphrase("Yarrrr, me hearties")
pirate.parrot_id = ""
- assert pirate.parrot_id_changed?
+ assert_predicate pirate, :parrot_id_changed?
assert_equal([0, nil], pirate.parrot_id_change)
end
def test_object_should_be_changed_if_any_attribute_is_changed
pirate = Pirate.new
- assert !pirate.changed?
+ assert_not_predicate pirate, :changed?
assert_equal [], pirate.changed
assert_equal Hash.new, pirate.changes
pirate.catchphrase = "arrr"
- assert pirate.changed?
+ assert_predicate pirate, :changed?
assert_nil pirate.catchphrase_was
assert_equal %w(catchphrase), pirate.changed
assert_equal({ "catchphrase" => [nil, "arrr"] }, pirate.changes)
pirate.save
- assert !pirate.changed?
+ assert_not_predicate pirate, :changed?
assert_equal [], pirate.changed
assert_equal Hash.new, pirate.changes
end
@@ -291,42 +290,40 @@ class DirtyTest < ActiveRecord::TestCase
def test_attribute_will_change!
pirate = Pirate.create!(catchphrase: "arr")
- assert !pirate.catchphrase_changed?
+ assert_not_predicate pirate, :catchphrase_changed?
assert pirate.catchphrase_will_change!
- assert pirate.catchphrase_changed?
+ assert_predicate pirate, :catchphrase_changed?
assert_equal ["arr", "arr"], pirate.catchphrase_change
pirate.catchphrase << " matey!"
- assert pirate.catchphrase_changed?
+ assert_predicate pirate, :catchphrase_changed?
assert_equal ["arr", "arr matey!"], pirate.catchphrase_change
end
def test_virtual_attribute_will_change
- assert_deprecated do
- parrot = Parrot.create!(name: "Ruby")
- parrot.send(:attribute_will_change!, :cancel_save_from_callback)
- assert parrot.has_changes_to_save?
- end
+ parrot = Parrot.create!(name: "Ruby")
+ parrot.send(:attribute_will_change!, :cancel_save_from_callback)
+ assert_predicate parrot, :has_changes_to_save?
end
def test_association_assignment_changes_foreign_key
pirate = Pirate.create!(catchphrase: "jarl")
pirate.parrot = Parrot.create!(name: "Lorre")
- assert pirate.changed?
+ assert_predicate pirate, :changed?
assert_equal %w(parrot_id), pirate.changed
end
def test_attribute_should_be_compared_with_type_cast
topic = Topic.new
- assert topic.approved?
- assert !topic.approved_changed?
+ assert_predicate topic, :approved?
+ assert_not_predicate topic, :approved_changed?
# Coming from web form.
params = { topic: { approved: 1 } }
# In the controller.
topic.attributes = params[:topic]
- assert topic.approved?
- assert !topic.approved_changed?
+ assert_predicate topic, :approved?
+ assert_not_predicate topic, :approved_changed?
end
def test_partial_update
@@ -349,13 +346,14 @@ class DirtyTest < ActiveRecord::TestCase
def test_partial_update_with_optimistic_locking
person = Person.new(first_name: "foo")
- old_lock_version = person.lock_version
with_partial_writes Person, false do
assert_queries(2) { 2.times { person.save! } }
Person.where(id: person.id).update_all(first_name: "baz")
end
+ old_lock_version = person.lock_version
+
with_partial_writes Person, true do
assert_queries(0) { 2.times { person.save! } }
assert_equal old_lock_version, person.reload.lock_version
@@ -368,7 +366,7 @@ class DirtyTest < ActiveRecord::TestCase
def test_changed_attributes_should_be_preserved_if_save_failure
pirate = Pirate.new
pirate.parrot_id = 1
- assert !pirate.save
+ assert_not pirate.save
check_pirate_after_save_failure(pirate)
pirate = Pirate.new
@@ -380,9 +378,9 @@ class DirtyTest < ActiveRecord::TestCase
def test_reload_should_clear_changed_attributes
pirate = Pirate.create!(catchphrase: "shiver me timbers")
pirate.catchphrase = "*hic*"
- assert pirate.changed?
+ assert_predicate pirate, :changed?
pirate.reload
- assert !pirate.changed?
+ assert_not_predicate pirate, :changed?
end
def test_dup_objects_should_not_copy_dirty_flag_from_creator
@@ -390,17 +388,17 @@ class DirtyTest < ActiveRecord::TestCase
pirate_dup = pirate.dup
pirate_dup.restore_catchphrase!
pirate.catchphrase = "I love Rum"
- assert pirate.catchphrase_changed?
- assert !pirate_dup.catchphrase_changed?
+ assert_predicate pirate, :catchphrase_changed?
+ assert_not_predicate pirate_dup, :catchphrase_changed?
end
def test_reverted_changes_are_not_dirty
phrase = "shiver me timbers"
pirate = Pirate.create!(catchphrase: phrase)
pirate.catchphrase = "*hic*"
- assert pirate.changed?
+ assert_predicate pirate, :changed?
pirate.catchphrase = phrase
- assert !pirate.changed?
+ assert_not_predicate pirate, :changed?
end
def test_reverted_changes_are_not_dirty_after_multiple_changes
@@ -408,40 +406,40 @@ class DirtyTest < ActiveRecord::TestCase
pirate = Pirate.create!(catchphrase: phrase)
10.times do |i|
pirate.catchphrase = "*hic*" * i
- assert pirate.changed?
+ assert_predicate pirate, :changed?
end
- assert pirate.changed?
+ assert_predicate pirate, :changed?
pirate.catchphrase = phrase
- assert !pirate.changed?
+ assert_not_predicate pirate, :changed?
end
def test_reverted_changes_are_not_dirty_going_from_nil_to_value_and_back
pirate = Pirate.create!(catchphrase: "Yar!")
pirate.parrot_id = 1
- assert pirate.changed?
- assert pirate.parrot_id_changed?
- assert !pirate.catchphrase_changed?
+ assert_predicate pirate, :changed?
+ assert_predicate pirate, :parrot_id_changed?
+ assert_not_predicate pirate, :catchphrase_changed?
pirate.parrot_id = nil
- assert !pirate.changed?
- assert !pirate.parrot_id_changed?
- assert !pirate.catchphrase_changed?
+ assert_not_predicate pirate, :changed?
+ assert_not_predicate pirate, :parrot_id_changed?
+ assert_not_predicate pirate, :catchphrase_changed?
end
def test_save_should_store_serialized_attributes_even_with_partial_writes
with_partial_writes(Topic) do
topic = Topic.create!(content: { a: "a" })
- assert_not topic.changed?
+ assert_not_predicate topic, :changed?
topic.content[:b] = "b"
- assert topic.changed?
+ assert_predicate topic, :changed?
topic.save!
- assert_not topic.changed?
+ assert_not_predicate topic, :changed?
assert_equal "b", topic.content[:b]
topic.reload
@@ -468,14 +466,21 @@ class DirtyTest < ActiveRecord::TestCase
def test_save_should_not_save_serialized_attribute_with_partial_writes_if_not_present
with_partial_writes(Topic) do
- Topic.create!(author_name: "Bill", content: { a: "a" })
- topic = Topic.select("id, author_name").first
+ topic = Topic.create!(author_name: "Bill", content: { a: "a" })
+ topic = Topic.select("id, author_name").find(topic.id)
topic.update_columns author_name: "John"
- topic = Topic.first
- assert_not_nil topic.content
+ assert_not_nil topic.reload.content
end
end
+ def test_changes_to_save_should_not_mutate_array_of_hashes
+ topic = Topic.new(author_name: "Bill", content: [{ a: "a" }])
+
+ topic.changes_to_save
+
+ assert_equal [{ a: "a" }], topic.content
+ end
+
def test_previous_changes
# original values should be in previous_changes
pirate = Pirate.new
@@ -491,7 +496,7 @@ class DirtyTest < ActiveRecord::TestCase
assert_not_nil pirate.previous_changes["updated_on"][1]
assert_nil pirate.previous_changes["created_on"][0]
assert_not_nil pirate.previous_changes["created_on"][1]
- assert !pirate.previous_changes.key?("parrot_id")
+ assert_not pirate.previous_changes.key?("parrot_id")
# original values should be in previous_changes
pirate = Pirate.new
@@ -505,7 +510,7 @@ class DirtyTest < ActiveRecord::TestCase
assert_equal [nil, pirate.id], pirate.previous_changes["id"]
assert_includes pirate.previous_changes, "updated_on"
assert_includes pirate.previous_changes, "created_on"
- assert !pirate.previous_changes.key?("parrot_id")
+ assert_not pirate.previous_changes.key?("parrot_id")
pirate.catchphrase = "Yar!!"
pirate.reload
@@ -522,8 +527,8 @@ class DirtyTest < ActiveRecord::TestCase
assert_equal ["arrr", "Me Maties!"], pirate.previous_changes["catchphrase"]
assert_not_nil pirate.previous_changes["updated_on"][0]
assert_not_nil pirate.previous_changes["updated_on"][1]
- assert !pirate.previous_changes.key?("parrot_id")
- assert !pirate.previous_changes.key?("created_on")
+ assert_not pirate.previous_changes.key?("parrot_id")
+ assert_not pirate.previous_changes.key?("created_on")
pirate = Pirate.find_by_catchphrase("Me Maties!")
@@ -536,8 +541,8 @@ class DirtyTest < ActiveRecord::TestCase
assert_equal ["Me Maties!", "Thar She Blows!"], pirate.previous_changes["catchphrase"]
assert_not_nil pirate.previous_changes["updated_on"][0]
assert_not_nil pirate.previous_changes["updated_on"][1]
- assert !pirate.previous_changes.key?("parrot_id")
- assert !pirate.previous_changes.key?("created_on")
+ assert_not pirate.previous_changes.key?("parrot_id")
+ assert_not pirate.previous_changes.key?("created_on")
travel(1.second)
@@ -548,8 +553,8 @@ class DirtyTest < ActiveRecord::TestCase
assert_equal ["Thar She Blows!", "Ahoy!"], pirate.previous_changes["catchphrase"]
assert_not_nil pirate.previous_changes["updated_on"][0]
assert_not_nil pirate.previous_changes["updated_on"][1]
- assert !pirate.previous_changes.key?("parrot_id")
- assert !pirate.previous_changes.key?("created_on")
+ assert_not pirate.previous_changes.key?("parrot_id")
+ assert_not pirate.previous_changes.key?("created_on")
travel(1.second)
@@ -560,10 +565,8 @@ class DirtyTest < ActiveRecord::TestCase
assert_equal ["Ahoy!", "Ninjas suck!"], pirate.previous_changes["catchphrase"]
assert_not_nil pirate.previous_changes["updated_on"][0]
assert_not_nil pirate.previous_changes["updated_on"][1]
- assert !pirate.previous_changes.key?("parrot_id")
- assert !pirate.previous_changes.key?("created_on")
- ensure
- travel_back
+ assert_not pirate.previous_changes.key?("parrot_id")
+ assert_not pirate.previous_changes.key?("created_on")
end
class Testings < ActiveRecord::Base; end
@@ -599,7 +602,7 @@ class DirtyTest < ActiveRecord::TestCase
pirate = Pirate.create!(catchphrase: "rrrr", created_on: time_in_paris)
pirate.created_on = pirate.created_on.in_time_zone("Tokyo").to_s
- assert !pirate.created_on_changed?
+ assert_not_predicate pirate, :created_on_changed?
end
test "partial insert" do
@@ -630,7 +633,7 @@ class DirtyTest < ActiveRecord::TestCase
pirate = Pirate.create!(catchphrase: "arrrr")
pirate.catchphrase << " matey!"
- assert pirate.catchphrase_changed?
+ assert_predicate pirate, :catchphrase_changed?
expected_changes = {
"catchphrase" => ["arrrr", "arrrr matey!"]
}
@@ -644,7 +647,7 @@ class DirtyTest < ActiveRecord::TestCase
pirate.reload
assert_equal "arrrr matey!", pirate.catchphrase
- assert_not pirate.changed?
+ assert_not_predicate pirate, :changed?
end
test "in place mutation for binary" do
@@ -655,19 +658,60 @@ class DirtyTest < ActiveRecord::TestCase
binary = klass.create!(data: "\\\\foo")
- assert_not binary.changed?
+ assert_not_predicate binary, :changed?
binary.data = binary.data.dup
- assert_not binary.changed?
+ assert_not_predicate binary, :changed?
binary = klass.last
- assert_not binary.changed?
+ assert_not_predicate binary, :changed?
binary.data << "bar"
- assert binary.changed?
+ assert_predicate binary, :changed?
+ end
+
+ test "changes is correct for subclass" do
+ foo = Class.new(Pirate) do
+ def catchphrase
+ super.upcase
+ end
+ end
+
+ pirate = foo.create!(catchphrase: "arrrr")
+
+ new_catchphrase = "arrrr matey!"
+
+ pirate.catchphrase = new_catchphrase
+ assert_predicate pirate, :catchphrase_changed?
+
+ expected_changes = {
+ "catchphrase" => ["arrrr", new_catchphrase]
+ }
+
+ assert_equal new_catchphrase.upcase, pirate.catchphrase
+ assert_equal expected_changes, pirate.changes
+ end
+
+ test "changes is correct if override attribute reader" do
+ pirate = Pirate.create!(catchphrase: "arrrr")
+ def pirate.catchphrase
+ super.upcase
+ end
+
+ new_catchphrase = "arrrr matey!"
+
+ pirate.catchphrase = new_catchphrase
+ assert_predicate pirate, :catchphrase_changed?
+
+ expected_changes = {
+ "catchphrase" => ["arrrr", new_catchphrase]
+ }
+
+ assert_equal new_catchphrase.upcase, pirate.catchphrase
+ assert_equal expected_changes, pirate.changes
end
test "attribute_changed? doesn't compute in-place changes for unrelated attributes" do
@@ -682,7 +726,7 @@ class DirtyTest < ActiveRecord::TestCase
end
model = klass.new(first_name: "Jim")
- assert model.first_name_changed?
+ assert_predicate model, :first_name_changed?
end
test "attribute_will_change! doesn't try to save non-persistable attributes" do
@@ -694,10 +738,28 @@ class DirtyTest < ActiveRecord::TestCase
record = klass.new(first_name: "Sean")
record.non_persisted_attribute_will_change!
- assert record.non_persisted_attribute_changed?
+ assert_predicate record, :non_persisted_attribute_changed?
assert record.save
end
+ test "virtual attributes are not written with partial_writes off" do
+ with_partial_writes(ActiveRecord::Base, false) do
+ klass = Class.new(ActiveRecord::Base) do
+ self.table_name = "people"
+ attribute :non_persisted_attribute, :string
+ end
+
+ record = klass.new(first_name: "Sean")
+ record.non_persisted_attribute_will_change!
+
+ assert record.save
+
+ record.non_persisted_attribute_will_change!
+
+ assert record.save
+ end
+ end
+
test "mutating and then assigning doesn't remove the change" do
pirate = Pirate.create!(catchphrase: "arrrr")
pirate.catchphrase << " matey!"
@@ -724,26 +786,33 @@ class DirtyTest < ActiveRecord::TestCase
test "attributes assigned but not selected are dirty" do
person = Person.select(:id).first
- refute person.changed?
+ assert_not_predicate person, :changed?
person.first_name = "Sean"
- assert person.changed?
+ assert_predicate person, :changed?
person.first_name = nil
- assert person.changed?
+ assert_predicate person, :changed?
+ end
+
+ test "attributes not selected are still missing after save" do
+ person = Person.select(:id).first
+ assert_raises(ActiveModel::MissingAttributeError) { person.first_name }
+ assert person.save # calls forget_attribute_assignments
+ assert_raises(ActiveModel::MissingAttributeError) { person.first_name }
end
test "saved_change_to_attribute? returns whether a change occurred in the last save" do
person = Person.create!(first_name: "Sean")
- assert person.saved_change_to_first_name?
- refute person.saved_change_to_gender?
+ assert_predicate person, :saved_change_to_first_name?
+ assert_not_predicate person, :saved_change_to_gender?
assert person.saved_change_to_first_name?(from: nil, to: "Sean")
assert person.saved_change_to_first_name?(from: nil)
assert person.saved_change_to_first_name?(to: "Sean")
- refute person.saved_change_to_first_name?(from: "Jim", to: "Sean")
- refute person.saved_change_to_first_name?(from: "Jim")
- refute person.saved_change_to_first_name?(to: "Jim")
+ assert_not person.saved_change_to_first_name?(from: "Jim", to: "Sean")
+ assert_not person.saved_change_to_first_name?(from: "Jim")
+ assert_not person.saved_change_to_first_name?(to: "Jim")
end
test "saved_change_to_attribute returns the change that occurred in the last save" do
@@ -778,11 +847,11 @@ class DirtyTest < ActiveRecord::TestCase
test "saved_changes? returns whether the last call to save changed anything" do
person = Person.create!(first_name: "Sean")
- assert person.saved_changes?
+ assert_predicate person, :saved_changes?
person.save
- refute person.saved_changes?
+ assert_not_predicate person, :saved_changes?
end
test "saved_changes returns a hash of all the changes that occurred" do
@@ -800,20 +869,39 @@ class DirtyTest < ActiveRecord::TestCase
assert_equal %w(first_name lock_version updated_at).sort, person.saved_changes.keys.sort
end
- test "changed? in after callbacks returns true but is deprecated" do
+ test "changed? in after callbacks returns false" do
klass = Class.new(ActiveRecord::Base) do
self.table_name = "people"
after_save do
- ActiveSupport::Deprecation.silence do
- raise "changed? should be true" unless changed?
- end
+ raise "changed? should be false" if changed?
+ raise "has_changes_to_save? should be false" if has_changes_to_save?
+ raise "saved_changes? should be true" unless saved_changes?
+ raise "id_in_database should not be nil" if id_in_database.nil?
+ end
+ end
+
+ person = klass.create!(first_name: "Sean")
+ assert_not_predicate person, :changed?
+ end
+
+ test "changed? in around callbacks after yield returns false" do
+ klass = Class.new(ActiveRecord::Base) do
+ self.table_name = "people"
+
+ around_create :check_around
+
+ def check_around
+ yield
+ raise "changed? should be false" if changed?
raise "has_changes_to_save? should be false" if has_changes_to_save?
+ raise "saved_changes? should be true" unless saved_changes?
+ raise "id_in_database should not be nil" if id_in_database.nil?
end
end
person = klass.create!(first_name: "Sean")
- refute person.changed?
+ assert_not_predicate person, :changed?
end
private
@@ -826,8 +914,8 @@ class DirtyTest < ActiveRecord::TestCase
end
def check_pirate_after_save_failure(pirate)
- assert pirate.changed?
- assert pirate.parrot_id_changed?
+ assert_predicate pirate, :changed?
+ assert_predicate pirate, :parrot_id_changed?
assert_equal %w(parrot_id), pirate.changed
assert_nil pirate.parrot_id_was
end
diff --git a/activerecord/test/cases/disconnected_test.rb b/activerecord/test/cases/disconnected_test.rb
index c25089a420..533665d0f4 100644
--- a/activerecord/test/cases/disconnected_test.rb
+++ b/activerecord/test/cases/disconnected_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
class TestRecord < ActiveRecord::Base
diff --git a/activerecord/test/cases/dup_test.rb b/activerecord/test/cases/dup_test.rb
index 3821e0c949..a2efbf89f9 100644
--- a/activerecord/test/cases/dup_test.rb
+++ b/activerecord/test/cases/dup_test.rb
@@ -1,20 +1,23 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/reply"
require "models/topic"
+require "models/movie"
module ActiveRecord
class DupTest < ActiveRecord::TestCase
fixtures :topics
def test_dup
- assert !Topic.new.freeze.dup.frozen?
+ assert_not_predicate Topic.new.freeze.dup, :frozen?
end
def test_not_readonly
topic = Topic.first
duped = topic.dup
- assert !duped.readonly?, "should not be readonly"
+ assert_not duped.readonly?, "should not be readonly"
end
def test_is_readonly
@@ -29,7 +32,7 @@ module ActiveRecord
topic = Topic.first
duped = topic.dup
- assert !duped.persisted?, "topic not persisted"
+ assert_not duped.persisted?, "topic not persisted"
assert duped.new_record?, "topic is new"
end
@@ -38,7 +41,7 @@ module ActiveRecord
topic.destroy
duped = topic.dup
- assert_not duped.destroyed?
+ assert_not_predicate duped, :destroyed?
end
def test_dup_has_no_id
@@ -60,10 +63,10 @@ module ActiveRecord
topic.attributes = dbtopic.attributes.except("id")
- #duped has no timestamp values
+ # duped has no timestamp values
duped = dbtopic.dup
- #clear topic timestamp values
+ # clear topic timestamp values
topic.send(:clear_timestamp_attributes)
assert_equal topic.changes, duped.changes
@@ -98,7 +101,7 @@ module ActiveRecord
# temporary change to the topic object
topic.updated_at -= 3.days
- #dup should not preserve the timestamps if present
+ # dup should not preserve the timestamps if present
new_topic = topic.dup
assert_nil new_topic.updated_at
assert_nil new_topic.created_at
@@ -124,12 +127,12 @@ module ActiveRecord
duped = topic.dup
duped.title = nil
- assert duped.invalid?
+ assert_predicate duped, :invalid?
topic.title = nil
duped.title = "Mathematics"
- assert topic.invalid?
- assert duped.valid?
+ assert_predicate topic, :invalid?
+ assert_predicate duped, :valid?
end
end
@@ -137,12 +140,14 @@ module ActiveRecord
prev_default_scopes = Topic.default_scopes
Topic.default_scopes = [proc { Topic.where(approved: true) }]
topic = Topic.new(approved: false)
- assert !topic.dup.approved?, "should not be overridden by default scopes"
+ assert_not topic.dup.approved?, "should not be overridden by default scopes"
ensure
Topic.default_scopes = prev_default_scopes
end
def test_dup_without_primary_key
+ skip if current_adapter?(:OracleAdapter)
+
klass = Class.new(ActiveRecord::Base) do
self.table_name = "parrots_pirates"
end
@@ -153,5 +158,20 @@ module ActiveRecord
record.dup
end
end
+
+ def test_dup_record_not_persisted_after_rollback_transaction
+ movie = Movie.new(name: "test")
+
+ assert_raises(ActiveRecord::RecordInvalid) do
+ Movie.transaction do
+ movie.save!
+ duped = movie.dup
+ duped.name = nil
+ duped.save!
+ end
+ end
+
+ assert_not movie.persisted?
+ end
end
end
diff --git a/activerecord/test/cases/enum_test.rb b/activerecord/test/cases/enum_test.rb
index b7641fcf32..d5a1d11e12 100644
--- a/activerecord/test/cases/enum_test.rb
+++ b/activerecord/test/cases/enum_test.rb
@@ -1,24 +1,27 @@
+# frozen_string_literal: true
+
require "cases/helper"
+require "models/author"
require "models/book"
class EnumTest < ActiveRecord::TestCase
- fixtures :books
+ fixtures :books, :authors, :author_addresses
setup do
@book = books(:awdr)
end
test "query state by predicate" do
- assert @book.published?
- assert_not @book.written?
- assert_not @book.proposed?
+ assert_predicate @book, :published?
+ assert_not_predicate @book, :written?
+ assert_not_predicate @book, :proposed?
- assert @book.read?
- assert @book.in_english?
- assert @book.author_visibility_visible?
- assert @book.illustrator_visibility_visible?
- assert @book.with_medium_font_size?
- assert @book.medium_to_read?
+ assert_predicate @book, :read?
+ assert_predicate @book, :in_english?
+ assert_predicate @book, :author_visibility_visible?
+ assert_predicate @book, :illustrator_visibility_visible?
+ assert_predicate @book, :with_medium_font_size?
+ assert_predicate @book, :medium_to_read?
end
test "query state with strings" do
@@ -37,6 +40,8 @@ class EnumTest < ActiveRecord::TestCase
assert_equal @book, Book.author_visibility_visible.first
assert_equal @book, Book.illustrator_visibility_visible.first
assert_equal @book, Book.medium_to_read.first
+ assert_equal books(:ddd), Book.forgotten.first
+ assert_equal books(:rfr), authors(:david).unpublished_books.first
end
test "find via where with values" do
@@ -57,6 +62,7 @@ class EnumTest < ActiveRecord::TestCase
assert_not_equal @book, Book.where(status: [:written]).first
assert_not_equal @book, Book.where.not(status: :published).first
assert_equal @book, Book.where.not(status: :written).first
+ assert_equal books(:ddd), Book.where(read_status: :forgotten).first
end
test "find via where with strings" do
@@ -66,49 +72,50 @@ class EnumTest < ActiveRecord::TestCase
assert_not_equal @book, Book.where(status: ["written"]).first
assert_not_equal @book, Book.where.not(status: "published").first
assert_equal @book, Book.where.not(status: "written").first
+ assert_equal books(:ddd), Book.where(read_status: "forgotten").first
end
test "build from scope" do
- assert Book.written.build.written?
- assert_not Book.written.build.proposed?
+ assert_predicate Book.written.build, :written?
+ assert_not_predicate Book.written.build, :proposed?
end
test "build from where" do
- assert Book.where(status: Book.statuses[:written]).build.written?
- assert_not Book.where(status: Book.statuses[:written]).build.proposed?
- assert Book.where(status: :written).build.written?
- assert_not Book.where(status: :written).build.proposed?
- assert Book.where(status: "written").build.written?
- assert_not Book.where(status: "written").build.proposed?
+ assert_predicate Book.where(status: Book.statuses[:written]).build, :written?
+ assert_not_predicate Book.where(status: Book.statuses[:written]).build, :proposed?
+ assert_predicate Book.where(status: :written).build, :written?
+ assert_not_predicate Book.where(status: :written).build, :proposed?
+ assert_predicate Book.where(status: "written").build, :written?
+ assert_not_predicate Book.where(status: "written").build, :proposed?
end
test "update by declaration" do
@book.written!
- assert @book.written?
+ assert_predicate @book, :written?
@book.in_english!
- assert @book.in_english?
+ assert_predicate @book, :in_english?
@book.author_visibility_visible!
- assert @book.author_visibility_visible?
+ assert_predicate @book, :author_visibility_visible?
end
test "update by setter" do
@book.update! status: :written
- assert @book.written?
+ assert_predicate @book, :written?
end
test "enum methods are overwritable" do
assert_equal "do publish work...", @book.published!
- assert @book.published?
+ assert_predicate @book, :published?
end
test "direct assignment" do
@book.status = :written
- assert @book.written?
+ assert_predicate @book, :written?
end
test "assign string value" do
@book.status = "written"
- assert @book.written?
+ assert_predicate @book, :written?
end
test "enum changed attributes" do
@@ -235,25 +242,27 @@ class EnumTest < ActiveRecord::TestCase
end
test "building new objects with enum scopes" do
- assert Book.written.build.written?
- assert Book.read.build.read?
- assert Book.in_spanish.build.in_spanish?
- assert Book.illustrator_visibility_invisible.build.illustrator_visibility_invisible?
+ assert_predicate Book.written.build, :written?
+ assert_predicate Book.read.build, :read?
+ assert_predicate Book.in_spanish.build, :in_spanish?
+ assert_predicate Book.illustrator_visibility_invisible.build, :illustrator_visibility_invisible?
end
test "creating new objects with enum scopes" do
- assert Book.written.create.written?
- assert Book.read.create.read?
- assert Book.in_spanish.create.in_spanish?
- assert Book.illustrator_visibility_invisible.create.illustrator_visibility_invisible?
+ assert_predicate Book.written.create, :written?
+ assert_predicate Book.read.create, :read?
+ assert_predicate Book.in_spanish.create, :in_spanish?
+ assert_predicate Book.illustrator_visibility_invisible.create, :illustrator_visibility_invisible?
end
- test "_before_type_cast returns the enum label (required for form fields)" do
- if @book.status_came_from_user?
- assert_equal "published", @book.status_before_type_cast
- else
- assert_equal "published", @book.status
- end
+ test "_before_type_cast" do
+ assert_equal 2, @book.status_before_type_cast
+ assert_equal "published", @book.status
+
+ @book.status = "published"
+
+ assert_equal "published", @book.status_before_type_cast
+ assert_equal "published", @book.status
end
test "reserved enum names" do
@@ -299,6 +308,24 @@ class EnumTest < ActiveRecord::TestCase
end
end
+ test "reserved enum values for relation" do
+ relation_method_samples = [
+ :records,
+ :to_ary,
+ :scope_for_create
+ ]
+
+ relation_method_samples.each do |value|
+ e = assert_raises(ArgumentError, "enum value `#{value}` should not be allowed") do
+ Class.new(ActiveRecord::Base) do
+ self.table_name = "books"
+ enum category: [:other, value]
+ end
+ end
+ assert_match(/You tried to define an enum named .* on the model/, e.message)
+ end
+ end
+
test "overriding enum method should not raise" do
assert_nothing_raised do
Class.new(ActiveRecord::Base) do
@@ -328,9 +355,9 @@ class EnumTest < ActiveRecord::TestCase
klass.delete_all
klass.create!(status: "proposed")
book = klass.new(status: "written")
- assert book.valid?
+ assert_predicate book, :valid?
book.status = "proposed"
- assert_not book.valid?
+ assert_not_predicate book, :valid?
end
test "validate inclusion of value in array" do
@@ -341,9 +368,9 @@ class EnumTest < ActiveRecord::TestCase
end
klass.delete_all
invalid_book = klass.new(status: "proposed")
- assert_not invalid_book.valid?
+ assert_not_predicate invalid_book, :valid?
valid_book = klass.new(status: "written")
- assert valid_book.valid?
+ assert_predicate valid_book, :valid?
end
test "enums are distinct per class" do
@@ -390,10 +417,10 @@ class EnumTest < ActiveRecord::TestCase
end
book1 = klass.proposed.create!
- assert book1.proposed?
+ assert_predicate book1, :proposed?
book2 = klass.single.create!
- assert book2.single?
+ assert_predicate book2, :single?
end
test "enum with alias_attribute" do
@@ -404,62 +431,62 @@ class EnumTest < ActiveRecord::TestCase
end
book = klass.proposed.create!
- assert book.proposed?
+ assert_predicate book, :proposed?
assert_equal "proposed", book.aliased_status
book = klass.find(book.id)
- assert book.proposed?
+ assert_predicate book, :proposed?
assert_equal "proposed", book.aliased_status
end
test "query state by predicate with prefix" do
- assert @book.author_visibility_visible?
- assert_not @book.author_visibility_invisible?
- assert @book.illustrator_visibility_visible?
- assert_not @book.illustrator_visibility_invisible?
+ assert_predicate @book, :author_visibility_visible?
+ assert_not_predicate @book, :author_visibility_invisible?
+ assert_predicate @book, :illustrator_visibility_visible?
+ assert_not_predicate @book, :illustrator_visibility_invisible?
end
test "query state by predicate with custom prefix" do
- assert @book.in_english?
- assert_not @book.in_spanish?
- assert_not @book.in_french?
+ assert_predicate @book, :in_english?
+ assert_not_predicate @book, :in_spanish?
+ assert_not_predicate @book, :in_french?
end
test "query state by predicate with custom suffix" do
- assert @book.medium_to_read?
- assert_not @book.easy_to_read?
- assert_not @book.hard_to_read?
+ assert_predicate @book, :medium_to_read?
+ assert_not_predicate @book, :easy_to_read?
+ assert_not_predicate @book, :hard_to_read?
end
test "enum methods with custom suffix defined" do
- assert @book.class.respond_to?(:easy_to_read)
- assert @book.class.respond_to?(:medium_to_read)
- assert @book.class.respond_to?(:hard_to_read)
+ assert_respond_to @book.class, :easy_to_read
+ assert_respond_to @book.class, :medium_to_read
+ assert_respond_to @book.class, :hard_to_read
- assert @book.respond_to?(:easy_to_read?)
- assert @book.respond_to?(:medium_to_read?)
- assert @book.respond_to?(:hard_to_read?)
+ assert_respond_to @book, :easy_to_read?
+ assert_respond_to @book, :medium_to_read?
+ assert_respond_to @book, :hard_to_read?
- assert @book.respond_to?(:easy_to_read!)
- assert @book.respond_to?(:medium_to_read!)
- assert @book.respond_to?(:hard_to_read!)
+ assert_respond_to @book, :easy_to_read!
+ assert_respond_to @book, :medium_to_read!
+ assert_respond_to @book, :hard_to_read!
end
test "update enum attributes with custom suffix" do
@book.medium_to_read!
- assert_not @book.easy_to_read?
- assert @book.medium_to_read?
- assert_not @book.hard_to_read?
+ assert_not_predicate @book, :easy_to_read?
+ assert_predicate @book, :medium_to_read?
+ assert_not_predicate @book, :hard_to_read?
@book.easy_to_read!
- assert @book.easy_to_read?
- assert_not @book.medium_to_read?
- assert_not @book.hard_to_read?
+ assert_predicate @book, :easy_to_read?
+ assert_not_predicate @book, :medium_to_read?
+ assert_not_predicate @book, :hard_to_read?
@book.hard_to_read!
- assert_not @book.easy_to_read?
- assert_not @book.medium_to_read?
- assert @book.hard_to_read?
+ assert_not_predicate @book, :easy_to_read?
+ assert_not_predicate @book, :medium_to_read?
+ assert_predicate @book, :hard_to_read?
end
test "uses default status when no status is provided in fixtures" do
@@ -470,12 +497,12 @@ class EnumTest < ActiveRecord::TestCase
test "uses default value from database on initialization" do
book = Book.new
- assert book.proposed?
+ assert_predicate book, :proposed?
end
test "uses default value from database on initialization when using custom mapping" do
book = Book.new
- assert book.hard?
+ assert_predicate book, :hard?
end
test "data type of Enum type" do
diff --git a/activerecord/test/cases/errors_test.rb b/activerecord/test/cases/errors_test.rb
index 73feb831d0..b90e6a66c5 100644
--- a/activerecord/test/cases/errors_test.rb
+++ b/activerecord/test/cases/errors_test.rb
@@ -1,4 +1,6 @@
-require_relative "../cases/helper"
+# frozen_string_literal: true
+
+require "cases/helper"
class ErrorsTest < ActiveRecord::TestCase
def test_can_be_instantiated_with_no_args
diff --git a/activerecord/test/cases/explain_subscriber_test.rb b/activerecord/test/cases/explain_subscriber_test.rb
index ca87e04012..79a0630193 100644
--- a/activerecord/test/cases/explain_subscriber_test.rb
+++ b/activerecord/test/cases/explain_subscriber_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "active_record/explain_subscriber"
require "active_record/explain_registry"
@@ -13,20 +15,20 @@ if ActiveRecord::Base.connection.supports_explain?
def test_collects_nothing_if_the_payload_has_an_exception
SUBSCRIBER.finish(nil, nil, exception: Exception.new)
- assert queries.empty?
+ assert_empty queries
end
def test_collects_nothing_for_ignored_payloads
ActiveRecord::ExplainSubscriber::IGNORED_PAYLOADS.each do |ip|
SUBSCRIBER.finish(nil, nil, name: ip)
end
- assert queries.empty?
+ assert_empty queries
end
def test_collects_nothing_if_collect_is_false
ActiveRecord::ExplainRegistry.collect = false
SUBSCRIBER.finish(nil, nil, name: "SQL", sql: "select 1 from users", binds: [1, 2])
- assert queries.empty?
+ assert_empty queries
end
def test_collects_pairs_of_queries_and_binds
@@ -38,14 +40,14 @@ if ActiveRecord::Base.connection.supports_explain?
assert_equal binds, queries[0][1]
end
- def test_collects_nothing_if_the_statement_is_not_whitelisted
+ def test_collects_nothing_if_the_statement_is_not_explainable
SUBSCRIBER.finish(nil, nil, name: "SQL", sql: "SHOW max_identifier_length")
- assert queries.empty?
+ assert_empty queries
end
def test_collects_nothing_if_the_statement_is_only_partially_matched
SUBSCRIBER.finish(nil, nil, name: "SQL", sql: "select_db yo_mama")
- assert queries.empty?
+ assert_empty queries
end
def test_collects_cte_queries
diff --git a/activerecord/test/cases/explain_test.rb b/activerecord/test/cases/explain_test.rb
index 86fe90ae51..a0e75f4e89 100644
--- a/activerecord/test/cases/explain_test.rb
+++ b/activerecord/test/cases/explain_test.rb
@@ -1,6 +1,7 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/car"
-require "active_support/core_ext/string/strip"
if ActiveRecord::Base.connection.supports_explain?
class ExplainTest < ActiveRecord::TestCase
@@ -51,7 +52,7 @@ if ActiveRecord::Base.connection.supports_explain?
queries = sqls.zip(binds)
stub_explain_for_query_plans(["query plan foo\n", "query plan bar\n"]) do
- expected = <<-SQL.strip_heredoc
+ expected = <<~SQL
EXPLAIN for: #{sqls[0]} [["wadus", 1]]
query plan foo
diff --git a/activerecord/test/cases/filter_attributes_test.rb b/activerecord/test/cases/filter_attributes_test.rb
new file mode 100644
index 0000000000..f88cecfe2b
--- /dev/null
+++ b/activerecord/test/cases/filter_attributes_test.rb
@@ -0,0 +1,80 @@
+# frozen_string_literal: true
+
+require "cases/helper"
+require "models/admin"
+require "models/admin/user"
+require "models/admin/account"
+require "pp"
+
+class FilterAttributesTest < ActiveRecord::TestCase
+ fixtures :"admin/users", :"admin/accounts"
+
+ setup do
+ ActiveRecord::Base.filter_attributes = [:name]
+ end
+
+ teardown do
+ ActiveRecord::Base.filter_attributes = []
+ end
+
+ test "filter_attributes" do
+ Admin::User.all.each do |user|
+ assert_includes user.inspect, "name: [FILTERED]"
+ assert_equal 1, user.inspect.scan("[FILTERED]").length
+ end
+
+ Admin::Account.all.each do |account|
+ assert_includes account.inspect, "name: [FILTERED]"
+ assert_equal 1, account.inspect.scan("[FILTERED]").length
+ end
+ end
+
+ test "filter_attributes could be overwritten by models" do
+ Admin::Account.all.each do |account|
+ assert_includes account.inspect, "name: [FILTERED]"
+ assert_equal 1, account.inspect.scan("[FILTERED]").length
+ end
+
+ Admin::Account.filter_attributes = []
+
+ # Above changes should not impact other models
+ Admin::User.all.each do |user|
+ assert_includes user.inspect, "name: [FILTERED]"
+ assert_equal 1, user.inspect.scan("[FILTERED]").length
+ end
+
+ Admin::Account.all.each do |account|
+ assert_not_includes account.inspect, "name: [FILTERED]"
+ assert_equal 0, account.inspect.scan("[FILTERED]").length
+ end
+
+ Admin::Account.filter_attributes = [:name]
+ end
+
+ test "filter_attributes should not filter nil value" do
+ account = Admin::Account.new
+
+ assert_includes account.inspect, "name: nil"
+ assert_not_includes account.inspect, "name: [FILTERED]"
+ assert_equal 0, account.inspect.scan("[FILTERED]").length
+ end
+
+ test "filter_attributes on pretty_print" do
+ user = admin_users(:david)
+ actual = "".dup
+ PP.pp(user, StringIO.new(actual))
+
+ assert_includes actual, "name: [FILTERED]"
+ assert_equal 1, actual.scan("[FILTERED]").length
+ end
+
+ test "filter_attributes on pretty_print should not filter nil value" do
+ user = Admin::User.new
+ actual = "".dup
+ PP.pp(user, StringIO.new(actual))
+
+ assert_includes actual, "name: nil"
+ assert_not_includes actual, "name: [FILTERED]"
+ assert_equal 0, actual.scan("[FILTERED]").length
+ end
+end
diff --git a/activerecord/test/cases/finder_respond_to_test.rb b/activerecord/test/cases/finder_respond_to_test.rb
index 3eaa993d45..59af4e6961 100644
--- a/activerecord/test/cases/finder_respond_to_test.rb
+++ b/activerecord/test/cases/finder_respond_to_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/topic"
@@ -6,7 +8,7 @@ class FinderRespondToTest < ActiveRecord::TestCase
def test_should_preserve_normal_respond_to_behaviour_on_base
assert_respond_to ActiveRecord::Base, :new
- assert !ActiveRecord::Base.respond_to?(:find_by_something)
+ assert_not_respond_to ActiveRecord::Base, :find_by_something
end
def test_should_preserve_normal_respond_to_behaviour_and_respond_to_newly_added_method
@@ -41,14 +43,14 @@ class FinderRespondToTest < ActiveRecord::TestCase
end
def test_should_not_respond_to_find_by_one_missing_attribute
- assert !Topic.respond_to?(:find_by_undertitle)
+ assert_not_respond_to Topic, :find_by_undertitle
end
def test_should_not_respond_to_find_by_invalid_method_syntax
- assert !Topic.respond_to?(:fail_to_find_by_title)
- assert !Topic.respond_to?(:find_by_title?)
- assert !Topic.respond_to?(:fail_to_find_or_create_by_title)
- assert !Topic.respond_to?(:find_or_create_by_title?)
+ assert_not_respond_to Topic, :fail_to_find_by_title
+ assert_not_respond_to Topic, :find_by_title?
+ assert_not_respond_to Topic, :fail_to_find_or_create_by_title
+ assert_not_respond_to Topic, :find_or_create_by_title?
end
private
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index 89d8a8bdca..e73c88dd5d 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/post"
require "models/author"
@@ -7,6 +9,7 @@ require "models/company"
require "models/tagging"
require "models/topic"
require "models/reply"
+require "models/rating"
require "models/entrant"
require "models/project"
require "models/developer"
@@ -117,6 +120,21 @@ class FinderTest < ActiveRecord::TestCase
assert_equal "The Fourth Topic of the day", records[2].title
end
+ def test_find_with_ids_with_no_id_passed
+ exception = assert_raises(ActiveRecord::RecordNotFound) { Topic.find }
+ assert_equal exception.model, "Topic"
+ assert_equal exception.primary_key, "id"
+ end
+
+ def test_find_with_ids_with_id_out_of_range
+ exception = assert_raises(ActiveRecord::RecordNotFound) do
+ Topic.find("9999999999999999999999999999999")
+ end
+
+ assert_equal exception.model, "Topic"
+ assert_equal exception.primary_key, "id"
+ end
+
def test_find_passing_active_record_object_is_not_permitted
assert_raises(ArgumentError) do
Topic.find(Topic.last)
@@ -154,6 +172,32 @@ class FinderTest < ActiveRecord::TestCase
assert_raise(NoMethodError) { Topic.exists?([1, 2]) }
end
+ def test_exists_with_scope
+ davids = Author.where(name: "David")
+ assert_equal true, davids.exists?
+ assert_equal true, davids.exists?(authors(:david).id)
+ assert_equal false, davids.exists?(authors(:mary).id)
+ assert_equal false, davids.exists?("42")
+ assert_equal false, davids.exists?(42)
+ assert_equal false, davids.exists?(davids.new.id)
+
+ fake = Author.where(name: "fake author")
+ assert_equal false, fake.exists?
+ assert_equal false, fake.exists?(authors(:david).id)
+ end
+
+ def test_exists_uses_existing_scope
+ post = authors(:david).posts.first
+ authors = Author.includes(:posts).where(name: "David", posts: { id: post.id })
+ assert_equal true, authors.exists?(authors(:david).id)
+ end
+
+ def test_any_with_scope_on_hash_includes
+ post = authors(:david).posts.first
+ categories = Categorization.includes(author: :posts).where(posts: { id: post.id })
+ assert_equal true, categories.exists?
+ end
+
def test_exists_with_polymorphic_relation
post = Post.create!(title: "Post", body: "default", taggings: [Tagging.new(comment: "tagging comment")])
relation = Post.tagged_with_comment("tagging comment")
@@ -202,26 +246,51 @@ class FinderTest < ActiveRecord::TestCase
assert_equal true, Topic.first.replies.exists?
end
- # ensures +exists?+ runs valid SQL by excluding order value
- def test_exists_with_order
+ # Ensure +exists?+ runs without an error by excluding distinct value.
+ # See https://github.com/rails/rails/pull/26981.
+ def test_exists_with_order_and_distinct
assert_equal true, Topic.order(:id).distinct.exists?
end
+ # Ensure +exists?+ runs without an error by excluding order value.
+ def test_exists_with_order
+ assert_equal true, Topic.order(Arel.sql("invalid sql here")).exists?
+ end
+
+ def test_exists_with_joins
+ assert_equal true, Topic.joins(:replies).where(replies_topics: { approved: true }).order("replies_topics.created_at DESC").exists?
+ end
+
+ def test_exists_with_left_joins
+ assert_equal true, Topic.left_joins(:replies).where(replies_topics: { approved: true }).order("replies_topics.created_at DESC").exists?
+ end
+
+ def test_exists_with_eager_load
+ assert_equal true, Topic.eager_load(:replies).where(replies_topics: { approved: true }).order("replies_topics.created_at DESC").exists?
+ end
+
def test_exists_with_includes_limit_and_empty_result
- assert_equal false, Topic.includes(:replies).limit(0).exists?
- assert_equal false, Topic.includes(:replies).limit(1).where("0 = 1").exists?
+ assert_no_queries { assert_equal false, Topic.includes(:replies).limit(0).exists? }
+ assert_queries(1) { assert_equal false, Topic.includes(:replies).limit(1).where("0 = 1").exists? }
end
def test_exists_with_distinct_association_includes_and_limit
author = Author.first
- assert_equal false, author.unique_categorized_posts.includes(:special_comments).limit(0).exists?
- assert_equal true, author.unique_categorized_posts.includes(:special_comments).limit(1).exists?
+ unique_categorized_posts = author.unique_categorized_posts.includes(:special_comments)
+ assert_no_queries { assert_equal false, unique_categorized_posts.limit(0).exists? }
+ assert_queries(1) { assert_equal true, unique_categorized_posts.limit(1).exists? }
end
def test_exists_with_distinct_association_includes_limit_and_order
author = Author.first
- assert_equal false, author.unique_categorized_posts.includes(:special_comments).order("comments.tags_count DESC").limit(0).exists?
- assert_equal true, author.unique_categorized_posts.includes(:special_comments).order("comments.tags_count DESC").limit(1).exists?
+ unique_categorized_posts = author.unique_categorized_posts.includes(:special_comments).order("comments.tags_count DESC")
+ assert_no_queries { assert_equal false, unique_categorized_posts.limit(0).exists? }
+ assert_queries(1) { assert_equal true, unique_categorized_posts.limit(1).exists? }
+ end
+
+ def test_exists_should_reference_correct_aliases_while_joining_tables_of_has_many_through_association
+ ratings = developers(:david).ratings.includes(comment: :post).where(posts: { id: 1 })
+ assert_queries(1) { assert_not_predicate ratings.limit(1), :exists? }
end
def test_exists_with_empty_table_and_no_args_given
@@ -236,9 +305,9 @@ class FinderTest < ActiveRecord::TestCase
def test_exists_with_aggregate_having_three_mappings_with_one_difference
existing_address = customers(:david).address
- assert_equal false, Customer.exists?(address: Address.new(existing_address.street, existing_address.city, existing_address.country + "1"))
- assert_equal false, Customer.exists?(address: Address.new(existing_address.street, existing_address.city + "1", existing_address.country))
- assert_equal false, Customer.exists?(address: Address.new(existing_address.street + "1", existing_address.city, existing_address.country))
+ assert_equal false, Customer.exists?(address: Address.new(existing_address.street, existing_address.city, existing_address.country + "1"))
+ assert_equal false, Customer.exists?(address: Address.new(existing_address.street, existing_address.city + "1", existing_address.country))
+ assert_equal false, Customer.exists?(address: Address.new(existing_address.street + "1", existing_address.city, existing_address.country))
end
def test_exists_does_not_instantiate_records
@@ -286,6 +355,12 @@ class FinderTest < ActiveRecord::TestCase
end
def test_find_on_relation_with_large_number
+ assert_raises(ActiveRecord::RecordNotFound) do
+ Topic.where("1=1").find(9999999999999999999999999999999)
+ end
+ end
+
+ def test_find_by_on_relation_with_large_number
assert_nil Topic.where("1=1").find_by(id: 9999999999999999999999999999999)
end
@@ -345,7 +420,7 @@ class FinderTest < ActiveRecord::TestCase
end
def test_take
- assert_equal topics(:first), Topic.take
+ assert_equal topics(:first), Topic.where("title = 'The First Topic'").take
end
def test_take_failing
@@ -388,6 +463,7 @@ class FinderTest < ActiveRecord::TestCase
expected = topics(:first)
expected.touch # PostgreSQL changes the default order if no order clause is used
assert_equal expected, Topic.first
+ assert_equal expected, Topic.limit(5).first
end
def test_model_class_responds_to_first_bang
@@ -410,6 +486,7 @@ class FinderTest < ActiveRecord::TestCase
expected = topics(:second)
expected.touch # PostgreSQL changes the default order if no order clause is used
assert_equal expected, Topic.second
+ assert_equal expected, Topic.limit(5).second
end
def test_model_class_responds_to_second_bang
@@ -432,6 +509,7 @@ class FinderTest < ActiveRecord::TestCase
expected = topics(:third)
expected.touch # PostgreSQL changes the default order if no order clause is used
assert_equal expected, Topic.third
+ assert_equal expected, Topic.limit(5).third
end
def test_model_class_responds_to_third_bang
@@ -454,6 +532,7 @@ class FinderTest < ActiveRecord::TestCase
expected = topics(:fourth)
expected.touch # PostgreSQL changes the default order if no order clause is used
assert_equal expected, Topic.fourth
+ assert_equal expected, Topic.limit(5).fourth
end
def test_model_class_responds_to_fourth_bang
@@ -476,6 +555,7 @@ class FinderTest < ActiveRecord::TestCase
expected = topics(:fifth)
expected.touch # PostgreSQL changes the default order if no order clause is used
assert_equal expected, Topic.fifth
+ assert_equal expected, Topic.limit(5).fifth
end
def test_model_class_responds_to_fifth_bang
@@ -496,7 +576,7 @@ class FinderTest < ActiveRecord::TestCase
assert_nil Topic.offset(4).second_to_last
assert_nil Topic.offset(5).second_to_last
- #test with limit
+ # test with limit
assert_nil Topic.limit(1).second
assert_nil Topic.limit(1).second_to_last
end
@@ -579,13 +659,13 @@ class FinderTest < ActiveRecord::TestCase
def test_last_with_integer_and_order_should_use_sql_limit
relation = Topic.order("title")
assert_queries(1) { relation.last(5) }
- assert !relation.loaded?
+ assert_not_predicate relation, :loaded?
end
def test_last_with_integer_and_reorder_should_use_sql_limit
relation = Topic.reorder("title")
assert_queries(1) { relation.last(5) }
- assert !relation.loaded?
+ assert_not_predicate relation, :loaded?
end
def test_last_on_loaded_relation_should_not_use_sql
@@ -598,7 +678,7 @@ class FinderTest < ActiveRecord::TestCase
def test_last_with_irreversible_order
assert_raises(ActiveRecord::IrreversibleOrderError) do
- Topic.order("coalesce(author_name, title)").last
+ Topic.order(Arel.sql("coalesce(author_name, title)")).last
end
end
@@ -610,12 +690,42 @@ class FinderTest < ActiveRecord::TestCase
assert_equal comments.limit(2).to_a.last(2), comments.limit(2).last(2)
assert_equal comments.limit(2).to_a.last(3), comments.limit(2).last(3)
+ assert_equal comments.offset(2).to_a.last, comments.offset(2).last
+ assert_equal comments.offset(2).to_a.last(2), comments.offset(2).last(2)
+ assert_equal comments.offset(2).to_a.last(3), comments.offset(2).last(3)
+
comments = comments.offset(1)
assert_equal comments.limit(2).to_a.last, comments.limit(2).last
assert_equal comments.limit(2).to_a.last(2), comments.limit(2).last(2)
assert_equal comments.limit(2).to_a.last(3), comments.limit(2).last(3)
end
+ def test_first_on_relation_with_limit_and_offset
+ post = posts("sti_comments")
+
+ comments = post.comments.order(id: :asc)
+ assert_equal comments.limit(2).to_a.first, comments.limit(2).first
+ assert_equal comments.limit(2).to_a.first(2), comments.limit(2).first(2)
+ assert_equal comments.limit(2).to_a.first(3), comments.limit(2).first(3)
+
+ assert_equal comments.offset(2).to_a.first, comments.offset(2).first
+ assert_equal comments.offset(2).to_a.first(2), comments.offset(2).first(2)
+ assert_equal comments.offset(2).to_a.first(3), comments.offset(2).first(3)
+
+ comments = comments.offset(1)
+ assert_equal comments.limit(2).to_a.first, comments.limit(2).first
+ assert_equal comments.limit(2).to_a.first(2), comments.limit(2).first(2)
+ assert_equal comments.limit(2).to_a.first(3), comments.limit(2).first(3)
+ end
+
+ def test_first_have_determined_order_by_default
+ expected = [companies(:second_client), companies(:another_client)]
+ clients = Client.where(name: expected.map(&:name))
+
+ assert_equal expected, clients.first(2)
+ assert_equal expected, clients.limit(5).first(2)
+ end
+
def test_take_and_first_and_last_with_integer_should_return_an_array
assert_kind_of Array, Topic.take(5)
assert_kind_of Array, Topic.first(5)
@@ -636,8 +746,8 @@ class FinderTest < ActiveRecord::TestCase
assert_raise(ActiveModel::MissingAttributeError) { topic.title? }
assert_nil topic.read_attribute("title")
assert_equal "David", topic.author_name
- assert !topic.attribute_present?("title")
- assert !topic.attribute_present?(:title)
+ assert_not topic.attribute_present?("title")
+ assert_not topic.attribute_present?(:title)
assert topic.attribute_present?("author_name")
assert_respond_to topic, "author_name"
end
@@ -721,11 +831,19 @@ class FinderTest < ActiveRecord::TestCase
assert_equal [1, 2, 6, 7, 8], Comment.where(id: [1..2, 6..8]).to_a.map(&:id).sort
end
+ def test_find_on_hash_conditions_with_open_ended_range
+ assert_equal [1, 2, 3], Comment.where(id: Float::INFINITY..3).to_a.map(&:id).sort
+ end
+
+ def test_find_on_hash_conditions_with_numeric_range_for_string
+ topic = Topic.create!(title: "12 Factor App")
+ assert_equal [topic], Topic.where(title: 10..2).to_a
+ end
+
def test_find_on_multiple_hash_conditions
assert Topic.where(author_name: "David", title: "The First Topic", replies_count: 1, approved: false).find(1)
assert_raise(ActiveRecord::RecordNotFound) { Topic.where(author_name: "David", title: "The First Topic", replies_count: 1, approved: true).find(1) }
assert_raise(ActiveRecord::RecordNotFound) { Topic.where(author_name: "David", title: "HHC", replies_count: 1, approved: false).find(1) }
- assert_raise(ActiveRecord::RecordNotFound) { Topic.where(author_name: "David", title: "The First Topic", replies_count: 1, approved: true).find(1) }
end
def test_condition_interpolation
@@ -778,6 +896,25 @@ class FinderTest < ActiveRecord::TestCase
assert_equal customers(:david), found_customer
end
+ def test_hash_condition_find_with_aggregate_having_three_mappings_array
+ david_address = customers(:david).address
+ zaphod_address = customers(:zaphod).address
+ barney_address = customers(:barney).address
+ assert_kind_of Address, david_address
+ assert_kind_of Address, zaphod_address
+ found_customers = Customer.where(address: [david_address, zaphod_address, barney_address])
+ assert_equal [customers(:david), customers(:zaphod), customers(:barney)], found_customers.sort_by(&:id)
+ end
+
+ def test_hash_condition_find_with_aggregate_having_one_mapping_array
+ david_balance = customers(:david).balance
+ zaphod_balance = customers(:zaphod).balance
+ assert_kind_of Money, david_balance
+ assert_kind_of Money, zaphod_balance
+ found_customers = Customer.where(balance: [david_balance, zaphod_balance])
+ assert_equal [customers(:david), customers(:zaphod)], found_customers.sort_by(&:id)
+ end
+
def test_hash_condition_find_with_aggregate_attribute_having_same_name_as_field_and_key_value_being_aggregate
gps_location = customers(:david).gps_location
assert_kind_of GpsLocation, gps_location
@@ -983,14 +1120,6 @@ class FinderTest < ActiveRecord::TestCase
assert_raise(ArgumentError) { Topic.find_by_title_and_author_name("The First Topic") }
end
- def test_find_last_with_offset
- devs = Developer.order("id")
-
- assert_equal devs[2], Developer.offset(2).first
- assert_equal devs[-3], Developer.offset(2).last
- assert_equal devs[-3], Developer.offset(2).order("id DESC").first
- end
-
def test_find_by_nil_attribute
topic = Topic.find_by_last_read nil
assert_not_nil topic
@@ -1006,16 +1135,6 @@ class FinderTest < ActiveRecord::TestCase
assert_raise(ActiveRecord::StatementInvalid) { Topic.find_by_sql "select 1 from badtable" }
end
- def test_find_all_with_join
- developers_on_project_one = Developer.
- joins("LEFT JOIN developers_projects ON developers.id = developers_projects.developer_id").
- where("project_id=1").to_a
- assert_equal 3, developers_on_project_one.length
- developer_names = developers_on_project_one.map(&:name)
- assert_includes developer_names, "David"
- assert_includes developer_names, "Jamis"
- end
-
def test_joins_dont_clobber_id
first = Firm.
joins("INNER JOIN companies clients ON clients.firm_id = companies.id").
@@ -1089,6 +1208,11 @@ class FinderTest < ActiveRecord::TestCase
order("author_addresses_authors.id DESC").limit(3).to_a.size
end
+ def test_find_with_eager_loading_collection_and_ordering_by_collection_primary_key
+ assert_equal Post.first, Post.eager_load(comments: :ratings).
+ order("posts.id, ratings.id, comments.id").first
+ end
+
def test_find_with_nil_inside_set_passed_for_one_attribute
client_of = Company.
where(client_of: [2, 1, nil],
@@ -1144,7 +1268,7 @@ class FinderTest < ActiveRecord::TestCase
e = assert_raises(ActiveRecord::RecordNotFound) do
model.find "Hello", "World!"
end
- assert_equal "Couldn't find all MercedesCars with 'name': (Hello, World!) (found 0 results, but was looking for 2)", e.message
+ assert_equal "Couldn't find all MercedesCars with 'name': (Hello, World!) (found 0 results, but was looking for 2).", e.message
end
end
@@ -1170,13 +1294,17 @@ class FinderTest < ActiveRecord::TestCase
assert_equal posts(:eager_other), Post.find_by("id = ?", posts(:eager_other).id)
end
+ test "find_by with range conditions returns the first matching record" do
+ assert_equal posts(:eager_other), Post.find_by(id: posts(:eager_other).id...posts(:misc_by_bob).id)
+ end
+
test "find_by returns nil if the record is missing" do
assert_nil Post.find_by("1 = 0")
end
test "find_by with associations" do
assert_equal authors(:david), Post.find_by(author: authors(:david)).author
- assert_equal authors(:mary) , Post.find_by(author: authors(:mary)).author
+ assert_equal authors(:mary), Post.find_by(author: authors(:mary)).author
end
test "find_by doesn't have implicit ordering" do
@@ -1225,6 +1353,34 @@ class FinderTest < ActiveRecord::TestCase
assert_equal tyre2, zyke.tyres.custom_find_by(id: tyre2.id)
end
+ test "#skip_query_cache! for #exists?" do
+ Topic.cache do
+ assert_queries(1) do
+ Topic.exists?
+ Topic.exists?
+ end
+
+ assert_queries(2) do
+ Topic.all.skip_query_cache!.exists?
+ Topic.all.skip_query_cache!.exists?
+ end
+ end
+ end
+
+ test "#skip_query_cache! for #exists? with a limited eager load" do
+ Topic.cache do
+ assert_queries(1) do
+ Topic.eager_load(:replies).limit(1).exists?
+ Topic.eager_load(:replies).limit(1).exists?
+ end
+
+ assert_queries(2) do
+ Topic.eager_load(:replies).limit(1).skip_query_cache!.exists?
+ Topic.eager_load(:replies).limit(1).skip_query_cache!.exists?
+ end
+ end
+ end
+
private
def table_with_custom_primary_key
yield(Class.new(Toy) do
diff --git a/activerecord/test/cases/fixture_set/file_test.rb b/activerecord/test/cases/fixture_set/file_test.rb
index 533edcc2e0..ff99988cb5 100644
--- a/activerecord/test/cases/fixture_set/file_test.rb
+++ b/activerecord/test/cases/fixture_set/file_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "tempfile"
diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb
index 51133e9495..c65523d8c1 100644
--- a/activerecord/test/cases/fixtures_test.rb
+++ b/activerecord/test/cases/fixtures_test.rb
@@ -1,4 +1,7 @@
+# frozen_string_literal: true
+
require "cases/helper"
+require "support/connection_helper"
require "models/admin"
require "models/admin/account"
require "models/admin/randomly_named_c1"
@@ -30,6 +33,8 @@ require "models/treasure"
require "tempfile"
class FixturesTest < ActiveRecord::TestCase
+ include ConnectionHelper
+
self.use_instantiated_fixtures = true
self.use_transactional_tests = false
@@ -54,6 +59,264 @@ class FixturesTest < ActiveRecord::TestCase
end
end
+ class InsertQuerySubscriber
+ attr_reader :events
+
+ def initialize
+ @events = []
+ end
+
+ def call(_, _, _, _, values)
+ @events << values[:sql] if values[:sql] =~ /INSERT/
+ end
+ end
+
+ if current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter)
+ def test_bulk_insert
+ begin
+ subscriber = InsertQuerySubscriber.new
+ subscription = ActiveSupport::Notifications.subscribe("sql.active_record", subscriber)
+ create_fixtures("bulbs")
+ assert_equal 1, subscriber.events.size, "It takes one INSERT query to insert two fixtures"
+ ensure
+ ActiveSupport::Notifications.unsubscribe(subscription)
+ end
+ end
+
+ def test_bulk_insert_multiple_table_with_a_multi_statement_query
+ subscriber = InsertQuerySubscriber.new
+ subscription = ActiveSupport::Notifications.subscribe("sql.active_record", subscriber)
+
+ create_fixtures("bulbs", "authors", "computers")
+
+ expected_sql = <<~EOS.chop
+ INSERT INTO #{ActiveRecord::Base.connection.quote_table_name("bulbs")} .*
+ INSERT INTO #{ActiveRecord::Base.connection.quote_table_name("authors")} .*
+ INSERT INTO #{ActiveRecord::Base.connection.quote_table_name("computers")} .*
+ EOS
+ assert_equal 1, subscriber.events.size
+ assert_match(/#{expected_sql}/, subscriber.events.first)
+ ensure
+ ActiveSupport::Notifications.unsubscribe(subscription)
+ end
+
+ def test_bulk_insert_with_a_multi_statement_query_raises_an_exception_when_any_insert_fails
+ require "models/aircraft"
+
+ assert_equal false, Aircraft.columns_hash["wheels_count"].null
+ fixtures = {
+ "aircraft" => [
+ { "name" => "working_aircrafts", "wheels_count" => 2 },
+ { "name" => "broken_aircrafts", "wheels_count" => nil },
+ ]
+ }
+
+ assert_no_difference "Aircraft.count" do
+ assert_raises(ActiveRecord::NotNullViolation) do
+ ActiveRecord::Base.connection.insert_fixtures_set(fixtures)
+ end
+ end
+ end
+
+ def test_bulk_insert_with_a_multi_statement_query_in_a_nested_transaction
+ fixtures = {
+ "traffic_lights" => [
+ { "location" => "US", "state" => ["NY"], "long_state" => ["a"] },
+ ]
+ }
+
+ assert_difference "TrafficLight.count" do
+ ActiveRecord::Base.transaction do
+ conn = ActiveRecord::Base.connection
+ assert_equal 1, conn.open_transactions
+ conn.insert_fixtures_set(fixtures)
+ assert_equal 1, conn.open_transactions
+ end
+ end
+ end
+ end
+
+ if current_adapter?(:Mysql2Adapter)
+ def test_bulk_insert_with_multi_statements_enabled
+ run_without_connection do |orig_connection|
+ ActiveRecord::Base.establish_connection(
+ orig_connection.merge(flags: %w[MULTI_STATEMENTS])
+ )
+
+ fixtures = {
+ "traffic_lights" => [
+ { "location" => "US", "state" => ["NY"], "long_state" => ["a"] },
+ ]
+ }
+
+ ActiveRecord::Base.connection.stub(:supports_set_server_option?, false) do
+ assert_nothing_raised do
+ conn = ActiveRecord::Base.connection
+ conn.execute("SELECT 1; SELECT 2;")
+ conn.raw_connection.abandon_results!
+ end
+
+ assert_difference "TrafficLight.count" do
+ ActiveRecord::Base.transaction do
+ conn = ActiveRecord::Base.connection
+ assert_equal 1, conn.open_transactions
+ conn.insert_fixtures_set(fixtures)
+ assert_equal 1, conn.open_transactions
+ end
+ end
+
+ assert_nothing_raised do
+ conn = ActiveRecord::Base.connection
+ conn.execute("SELECT 1; SELECT 2;")
+ conn.raw_connection.abandon_results!
+ end
+ end
+ end
+ end
+
+ def test_bulk_insert_with_multi_statements_disabled
+ run_without_connection do |orig_connection|
+ ActiveRecord::Base.establish_connection(
+ orig_connection.merge(flags: [])
+ )
+
+ fixtures = {
+ "traffic_lights" => [
+ { "location" => "US", "state" => ["NY"], "long_state" => ["a"] },
+ ]
+ }
+
+ ActiveRecord::Base.connection.stub(:supports_set_server_option?, false) do
+ assert_raises(ActiveRecord::StatementInvalid) do
+ conn = ActiveRecord::Base.connection
+ conn.execute("SELECT 1; SELECT 2;")
+ conn.raw_connection.abandon_results!
+ end
+
+ assert_difference "TrafficLight.count" do
+ conn = ActiveRecord::Base.connection
+ conn.insert_fixtures_set(fixtures)
+ end
+
+ assert_raises(ActiveRecord::StatementInvalid) do
+ conn = ActiveRecord::Base.connection
+ conn.execute("SELECT 1; SELECT 2;")
+ conn.raw_connection.abandon_results!
+ end
+ end
+ end
+ end
+
+ def test_insert_fixtures_set_raises_an_error_when_max_allowed_packet_is_smaller_than_fixtures_set_size
+ conn = ActiveRecord::Base.connection
+ mysql_margin = 2
+ packet_size = 1024
+ bytes_needed_to_have_a_1024_bytes_fixture = 858
+ fixtures = {
+ "traffic_lights" => [
+ { "location" => "US", "state" => ["NY"], "long_state" => ["a" * bytes_needed_to_have_a_1024_bytes_fixture] },
+ ]
+ }
+
+ conn.stub(:max_allowed_packet, packet_size - mysql_margin) do
+ error = assert_raises(ActiveRecord::ActiveRecordError) { conn.insert_fixtures_set(fixtures) }
+ assert_match(/Fixtures set is too large #{packet_size}\./, error.message)
+ end
+ end
+
+ def test_insert_fixture_set_when_max_allowed_packet_is_bigger_than_fixtures_set_size
+ conn = ActiveRecord::Base.connection
+ packet_size = 1024
+ fixtures = {
+ "traffic_lights" => [
+ { "location" => "US", "state" => ["NY"], "long_state" => ["a" * 51] },
+ ]
+ }
+
+ conn.stub(:max_allowed_packet, packet_size) do
+ assert_difference "TrafficLight.count" do
+ conn.insert_fixtures_set(fixtures)
+ end
+ end
+ end
+
+ def test_insert_fixtures_set_split_the_total_sql_into_two_chunks_smaller_than_max_allowed_packet
+ subscriber = InsertQuerySubscriber.new
+ subscription = ActiveSupport::Notifications.subscribe("sql.active_record", subscriber)
+ conn = ActiveRecord::Base.connection
+ packet_size = 1024
+ fixtures = {
+ "traffic_lights" => [
+ { "location" => "US", "state" => ["NY"], "long_state" => ["a" * 450] },
+ ],
+ "comments" => [
+ { "post_id" => 1, "body" => "a" * 450 },
+ ]
+ }
+
+ conn.stub(:max_allowed_packet, packet_size) do
+ conn.insert_fixtures_set(fixtures)
+
+ assert_equal 2, subscriber.events.size
+ assert_operator subscriber.events.first.bytesize, :<, packet_size
+ assert_operator subscriber.events.second.bytesize, :<, packet_size
+ end
+ ensure
+ ActiveSupport::Notifications.unsubscribe(subscription)
+ end
+
+ def test_insert_fixtures_set_concat_total_sql_into_a_single_packet_smaller_than_max_allowed_packet
+ subscriber = InsertQuerySubscriber.new
+ subscription = ActiveSupport::Notifications.subscribe("sql.active_record", subscriber)
+ conn = ActiveRecord::Base.connection
+ packet_size = 1024
+ fixtures = {
+ "traffic_lights" => [
+ { "location" => "US", "state" => ["NY"], "long_state" => ["a" * 200] },
+ ],
+ "comments" => [
+ { "post_id" => 1, "body" => "a" * 200 },
+ ]
+ }
+
+ conn.stub(:max_allowed_packet, packet_size) do
+ assert_difference ["TrafficLight.count", "Comment.count"], +1 do
+ conn.insert_fixtures_set(fixtures)
+ end
+ end
+ assert_equal 1, subscriber.events.size
+ ensure
+ ActiveSupport::Notifications.unsubscribe(subscription)
+ end
+ end
+
+ def test_auto_value_on_primary_key
+ fixtures = [
+ { "name" => "first", "wheels_count" => 2 },
+ { "name" => "second", "wheels_count" => 3 }
+ ]
+ conn = ActiveRecord::Base.connection
+ assert_nothing_raised do
+ conn.insert_fixtures_set({ "aircraft" => fixtures }, ["aircraft"])
+ end
+ result = conn.select_all("SELECT name, wheels_count FROM aircraft ORDER BY id")
+ assert_equal fixtures, result.to_a
+ end
+
+ def test_deprecated_insert_fixtures
+ fixtures = [
+ { "name" => "first", "wheels_count" => 2 },
+ { "name" => "second", "wheels_count" => 3 }
+ ]
+ conn = ActiveRecord::Base.connection
+ conn.delete("DELETE FROM aircraft")
+ assert_deprecated do
+ conn.insert_fixtures(fixtures, "aircraft")
+ end
+ result = conn.select_all("SELECT name, wheels_count FROM aircraft ORDER BY id")
+ assert_equal fixtures, result.to_a
+ end
+
def test_broken_yaml_exception
badyaml = Tempfile.new ["foo", ".yml"]
badyaml.write "a: : "
@@ -95,6 +358,24 @@ class FixturesTest < ActiveRecord::TestCase
assert_nil(topics["second"]["author_email_address"])
end
+ def test_no_args_returns_all
+ all_topics = topics
+ assert_equal 5, all_topics.length
+ assert_equal "The First Topic", all_topics.first["title"]
+ assert_equal 5, all_topics.last.id
+ end
+
+ def test_no_args_record_returns_all_without_array
+ all_binaries = binaries
+ assert_kind_of(Array, all_binaries)
+ assert_equal 2, binaries.length
+ end
+
+ def test_nil_raises
+ assert_raise(StandardError) { topics(nil) }
+ assert_raise(StandardError) { topics([nil]) }
+ end
+
def test_inserts
create_fixtures("topics")
first_row = ActiveRecord::Base.connection.select_one("SELECT * FROM topics WHERE author_name = 'David'")
@@ -202,8 +483,8 @@ class FixturesTest < ActiveRecord::TestCase
def test_nonexistent_fixture_file
nonexistent_fixture_path = FIXTURES_ROOT + "/imnothere"
- #sanity check to make sure that this file never exists
- assert Dir[nonexistent_fixture_path + "*"].empty?
+ # sanity check to make sure that this file never exists
+ assert_empty Dir[nonexistent_fixture_path + "*"]
assert_raise(Errno::ENOENT) do
ActiveRecord::FixtureSet.new(Account.connection, "companies", Company, nonexistent_fixture_path)
@@ -230,7 +511,12 @@ class FixturesTest < ActiveRecord::TestCase
e = assert_raise(ActiveRecord::Fixture::FixtureError) do
ActiveRecord::FixtureSet.create_fixtures(FIXTURES_ROOT + "/naked/yml", "parrots")
end
- assert_equal(%(table "parrots" has no column named "arrr".), e.message)
+
+ if current_adapter?(:SQLite3Adapter)
+ assert_equal(%(table "parrots" has no column named "arrr".), e.message)
+ else
+ assert_equal(%(table "parrots" has no columns named "arrr", "foobar".), e.message)
+ end
end
def test_yaml_file_with_symbol_columns
@@ -263,6 +549,7 @@ class FixturesTest < ActiveRecord::TestCase
data.force_encoding("ASCII-8BIT")
data.freeze
assert_equal data, @flowers.data
+ assert_equal data, @binary_helper.data
end
def test_serialized_fixtures
@@ -347,6 +634,7 @@ if Account.connection.respond_to?(:reset_pk_sequence!)
class FixturesResetPkSequenceTest < ActiveRecord::TestCase
fixtures :accounts
fixtures :companies
+ self.use_transactional_tests = false
def setup
@instances = [Account.new(credit_limit: 50), Company.new(name: "RoR Consulting"), Course.new(name: "Test")]
@@ -395,14 +683,14 @@ class FixturesWithoutInstantiationTest < ActiveRecord::TestCase
fixtures :topics, :developers, :accounts
def test_without_complete_instantiation
- assert !defined?(@first)
- assert !defined?(@topics)
- assert !defined?(@developers)
- assert !defined?(@accounts)
+ assert_not defined?(@first)
+ assert_not defined?(@topics)
+ assert_not defined?(@developers)
+ assert_not defined?(@accounts)
end
def test_fixtures_from_root_yml_without_instantiation
- assert !defined?(@unknown), "@unknown is not defined"
+ assert_not defined?(@unknown), "@unknown is not defined"
end
def test_visibility_of_accessor_method
@@ -437,7 +725,7 @@ class FixturesWithoutInstanceInstantiationTest < ActiveRecord::TestCase
fixtures :topics, :developers, :accounts
def test_without_instance_instantiation
- assert !defined?(@first), "@first is not defined"
+ assert_not defined?(@first), "@first is not defined"
end
end
@@ -636,44 +924,58 @@ class TransactionalFixturesOnConnectionNotification < ActiveRecord::TestCase
self.use_instantiated_fixtures = false
def test_transaction_created_on_connection_notification
- connection = stub(transaction_open?: false)
- connection.expects(:begin_transaction).with(joinable: false)
- pool = connection.stubs(:pool).returns(ActiveRecord::ConnectionAdapters::ConnectionPool.new(ActiveRecord::Base.connection_pool.spec))
- pool.stubs(:lock_thread=).with(false)
- fire_connection_notification(connection)
+ connection = Class.new do
+ attr_accessor :pool
+
+ def transaction_open?; end
+ def begin_transaction(*args); end
+ def rollback_transaction(*args); end
+ end.new
+
+ connection.pool = Class.new do
+ def lock_thread=(lock_thread); end
+ end.new
+
+ assert_called_with(connection, :begin_transaction, [joinable: false]) do
+ fire_connection_notification(connection)
+ end
end
def test_notification_established_transactions_are_rolled_back
- # Mocha is not thread-safe so define our own stub to test
connection = Class.new do
attr_accessor :rollback_transaction_called
attr_accessor :pool
+
def transaction_open?; true; end
def begin_transaction(*args); end
def rollback_transaction(*args)
@rollback_transaction_called = true
end
end.new
+
connection.pool = Class.new do
- def lock_thread=(lock_thread); false; end
+ def lock_thread=(lock_thread); end
end.new
+
fire_connection_notification(connection)
teardown_fixtures
+
assert(connection.rollback_transaction_called, "Expected <mock connection>#rollback_transaction to be called but was not")
end
private
def fire_connection_notification(connection)
- ActiveRecord::Base.connection_handler.stubs(:retrieve_connection).with("book").returns(connection)
- message_bus = ActiveSupport::Notifications.instrumenter
- payload = {
- spec_name: "book",
- config: nil,
- connection_id: connection.object_id
- }
+ assert_called_with(ActiveRecord::Base.connection_handler, :retrieve_connection, ["book"], returns: connection) do
+ message_bus = ActiveSupport::Notifications.instrumenter
+ payload = {
+ spec_name: "book",
+ config: nil,
+ connection_id: connection.object_id
+ }
- message_bus.instrument("!connection.active_record", payload) {}
+ message_bus.instrument("!connection.active_record", payload) {}
+ end
end
end
@@ -766,7 +1068,7 @@ end
class FasterFixturesTest < ActiveRecord::TestCase
self.use_transactional_tests = false
- fixtures :categories, :authors
+ fixtures :categories, :authors, :author_addresses
def load_extra_fixture(name)
fixture = create_fixtures(name).first
@@ -791,6 +1093,8 @@ class FasterFixturesTest < ActiveRecord::TestCase
end
class FoxyFixturesTest < ActiveRecord::TestCase
+ # Set to false to blow away fixtures cache and ensure our fixtures are loaded
+ self.use_transactional_tests = false
fixtures :parrots, :parrots_pirates, :pirates, :treasures, :mateys, :ships, :computers,
:developers, :"admin/accounts", :"admin/users", :live_parrots, :dead_parrots, :books
@@ -883,13 +1187,13 @@ class FoxyFixturesTest < ActiveRecord::TestCase
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)))
+ assert_not(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)))
+ assert_not(parrots(:polly).treasures.include?(treasures(:diamond)))
end
def test_supports_yaml_arrays
@@ -944,10 +1248,10 @@ class FoxyFixturesTest < ActiveRecord::TestCase
end
def test_resolves_enums
- assert books(:awdr).published?
- assert books(:awdr).read?
- assert books(:rfr).proposed?
- assert books(:ddd).published?
+ assert_predicate books(:awdr), :published?
+ assert_predicate books(:awdr), :read?
+ assert_predicate books(:rfr), :proposed?
+ assert_predicate books(:ddd), :published?
end
end
@@ -987,7 +1291,7 @@ class CustomNameForFixtureOrModelTest < ActiveRecord::TestCase
end
def test_table_name_is_defined_in_the_model
- assert_equal "randomly_named_table2", ActiveRecord::FixtureSet::all_loaded_fixtures["admin/randomly_named_a9"].table_name
+ assert_equal "randomly_named_table2", ActiveRecord::FixtureSet.all_loaded_fixtures["admin/randomly_named_a9"].table_name
assert_equal "randomly_named_table2", Admin::ClassNameThatDoesNotFollowCONVENTIONS1.table_name
end
end
diff --git a/activerecord/test/cases/forbidden_attributes_protection_test.rb b/activerecord/test/cases/forbidden_attributes_protection_test.rb
index ffa3f63e0d..101fa118c8 100644
--- a/activerecord/test/cases/forbidden_attributes_protection_test.rb
+++ b/activerecord/test/cases/forbidden_attributes_protection_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "active_support/core_ext/hash/indifferent_access"
diff --git a/activerecord/test/cases/habtm_destroy_order_test.rb b/activerecord/test/cases/habtm_destroy_order_test.rb
index 365d4576dd..b15e1b48c4 100644
--- a/activerecord/test/cases/habtm_destroy_order_test.rb
+++ b/activerecord/test/cases/habtm_destroy_order_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/lesson"
require "models/student"
@@ -13,7 +15,7 @@ class HabtmDestroyOrderTest < ActiveRecord::TestCase
sicp.destroy
end
end
- assert !sicp.destroyed?
+ assert_not_predicate sicp, :destroyed?
end
test "should not raise error if have foreign key in the join table" do
@@ -40,7 +42,7 @@ class HabtmDestroyOrderTest < ActiveRecord::TestCase
ben.lessons << sicp
ben.save!
ben.destroy
- assert !ben.reload.lessons.empty?
+ assert_not_empty ben.reload.lessons
ensure
# get rid of it so Student is still like it was
Student.reset_callbacks(:destroy)
@@ -56,6 +58,6 @@ class HabtmDestroyOrderTest < ActiveRecord::TestCase
assert_raises LessonError do
sicp.destroy
end
- assert !sicp.reload.students.empty?
+ assert_not_empty sicp.reload.students
end
end
diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb
index 5a3b8e3fb5..68be685e4b 100644
--- a/activerecord/test/cases/helper.rb
+++ b/activerecord/test/cases/helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "config"
require "stringio"
@@ -141,6 +143,8 @@ def load_schema
if File.exist?(adapter_specific_schema_file)
load adapter_specific_schema_file
end
+
+ ActiveRecord::FixtureSet.reset_cache
ensure
$stdout = original_stdout
end
@@ -179,5 +183,3 @@ module InTimeZone
ActiveRecord::Base.time_zone_aware_attributes = old_tz
end
end
-
-require "mocha/setup" # FIXME: stop using mocha
diff --git a/activerecord/test/cases/hot_compatibility_test.rb b/activerecord/test/cases/hot_compatibility_test.rb
index e107ff2362..e7778af55b 100644
--- a/activerecord/test/cases/hot_compatibility_test.rb
+++ b/activerecord/test/cases/hot_compatibility_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "support/connection_helper"
diff --git a/activerecord/test/cases/i18n_test.rb b/activerecord/test/cases/i18n_test.rb
index 7f03c5b23d..22981c142a 100644
--- a/activerecord/test/cases/i18n_test.rb
+++ b/activerecord/test/cases/i18n_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/topic"
require "models/reply"
diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb
index e570e9ac1d..3d3189900f 100644
--- a/activerecord/test/cases/inheritance_test.rb
+++ b/activerecord/test/cases/inheritance_test.rb
@@ -1,6 +1,9 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/author"
require "models/company"
+require "models/membership"
require "models/person"
require "models/post"
require "models/project"
@@ -29,7 +32,7 @@ end
class InheritanceTest < ActiveRecord::TestCase
include InheritanceTestHelper
- fixtures :companies, :projects, :subscribers, :accounts, :vegetables
+ fixtures :companies, :projects, :subscribers, :accounts, :vegetables, :memberships
def test_class_with_store_full_sti_class_returns_full_name
with_store_full_sti_class do
@@ -88,7 +91,6 @@ class InheritanceTest < ActiveRecord::TestCase
end
ActiveSupport::Dependencies.stub(:safe_constantize, proc { raise e }) do
-
exception = assert_raises NameError do
Company.send :compute_type, "InvalidModel"
end
@@ -127,56 +129,70 @@ class InheritanceTest < ActiveRecord::TestCase
end
def test_descends_from_active_record
- assert !ActiveRecord::Base.descends_from_active_record?
+ assert_not_predicate ActiveRecord::Base, :descends_from_active_record?
# Abstract subclass of AR::Base.
- assert LoosePerson.descends_from_active_record?
+ assert_predicate LoosePerson, :descends_from_active_record?
# Concrete subclass of an abstract class.
- assert LooseDescendant.descends_from_active_record?
+ assert_predicate LooseDescendant, :descends_from_active_record?
# Concrete subclass of AR::Base.
- assert TightPerson.descends_from_active_record?
+ assert_predicate TightPerson, :descends_from_active_record?
# Concrete subclass of a concrete class but has no type column.
- assert TightDescendant.descends_from_active_record?
+ assert_predicate TightDescendant, :descends_from_active_record?
# Concrete subclass of AR::Base.
- assert Post.descends_from_active_record?
+ assert_predicate Post, :descends_from_active_record?
+
+ # Concrete subclasses of a concrete class which has a type column.
+ assert_not_predicate StiPost, :descends_from_active_record?
+ assert_not_predicate SubStiPost, :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?
+ assert_not_predicate AbstractStiPost, :descends_from_active_record?
- # Concrete subclasses an abstract class which has a type column.
- assert !SubStiPost.descends_from_active_record?
+ # Concrete subclass of an abstract class which has a type column.
+ assert_not_predicate SubAbstractStiPost, :descends_from_active_record?
end
def test_company_descends_from_active_record
- assert !ActiveRecord::Base.descends_from_active_record?
+ assert_not_predicate 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"
+ assert_not Class.new(Company).descends_from_active_record?, "Company subclass should not descend from ActiveRecord::Base"
end
def test_abstract_class
- assert !ActiveRecord::Base.abstract_class?
- assert LoosePerson.abstract_class?
- assert !LooseDescendant.abstract_class?
+ assert_not_predicate ActiveRecord::Base, :abstract_class?
+ assert_predicate LoosePerson, :abstract_class?
+ assert_not_predicate LooseDescendant, :abstract_class?
end
def test_inheritance_base_class
assert_equal Post, Post.base_class
+ assert_predicate Post, :base_class?
assert_equal Post, SpecialPost.base_class
+ assert_not_predicate SpecialPost, :base_class?
assert_equal Post, StiPost.base_class
- assert_equal SubStiPost, SubStiPost.base_class
+ assert_not_predicate StiPost, :base_class?
+ assert_equal Post, SubStiPost.base_class
+ assert_not_predicate SubStiPost, :base_class?
+ assert_equal SubAbstractStiPost, SubAbstractStiPost.base_class
+ assert_predicate SubAbstractStiPost, :base_class?
end
def test_abstract_inheritance_base_class
assert_equal LoosePerson, LoosePerson.base_class
+ assert_predicate LoosePerson, :base_class?
assert_equal LooseDescendant, LooseDescendant.base_class
+ assert_predicate LooseDescendant, :base_class?
assert_equal TightPerson, TightPerson.base_class
+ assert_predicate TightPerson, :base_class?
assert_equal TightPerson, TightDescendant.base_class
+ assert_not_predicate TightDescendant, :base_class?
end
def test_base_class_activerecord_error
@@ -272,6 +288,21 @@ class InheritanceTest < ActiveRecord::TestCase
assert_equal Firm, firm.class
end
+ def test_where_new_with_subclass
+ firm = Company.where(type: "Firm").new
+ assert_equal Firm, firm.class
+ end
+
+ def test_where_create_with_subclass
+ firm = Company.where(type: "Firm").create(name: "Basecamp")
+ assert_equal Firm, firm.class
+ end
+
+ def test_where_create_bang_with_subclass
+ firm = Company.where(type: "Firm").create!(name: "Basecamp")
+ assert_equal Firm, firm.class
+ end
+
def test_new_with_abstract_class
e = assert_raises(NotImplementedError) do
AbstractCompany.new
@@ -294,6 +325,30 @@ class InheritanceTest < ActiveRecord::TestCase
assert_raise(ActiveRecord::SubclassNotFound) { Company.new(type: "Account") }
end
+ def test_where_new_with_invalid_type
+ assert_raise(ActiveRecord::SubclassNotFound) { Company.where(type: "InvalidType").new }
+ end
+
+ def test_where_new_with_unrelated_type
+ assert_raise(ActiveRecord::SubclassNotFound) { Company.where(type: "Account").new }
+ end
+
+ def test_where_create_with_invalid_type
+ assert_raise(ActiveRecord::SubclassNotFound) { Company.where(type: "InvalidType").create }
+ end
+
+ def test_where_create_with_unrelated_type
+ assert_raise(ActiveRecord::SubclassNotFound) { Company.where(type: "Account").create }
+ end
+
+ def test_where_create_bang_with_invalid_type
+ assert_raise(ActiveRecord::SubclassNotFound) { Company.where(type: "InvalidType").create! }
+ end
+
+ def test_where_create_bang_with_unrelated_type
+ assert_raise(ActiveRecord::SubclassNotFound) { Company.where(type: "Account").create! }
+ end
+
def test_new_with_unrelated_namespaced_type
without_store_full_sti_class do
e = assert_raises ActiveRecord::SubclassNotFound do
@@ -316,7 +371,7 @@ class InheritanceTest < ActiveRecord::TestCase
end
def test_new_with_autoload_paths
- path = File.expand_path("../../models/autoloadable", __FILE__)
+ path = File.expand_path("../models/autoloadable", __dir__)
ActiveSupport::Dependencies.autoload_paths << path
firm = Company.new(type: "ExtraFirm")
@@ -417,7 +472,8 @@ class InheritanceTest < ActiveRecord::TestCase
def test_eager_load_belongs_to_primary_key_quoting
con = Account.connection
- assert_sql(/#{con.quote_table_name('companies')}.#{con.quote_column_name('id')} = 1/) do
+ bind_param = Arel::Nodes::BindParam.new(nil)
+ assert_sql(/#{con.quote_table_name('companies')}\.#{con.quote_column_name('id')} = (?:#{Regexp.quote(bind_param.to_sql)}|1)/) do
Account.all.merge!(includes: :firm).find(1)
end
end
@@ -435,6 +491,10 @@ class InheritanceTest < ActiveRecord::TestCase
assert_nothing_raised { Company.of_first_firm }
assert_nothing_raised { Client.of_first_firm }
end
+
+ def test_inheritance_with_default_scope
+ assert_equal 1, SelectedMembership.count(:all)
+ end
end
class InheritanceComputeTypeTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/instrumentation_test.rb b/activerecord/test/cases/instrumentation_test.rb
new file mode 100644
index 0000000000..e6e8468757
--- /dev/null
+++ b/activerecord/test/cases/instrumentation_test.rb
@@ -0,0 +1,72 @@
+# frozen_string_literal: true
+
+require "cases/helper"
+require "models/book"
+
+module ActiveRecord
+ class InstrumentationTest < ActiveRecord::TestCase
+ def test_payload_name_on_load
+ Book.create(name: "test book")
+ subscriber = ActiveSupport::Notifications.subscribe("sql.active_record") do |*args|
+ event = ActiveSupport::Notifications::Event.new(*args)
+ if event.payload[:sql].match "SELECT"
+ assert_equal "Book Load", event.payload[:name]
+ end
+ end
+ Book.first
+ ensure
+ ActiveSupport::Notifications.unsubscribe(subscriber) if subscriber
+ end
+
+ def test_payload_name_on_create
+ subscriber = ActiveSupport::Notifications.subscribe("sql.active_record") do |*args|
+ event = ActiveSupport::Notifications::Event.new(*args)
+ if event.payload[:sql].match "INSERT"
+ assert_equal "Book Create", event.payload[:name]
+ end
+ end
+ Book.create(name: "test book")
+ ensure
+ ActiveSupport::Notifications.unsubscribe(subscriber) if subscriber
+ end
+
+ def test_payload_name_on_update
+ subscriber = ActiveSupport::Notifications.subscribe("sql.active_record") do |*args|
+ event = ActiveSupport::Notifications::Event.new(*args)
+ if event.payload[:sql].match "UPDATE"
+ assert_equal "Book Update", event.payload[:name]
+ end
+ end
+ book = Book.create(name: "test book")
+ book.update_attribute(:name, "new name")
+ ensure
+ ActiveSupport::Notifications.unsubscribe(subscriber) if subscriber
+ end
+
+ def test_payload_name_on_update_all
+ subscriber = ActiveSupport::Notifications.subscribe("sql.active_record") do |*args|
+ event = ActiveSupport::Notifications::Event.new(*args)
+ if event.payload[:sql].match "UPDATE"
+ assert_equal "Book Update All", event.payload[:name]
+ end
+ end
+ Book.create(name: "test book")
+ Book.update_all(name: "new name")
+ ensure
+ ActiveSupport::Notifications.unsubscribe(subscriber) if subscriber
+ end
+
+ def test_payload_name_on_destroy
+ subscriber = ActiveSupport::Notifications.subscribe("sql.active_record") do |*args|
+ event = ActiveSupport::Notifications::Event.new(*args)
+ if event.payload[:sql].match "DELETE"
+ assert_equal "Book Destroy", event.payload[:name]
+ end
+ end
+ book = Book.create(name: "test book")
+ book.destroy
+ ensure
+ ActiveSupport::Notifications.unsubscribe(subscriber) if subscriber
+ end
+ end
+end
diff --git a/activerecord/test/cases/integration_test.rb b/activerecord/test/cases/integration_test.rb
index d7aa091623..36cd63c4d4 100644
--- a/activerecord/test/cases/integration_test.rb
+++ b/activerecord/test/cases/integration_test.rb
@@ -1,3 +1,4 @@
+# frozen_string_literal: true
require "cases/helper"
require "models/company"
@@ -169,13 +170,65 @@ class IntegrationTest < ActiveRecord::TestCase
end
def test_named_timestamps_for_cache_key
- owner = owners(:blackbeard)
- assert_equal "owners/#{owner.id}-#{owner.happy_at.utc.to_s(:usec)}", owner.cache_key(:updated_at, :happy_at)
+ assert_deprecated do
+ owner = owners(:blackbeard)
+ assert_equal "owners/#{owner.id}-#{owner.happy_at.utc.to_s(:usec)}", owner.cache_key(:updated_at, :happy_at)
+ end
end
def test_cache_key_when_named_timestamp_is_nil
- owner = owners(:blackbeard)
- owner.happy_at = nil
- assert_equal "owners/#{owner.id}", owner.cache_key(:happy_at)
+ assert_deprecated do
+ owner = owners(:blackbeard)
+ owner.happy_at = nil
+ assert_equal "owners/#{owner.id}", owner.cache_key(:happy_at)
+ end
+ end
+
+ def test_cache_key_is_stable_with_versioning_on
+ Developer.cache_versioning = true
+
+ developer = Developer.first
+ first_key = developer.cache_key
+
+ developer.touch
+ second_key = developer.cache_key
+
+ assert_equal first_key, second_key
+ ensure
+ Developer.cache_versioning = false
+ end
+
+ def test_cache_version_changes_with_versioning_on
+ Developer.cache_versioning = true
+
+ developer = Developer.first
+ first_version = developer.cache_version
+
+ travel 10.seconds do
+ developer.touch
+ end
+
+ second_version = developer.cache_version
+
+ assert_not_equal first_version, second_version
+ ensure
+ Developer.cache_versioning = false
+ end
+
+ def test_cache_key_retains_version_when_custom_timestamp_is_used
+ Developer.cache_versioning = true
+
+ developer = Developer.first
+ first_key = developer.cache_key_with_version
+
+ travel 10.seconds do
+ developer.touch
+ end
+
+ second_key = developer.cache_key_with_version
+
+ assert_not_equal first_key, second_key
+ ensure
+ Developer.cache_versioning = false
end
end
diff --git a/activerecord/test/cases/invalid_connection_test.rb b/activerecord/test/cases/invalid_connection_test.rb
index 1367af2859..a1be9c2780 100644
--- a/activerecord/test/cases/invalid_connection_test.rb
+++ b/activerecord/test/cases/invalid_connection_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
if current_adapter?(:Mysql2Adapter)
diff --git a/activerecord/test/cases/invertible_migration_test.rb b/activerecord/test/cases/invertible_migration_test.rb
index cc3951e2ba..363beb4780 100644
--- a/activerecord/test/cases/invertible_migration_test.rb
+++ b/activerecord/test/cases/invertible_migration_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
class Horse < ActiveRecord::Base
@@ -159,6 +161,15 @@ module ActiveRecord
end
end
+ class UpOnlyMigration < SilentMigration
+ def change
+ add_column :horses, :oldie, :integer, default: 0
+ up_only { execute "update horses set oldie = 1" }
+ end
+ end
+
+ self.use_transactional_tests = false
+
setup do
@verbose_was, ActiveRecord::Migration.verbose = ActiveRecord::Migration.verbose, false
end
@@ -204,7 +215,7 @@ module ActiveRecord
migration = InvertibleMigration.new
migration.migrate :up
migration.migrate :down
- assert !migration.connection.table_exists?("horses")
+ assert_not migration.connection.table_exists?("horses")
end
def test_migrate_revert
@@ -212,11 +223,11 @@ module ActiveRecord
revert = InvertibleRevertMigration.new
migration.migrate :up
revert.migrate :up
- assert !migration.connection.table_exists?("horses")
+ assert_not migration.connection.table_exists?("horses")
revert.migrate :down
assert migration.connection.table_exists?("horses")
migration.migrate :down
- assert !migration.connection.table_exists?("horses")
+ assert_not migration.connection.table_exists?("horses")
end
def test_migrate_revert_by_part
@@ -230,12 +241,12 @@ module ActiveRecord
}
migration.migrate :up
assert_equal [:both, :up], received
- assert !migration.connection.table_exists?("horses")
+ assert_not migration.connection.table_exists?("horses")
assert migration.connection.table_exists?("new_horses")
migration.migrate :down
assert_equal [:both, :up, :both, :down], received
assert migration.connection.table_exists?("horses")
- assert !migration.connection.table_exists?("new_horses")
+ assert_not migration.connection.table_exists?("new_horses")
end
def test_migrate_revert_whole_migration
@@ -244,11 +255,11 @@ module ActiveRecord
revert = RevertWholeMigration.new(klass)
migration.migrate :up
revert.migrate :up
- assert !migration.connection.table_exists?("horses")
+ assert_not migration.connection.table_exists?("horses")
revert.migrate :down
assert migration.connection.table_exists?("horses")
migration.migrate :down
- assert !migration.connection.table_exists?("horses")
+ assert_not migration.connection.table_exists?("horses")
end
end
@@ -257,7 +268,7 @@ module ActiveRecord
revert.migrate :down
assert revert.connection.table_exists?("horses")
revert.migrate :up
- assert !revert.connection.table_exists?("horses")
+ assert_not revert.connection.table_exists?("horses")
end
def test_migrate_revert_change_column_default
@@ -293,6 +304,8 @@ module ActiveRecord
migration2.migrate(:down)
assert_equal false, Horse.connection.extension_enabled?("hstore")
+ ensure
+ enable_extension!("hstore", ActiveRecord::Base.connection)
end
end
@@ -328,7 +341,7 @@ module ActiveRecord
def test_legacy_down
LegacyMigration.migrate :up
LegacyMigration.migrate :down
- assert !ActiveRecord::Base.connection.table_exists?("horses"), "horses should not exist"
+ assert_not ActiveRecord::Base.connection.table_exists?("horses"), "horses should not exist"
end
def test_up
@@ -339,7 +352,7 @@ module ActiveRecord
def test_down
LegacyMigration.up
LegacyMigration.down
- assert !ActiveRecord::Base.connection.table_exists?("horses"), "horses should not exist"
+ assert_not ActiveRecord::Base.connection.table_exists?("horses"), "horses should not exist"
end
def test_migrate_down_with_table_name_prefix
@@ -348,7 +361,7 @@ module ActiveRecord
migration = InvertibleMigration.new
migration.migrate(:up)
assert_nothing_raised { migration.migrate(:down) }
- assert !ActiveRecord::Base.connection.table_exists?("p_horses_s"), "p_horses_s should not exist"
+ assert_not ActiveRecord::Base.connection.table_exists?("p_horses_s"), "p_horses_s should not exist"
ensure
ActiveRecord::Base.table_name_prefix = ActiveRecord::Base.table_name_suffix = ""
end
@@ -370,9 +383,27 @@ module ActiveRecord
connection = ActiveRecord::Base.connection
assert connection.index_exists?(:horses, :content),
"index on content should exist"
- assert !connection.index_exists?(:horses, :content, name: "horses_index_named"),
+ assert_not connection.index_exists?(:horses, :content, name: "horses_index_named"),
"horses_index_named index should not exist"
end
end
+
+ def test_up_only
+ InvertibleMigration.new.migrate(:up)
+ horse1 = Horse.create
+ # populates existing horses with oldie = 1 but new ones have default 0
+ UpOnlyMigration.new.migrate(:up)
+ Horse.reset_column_information
+ horse1.reload
+ horse2 = Horse.create
+
+ assert 1, horse1.oldie # created before migration
+ assert 0, horse2.oldie # created after migration
+
+ UpOnlyMigration.new.migrate(:down) # should be no error
+ connection = ActiveRecord::Base.connection
+ assert_not connection.column_exists?(:horses, :oldie)
+ Horse.reset_column_information
+ end
end
end
diff --git a/activerecord/test/cases/json_attribute_test.rb b/activerecord/test/cases/json_attribute_test.rb
new file mode 100644
index 0000000000..afc39d0420
--- /dev/null
+++ b/activerecord/test/cases/json_attribute_test.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+require "cases/helper"
+require "cases/json_shared_test_cases"
+
+class JsonAttributeTest < ActiveRecord::TestCase
+ include JSONSharedTestCases
+ self.use_transactional_tests = false
+
+ class JsonDataTypeOnText < ActiveRecord::Base
+ self.table_name = "json_data_type"
+
+ attribute :payload, :json
+ attribute :settings, :json
+
+ store_accessor :settings, :resolution
+ end
+
+ def setup
+ super
+ @connection.create_table("json_data_type") do |t|
+ t.string "payload"
+ t.string "settings"
+ end
+ end
+
+ private
+ def column_type
+ :string
+ end
+
+ def klass
+ JsonDataTypeOnText
+ end
+end
diff --git a/activerecord/test/cases/json_serialization_test.rb b/activerecord/test/cases/json_serialization_test.rb
index 5a1d066aef..82cf281cff 100644
--- a/activerecord/test/cases/json_serialization_test.rb
+++ b/activerecord/test/cases/json_serialization_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/contact"
require "models/post"
@@ -160,15 +162,13 @@ class JsonSerializationTest < ActiveRecord::TestCase
end
def test_serializable_hash_should_not_modify_options_in_argument
- options = { only: :name }
- @contact.serializable_hash(options)
-
- assert_nil options[:except]
+ options = { only: :name }.freeze
+ assert_nothing_raised { @contact.serializable_hash(options) }
end
end
class DatabaseConnectedJsonEncodingTest < ActiveRecord::TestCase
- fixtures :authors, :posts, :comments, :tags, :taggings
+ fixtures :authors, :author_addresses, :posts, :comments, :tags, :taggings
include JsonSerializationHelpers
@@ -252,7 +252,7 @@ class DatabaseConnectedJsonEncodingTest < ActiveRecord::TestCase
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_not_respond_to @david.posts.first, :favorite_quote
assert_match %r{"favorite_quote":"Constraints are liberating"}, json
assert_equal 1, %r{"favorite_quote":}.match(json).size
end
diff --git a/activerecord/test/cases/json_shared_test_cases.rb b/activerecord/test/cases/json_shared_test_cases.rb
new file mode 100644
index 0000000000..9b79803503
--- /dev/null
+++ b/activerecord/test/cases/json_shared_test_cases.rb
@@ -0,0 +1,269 @@
+# frozen_string_literal: true
+
+require "support/schema_dumping_helper"
+
+module JSONSharedTestCases
+ include SchemaDumpingHelper
+
+ class JsonDataType < ActiveRecord::Base
+ self.table_name = "json_data_type"
+
+ store_accessor :settings, :resolution
+ end
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+ end
+
+ def teardown
+ @connection.drop_table :json_data_type, if_exists: true
+ klass.reset_column_information
+ end
+
+ def test_column
+ column = klass.columns_hash["payload"]
+ assert_equal column_type, column.type
+ assert_type_match column_type, column.sql_type
+
+ type = klass.type_for_attribute("payload")
+ assert_not_predicate type, :binary?
+ end
+
+ def test_change_table_supports_json
+ @connection.change_table("json_data_type") do |t|
+ t.public_send column_type, "users"
+ end
+ klass.reset_column_information
+ column = klass.columns_hash["users"]
+ assert_equal column_type, column.type
+ assert_type_match column_type, column.sql_type
+ end
+
+ def test_schema_dumping
+ output = dump_table_schema("json_data_type")
+ assert_match(/t\.#{column_type}\s+"settings"/, output)
+ end
+
+ def test_cast_value_on_write
+ x = klass.new(payload: { "string" => "foo", :symbol => :bar })
+ assert_equal({ "string" => "foo", :symbol => :bar }, x.payload_before_type_cast)
+ assert_equal({ "string" => "foo", "symbol" => "bar" }, x.payload)
+ x.save!
+ assert_equal({ "string" => "foo", "symbol" => "bar" }, x.reload.payload)
+ end
+
+ def test_type_cast_json
+ type = klass.type_for_attribute("payload")
+
+ data = '{"a_key":"a_value"}'
+ hash = type.deserialize(data)
+ assert_equal({ "a_key" => "a_value" }, hash)
+ assert_equal({ "a_key" => "a_value" }, type.deserialize(data))
+
+ assert_equal({}, type.deserialize("{}"))
+ assert_equal({ "key" => nil }, type.deserialize('{"key": null}'))
+ assert_equal({ "c" => "}", '"a"' => 'b "a b' }, type.deserialize(%q({"c":"}", "\"a\"":"b \"a b"})))
+ end
+
+ def test_rewrite
+ @connection.execute(insert_statement_per_database('{"k":"v"}'))
+ x = klass.first
+ x.payload = { '"a\'' => "b" }
+ assert x.save!
+ end
+
+ def test_select
+ @connection.execute(insert_statement_per_database('{"k":"v"}'))
+ x = klass.first
+ assert_equal({ "k" => "v" }, x.payload)
+ end
+
+ def test_select_multikey
+ @connection.execute(insert_statement_per_database('{"k1":"v1", "k2":"v2", "k3":[1,2,3]}'))
+ x = klass.first
+ assert_equal({ "k1" => "v1", "k2" => "v2", "k3" => [1, 2, 3] }, x.payload)
+ end
+
+ def test_null_json
+ @connection.execute(insert_statement_per_database("null"))
+ x = klass.first
+ assert_nil(x.payload)
+ end
+
+ def test_select_nil_json_after_create
+ json = klass.create!(payload: nil)
+ x = klass.where(payload: nil).first
+ assert_equal(json, x)
+ end
+
+ def test_select_nil_json_after_update
+ json = klass.create!(payload: "foo")
+ x = klass.where(payload: nil).first
+ assert_nil(x)
+
+ json.update(payload: nil)
+ x = klass.where(payload: nil).first
+ assert_equal(json.reload, x)
+ end
+
+ def test_select_array_json_value
+ @connection.execute(insert_statement_per_database('["v0",{"k1":"v1"}]'))
+ x = klass.first
+ assert_equal(["v0", { "k1" => "v1" }], x.payload)
+ end
+
+ def test_rewrite_array_json_value
+ @connection.execute(insert_statement_per_database('["v0",{"k1":"v1"}]'))
+ x = klass.first
+ x.payload = ["v1", { "k2" => "v2" }, "v3"]
+ assert x.save!
+ end
+
+ def test_with_store_accessors
+ x = klass.new(resolution: "320×480")
+ assert_equal "320×480", x.resolution
+
+ x.save!
+ x = klass.first
+ assert_equal "320×480", x.resolution
+
+ x.resolution = "640×1136"
+ x.save!
+
+ x = klass.first
+ assert_equal "640×1136", x.resolution
+ end
+
+ def test_duplication_with_store_accessors
+ x = klass.new(resolution: "320×480")
+ assert_equal "320×480", x.resolution
+
+ y = x.dup
+ assert_equal "320×480", y.resolution
+ end
+
+ def test_yaml_round_trip_with_store_accessors
+ x = klass.new(resolution: "320×480")
+ assert_equal "320×480", x.resolution
+
+ y = YAML.load(YAML.dump(x))
+ assert_equal "320×480", y.resolution
+ end
+
+ def test_changes_in_place
+ json = klass.new
+ assert_not_predicate json, :changed?
+
+ json.payload = { "one" => "two" }
+ assert_predicate json, :changed?
+ assert_predicate json, :payload_changed?
+
+ json.save!
+ assert_not_predicate json, :changed?
+
+ json.payload["three"] = "four"
+ assert_predicate json, :payload_changed?
+
+ json.save!
+ json.reload
+
+ assert_equal({ "one" => "two", "three" => "four" }, json.payload)
+ assert_not_predicate json, :changed?
+ end
+
+ def test_changes_in_place_ignores_key_order
+ json = klass.new
+ assert_not_predicate json, :changed?
+
+ json.payload = { "three" => "four", "one" => "two" }
+ json.save!
+ json.reload
+
+ json.payload = { "three" => "four", "one" => "two" }
+ assert_not_predicate json, :changed?
+
+ json.payload = [{ "three" => "four", "one" => "two" }, { "seven" => "eight", "five" => "six" }]
+ json.save!
+ json.reload
+
+ json.payload = [{ "three" => "four", "one" => "two" }, { "seven" => "eight", "five" => "six" }]
+ assert_not_predicate json, :changed?
+ end
+
+ def test_changes_in_place_with_ruby_object
+ time = Time.now.utc
+ json = klass.create!(payload: time)
+
+ json.reload
+ assert_not_predicate json, :changed?
+
+ json.payload = time
+ assert_not_predicate json, :changed?
+ end
+
+ def test_assigning_string_literal
+ json = klass.create!(payload: "foo")
+ assert_equal "foo", json.payload
+ end
+
+ def test_assigning_number
+ json = klass.create!(payload: 1.234)
+ assert_equal 1.234, json.payload
+ end
+
+ def test_assigning_boolean
+ json = klass.create!(payload: true)
+ assert_equal true, json.payload
+ end
+
+ def test_not_compatible_with_serialize_json
+ new_klass = Class.new(klass) do
+ serialize :payload, JSON
+ end
+ assert_raises(ActiveRecord::AttributeMethods::Serialization::ColumnNotSerializableError) do
+ new_klass.new
+ end
+ end
+
+ class MySettings
+ def initialize(hash); @hash = hash end
+ def to_hash; @hash end
+ def self.load(hash); new(hash) end
+ def self.dump(object); object.to_hash end
+ end
+
+ def test_json_with_serialized_attributes
+ new_klass = Class.new(klass) do
+ serialize :settings, MySettings
+ end
+
+ new_klass.create!(settings: MySettings.new("one" => "two"))
+ record = new_klass.first
+
+ assert_instance_of MySettings, record.settings
+ assert_equal({ "one" => "two" }, record.settings.to_hash)
+
+ record.settings = MySettings.new("three" => "four")
+ record.save!
+
+ assert_equal({ "three" => "four" }, record.reload.settings.to_hash)
+ end
+
+ private
+ def klass
+ JsonDataType
+ end
+
+ def assert_type_match(type, sql_type)
+ native_type = ActiveRecord::Base.connection.native_database_types[type][:name]
+ assert_match %r(\A#{native_type}\b), sql_type
+ end
+
+ def insert_statement_per_database(values)
+ if current_adapter?(:OracleAdapter)
+ "insert into json_data_type (id, payload) VALUES (json_data_type_seq.nextval, '#{values}')"
+ else
+ "insert into json_data_type (payload) VALUES ('#{values}')"
+ end
+ end
+end
diff --git a/activerecord/test/cases/legacy_configurations_test.rb b/activerecord/test/cases/legacy_configurations_test.rb
new file mode 100644
index 0000000000..c36feb5116
--- /dev/null
+++ b/activerecord/test/cases/legacy_configurations_test.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+require "cases/helper"
+
+module ActiveRecord
+ class LegacyConfigurationsTest < ActiveRecord::TestCase
+ def test_can_turn_configurations_into_a_hash
+ assert ActiveRecord::Base.configurations.to_h.is_a?(Hash), "expected to be a hash but was not."
+ assert_equal ["arunit", "arunit2", "arunit_without_prepared_statements"].sort, ActiveRecord::Base.configurations.to_h.keys.sort
+ end
+
+ def test_each_is_deprecated
+ assert_deprecated do
+ ActiveRecord::Base.configurations.each do |db_config|
+ assert_equal "primary", db_config.spec_name
+ end
+ end
+ end
+
+ def test_first_is_deprecated
+ assert_deprecated do
+ db_config = ActiveRecord::Base.configurations.first
+ assert_equal "arunit", db_config.env_name
+ assert_equal "primary", db_config.spec_name
+ end
+ end
+
+ def test_fetch_is_deprecated
+ assert_deprecated do
+ db_config = ActiveRecord::Base.configurations.fetch("arunit").first
+ assert_equal "arunit", db_config.env_name
+ assert_equal "primary", db_config.spec_name
+ end
+ end
+
+ def test_values_are_deprecated
+ config_hashes = ActiveRecord::Base.configurations.configurations.map(&:config)
+ assert_deprecated do
+ assert_equal config_hashes, ActiveRecord::Base.configurations.values
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb
index 23095618a4..33bd74e114 100644
--- a/activerecord/test/cases/locking_test.rb
+++ b/activerecord/test/cases/locking_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "thread"
require "cases/helper"
require "models/person"
@@ -13,6 +15,7 @@ require "models/bulb"
require "models/engine"
require "models/wheel"
require "models/treasure"
+require "models/frog"
class LockWithoutDefault < ActiveRecord::Base; end
@@ -67,8 +70,8 @@ class OptimisticLockingTest < ActiveRecord::TestCase
assert_raise(ActiveRecord::StaleObjectError) { s2.destroy }
assert s1.destroy
- assert s1.frozen?
- assert s1.destroyed?
+ assert_predicate s1, :frozen?
+ assert_predicate s1, :destroyed?
assert_raises(ActiveRecord::RecordNotFound) { StringKeyObject.find("record1") }
end
@@ -102,8 +105,8 @@ class OptimisticLockingTest < ActiveRecord::TestCase
assert_raises(ActiveRecord::StaleObjectError) { p2.destroy }
assert p1.destroy
- assert p1.frozen?
- assert p1.destroyed?
+ assert_predicate p1, :frozen?
+ assert_predicate p1, :destroyed?
assert_raises(ActiveRecord::RecordNotFound) { Person.find(1) }
end
@@ -167,6 +170,12 @@ class OptimisticLockingTest < ActiveRecord::TestCase
assert_equal 0, p1.lock_version
end
+ def test_lock_new_when_explicitly_passing_value
+ p1 = Person.new(first_name: "Douglas Adams", lock_version: 42)
+ p1.save!
+ assert_equal 42, p1.lock_version
+ end
+
def test_touch_existing_lock
p1 = Person.find(1)
assert_equal 0, p1.lock_version
@@ -186,6 +195,58 @@ class OptimisticLockingTest < ActiveRecord::TestCase
end
end
+ def test_update_with_dirty_primary_key
+ assert_raises(ActiveRecord::RecordNotUnique) do
+ person = Person.find(1)
+ person.id = 2
+ person.save!
+ end
+
+ person = Person.find(1)
+ person.id = 42
+ person.save!
+
+ assert Person.find(42)
+ assert_raises(ActiveRecord::RecordNotFound) do
+ Person.find(1)
+ end
+ end
+
+ def test_delete_with_dirty_primary_key
+ person = Person.find(1)
+ person.id = 2
+ person.delete
+
+ assert Person.find(2)
+ assert_raises(ActiveRecord::RecordNotFound) do
+ Person.find(1)
+ end
+ end
+
+ def test_destroy_with_dirty_primary_key
+ person = Person.find(1)
+ person.id = 2
+ person.destroy
+
+ assert Person.find(2)
+ assert_raises(ActiveRecord::RecordNotFound) do
+ Person.find(1)
+ end
+ end
+
+ def test_explicit_update_lock_column_raise_error
+ person = Person.find(1)
+
+ assert_raises(ActiveRecord::StaleObjectError) do
+ person.first_name = "Douglas Adams"
+ person.lock_version = 42
+
+ assert_predicate person, :lock_version_changed?
+
+ person.save
+ end
+ end
+
def test_lock_column_name_existing
t1 = LegacyThing.find(1)
t2 = LegacyThing.find(1)
@@ -225,10 +286,33 @@ class OptimisticLockingTest < ActiveRecord::TestCase
assert_equal 0, t1.lock_version_before_type_cast
end
+ def test_touch_existing_lock_without_default_should_work_with_null_in_the_database
+ ActiveRecord::Base.connection.execute("INSERT INTO lock_without_defaults(title) VALUES('title1')")
+ t1 = LockWithoutDefault.last
+
+ assert_equal 0, t1.lock_version
+ assert_nil t1.lock_version_before_type_cast
+
+ t1.touch
+
+ assert_equal 1, t1.lock_version
+ end
+
+ def test_touch_stale_object_with_lock_without_default
+ t1 = LockWithoutDefault.create!(title: "title1")
+ stale_object = LockWithoutDefault.find(t1.id)
+
+ t1.update!(title: "title2")
+
+ assert_raises(ActiveRecord::StaleObjectError) do
+ stale_object.touch
+ end
+ end
+
def test_lock_without_default_should_work_with_null_in_the_database
ActiveRecord::Base.connection.execute("INSERT INTO lock_without_defaults(title) VALUES('title1')")
t1 = LockWithoutDefault.last
- t2 = LockWithoutDefault.last
+ t2 = LockWithoutDefault.find(t1.id)
assert_equal 0, t1.lock_version
assert_nil t1.lock_version_before_type_cast
@@ -247,17 +331,6 @@ class OptimisticLockingTest < ActiveRecord::TestCase
assert_equal "new title2", t2.title
end
- def test_lock_without_default_should_update_with_lock_col
- t1 = LockWithoutDefault.create(title: "title1", lock_version: 6)
-
- assert_equal 6, t1.lock_version
-
- t1.update(lock_version: 0)
- t1.reload
-
- assert_equal 0, t1.lock_version
- end
-
def test_lock_without_default_queries_count
t1 = LockWithoutDefault.create(title: "title1")
@@ -270,12 +343,6 @@ class OptimisticLockingTest < ActiveRecord::TestCase
assert_equal "title2", t1.title
assert_equal 1, t1.lock_version
- assert_queries(1) { t1.update(title: "title3", lock_version: 6) }
-
- t1.reload
- assert_equal "title3", t1.title
- assert_equal 6, t1.lock_version
-
t2 = LockWithoutDefault.new(title: "title1")
assert_queries(1) { t2.save! }
@@ -302,7 +369,7 @@ class OptimisticLockingTest < ActiveRecord::TestCase
ActiveRecord::Base.connection.execute("INSERT INTO lock_without_defaults_cust(title) VALUES('title1')")
t1 = LockWithCustomColumnWithoutDefault.last
- t2 = LockWithCustomColumnWithoutDefault.last
+ t2 = LockWithCustomColumnWithoutDefault.find(t1.id)
assert_equal 0, t1.custom_lock_version
assert_nil t1.custom_lock_version_before_type_cast
@@ -321,17 +388,6 @@ class OptimisticLockingTest < ActiveRecord::TestCase
assert_equal "new title2", t2.title
end
- def test_lock_with_custom_column_without_default_should_update_with_lock_col
- t1 = LockWithCustomColumnWithoutDefault.create(title: "title1", custom_lock_version: 6)
-
- assert_equal 6, t1.custom_lock_version
-
- t1.update(custom_lock_version: 0)
- t1.reload
-
- assert_equal 0, t1.custom_lock_version
- end
-
def test_lock_with_custom_column_without_default_queries_count
t1 = LockWithCustomColumnWithoutDefault.create(title: "title1")
@@ -344,12 +400,6 @@ class OptimisticLockingTest < ActiveRecord::TestCase
assert_equal "title2", t1.title
assert_equal 1, t1.custom_lock_version
- assert_queries(1) { t1.update(title: "title3", custom_lock_version: 6) }
-
- t1.reload
- assert_equal "title3", t1.title
- assert_equal 6, t1.custom_lock_version
-
t2 = LockWithCustomColumnWithoutDefault.new(title: "title1")
assert_queries(1) { t2.save! }
@@ -389,25 +439,65 @@ class OptimisticLockingTest < ActiveRecord::TestCase
end
end
+ def test_counter_cache_with_touch_and_lock_version
+ car = Car.create!
+
+ assert_equal 0, car.wheels_count
+ assert_equal 0, car.lock_version
+
+ previously_updated_at = car.updated_at
+ previously_wheels_owned_at = car.wheels_owned_at
+ travel(1.second) do
+ Wheel.create!(wheelable: car)
+ end
+
+ assert_equal 1, car.reload.wheels_count
+ assert_equal 1, car.lock_version
+ assert_operator previously_updated_at, :<, car.updated_at
+ assert_operator previously_wheels_owned_at, :<, car.wheels_owned_at
+
+ previously_updated_at = car.updated_at
+ previously_wheels_owned_at = car.wheels_owned_at
+ travel(2.second) do
+ car.wheels.first.update(size: 42)
+ end
+
+ assert_equal 1, car.reload.wheels_count
+ assert_equal 2, car.lock_version
+ assert_operator previously_updated_at, :<, car.updated_at
+ assert_operator previously_wheels_owned_at, :<, car.wheels_owned_at
+
+ previously_updated_at = car.updated_at
+ previously_wheels_owned_at = car.wheels_owned_at
+ travel(3.second) do
+ car.wheels.first.destroy!
+ end
+
+ assert_equal 0, car.reload.wheels_count
+ assert_equal 3, car.lock_version
+ assert_operator previously_updated_at, :<, car.updated_at
+ assert_operator previously_wheels_owned_at, :<, car.wheels_owned_at
+ end
+
def test_polymorphic_destroy_with_dependencies_and_lock_version
car = Car.create!
assert_difference "car.wheels.count" do
- car.wheels << Wheel.create!
+ car.wheels.create
end
assert_difference "car.wheels.count", -1 do
car.reload.destroy
end
- assert car.destroyed?
+ assert_predicate car, :destroyed?
end
def test_removing_has_and_belongs_to_many_associations_upon_destroy
p = RichPerson.create! first_name: "Jon"
p.treasures.create!
- assert !p.treasures.empty?
+ assert_not_empty p.treasures
p.destroy
- assert p.treasures.empty?
- assert RichPerson.connection.select_all("SELECT * FROM peoples_treasures WHERE rich_person_id = 1").empty?
+ assert_empty p.treasures
+ assert_empty RichPerson.connection.select_all("SELECT * FROM peoples_treasures WHERE rich_person_id = 1")
end
def test_yaml_dumping_with_lock_column
@@ -468,6 +558,31 @@ class OptimisticLockingWithSchemaChangeTest < ActiveRecord::TestCase
PersonalLegacyThing.reset_column_information
end
+ def test_destroy_existing_object_with_locking_column_value_null_in_the_database
+ ActiveRecord::Base.connection.execute("INSERT INTO lock_without_defaults(title) VALUES('title1')")
+ t1 = LockWithoutDefault.last
+
+ assert_equal 0, t1.lock_version
+ assert_nil t1.lock_version_before_type_cast
+
+ t1.destroy
+
+ assert_predicate t1, :destroyed?
+ end
+
+ def test_destroy_stale_object
+ t1 = LockWithoutDefault.create!(title: "title1")
+ stale_object = LockWithoutDefault.find(t1.id)
+
+ t1.update!(title: "title2")
+
+ assert_raises(ActiveRecord::StaleObjectError) do
+ stale_object.destroy!
+ end
+
+ assert_not_predicate stale_object, :destroyed?
+ end
+
private
def add_counter_column_to(model, col = "test_count")
@@ -530,17 +645,27 @@ unless in_memory_db?
end
end
- # Locking a record reloads it.
- def test_sane_lock_method
+ def test_lock_does_not_raise_when_the_object_is_not_dirty
+ person = Person.find 1
assert_nothing_raised do
- Person.transaction do
- person = Person.find 1
- old, person.first_name = person.first_name, "fooman"
- # Locking a dirty record is deprecated
- assert_deprecated do
- person.lock!
- end
- assert_equal old, person.first_name
+ person.lock!
+ end
+ end
+
+ def test_lock_raises_when_the_record_is_dirty
+ person = Person.find 1
+ person.first_name = "fooman"
+ assert_raises(RuntimeError) do
+ person.lock!
+ end
+ end
+
+ def test_locking_in_after_save_callback
+ assert_nothing_raised do
+ frog = ::Frog.create(name: "Old Frog")
+ frog.name = "New Frog"
+ assert_not_deprecated do
+ frog.save!
end
end
end
@@ -576,14 +701,12 @@ unless in_memory_db?
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
-
- private
+ def test_no_locks_no_wait
+ first, second = duel { Person.find 1 }
+ assert first.end > second.end
+ end
+ private
def duel(zzz = 5)
t0, t1, t2, t3 = nil, nil, nil, nil
@@ -611,6 +734,5 @@ unless in_memory_db?
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/log_subscriber_test.rb b/activerecord/test/cases/log_subscriber_test.rb
index 90ad970e16..ae2597adc8 100644
--- a/activerecord/test/cases/log_subscriber_test.rb
+++ b/activerecord/test/cases/log_subscriber_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/binary"
require "models/developer"
@@ -21,6 +23,7 @@ class LogSubscriberTest < ActiveRecord::TestCase
TRANSACTION: REGEXP_CYAN,
OTHER: REGEXP_MAGENTA
}
+ Event = Struct.new(:duration, :payload)
class TestDebugLogSubscriber < ActiveRecord::LogSubscriber
attr_reader :debugs
@@ -30,8 +33,9 @@ class LogSubscriberTest < ActiveRecord::TestCase
super
end
- def debug(message)
- @debugs << message
+ def debug(progname = nil, &block)
+ @debugs << progname
+ super
end
end
@@ -40,6 +44,7 @@ class LogSubscriberTest < ActiveRecord::TestCase
def setup
@old_logger = ActiveRecord::Base.logger
Developer.primary_key
+ ActiveRecord::Base.connection.materialize_transactions
super
ActiveRecord::LogSubscriber.attach_to(:active_record)
end
@@ -55,25 +60,22 @@ class LogSubscriberTest < ActiveRecord::TestCase
end
def test_schema_statements_are_ignored
- event = Struct.new(:duration, :payload)
-
logger = TestDebugLogSubscriber.new
assert_equal 0, logger.debugs.length
- logger.sql(event.new(0, sql: "hi mom!"))
+ logger.sql(Event.new(0.9, sql: "hi mom!"))
assert_equal 1, logger.debugs.length
- logger.sql(event.new(0, sql: "hi mom!", name: "foo"))
+ logger.sql(Event.new(0.9, sql: "hi mom!", name: "foo"))
assert_equal 2, logger.debugs.length
- logger.sql(event.new(0, sql: "hi mom!", name: "SCHEMA"))
+ logger.sql(Event.new(0.9, sql: "hi mom!", name: "SCHEMA"))
assert_equal 2, logger.debugs.length
end
def test_sql_statements_are_not_squeezed
- event = Struct.new(:duration, :payload)
logger = TestDebugLogSubscriber.new
- logger.sql(event.new(0, sql: "ruby rails"))
+ logger.sql(Event.new(0.9, sql: "ruby rails"))
assert_match(/ruby rails/, logger.debugs.first)
end
@@ -86,56 +88,51 @@ class LogSubscriberTest < ActiveRecord::TestCase
end
def test_basic_query_logging_coloration
- event = Struct.new(:duration, :payload)
logger = TestDebugLogSubscriber.new
logger.colorize_logging = true
SQL_COLORINGS.each do |verb, color_regex|
- logger.sql(event.new(0, sql: verb.to_s))
+ logger.sql(Event.new(0.9, sql: verb.to_s))
assert_match(/#{REGEXP_BOLD}#{color_regex}#{verb}#{REGEXP_CLEAR}/i, logger.debugs.last)
end
end
def test_basic_payload_name_logging_coloration_generic_sql
- event = Struct.new(:duration, :payload)
logger = TestDebugLogSubscriber.new
logger.colorize_logging = true
SQL_COLORINGS.each do |verb, _|
- logger.sql(event.new(0, sql: verb.to_s))
- assert_match(/#{REGEXP_BOLD}#{REGEXP_MAGENTA} \(0.0ms\)#{REGEXP_CLEAR}/i, logger.debugs.last)
+ logger.sql(Event.new(0.9, sql: verb.to_s))
+ assert_match(/#{REGEXP_BOLD}#{REGEXP_MAGENTA} \(0\.9ms\)#{REGEXP_CLEAR}/i, logger.debugs.last)
- logger.sql(event.new(0, sql: verb.to_s, name: "SQL"))
- assert_match(/#{REGEXP_BOLD}#{REGEXP_MAGENTA}SQL \(0.0ms\)#{REGEXP_CLEAR}/i, logger.debugs.last)
+ logger.sql(Event.new(0.9, sql: verb.to_s, name: "SQL"))
+ assert_match(/#{REGEXP_BOLD}#{REGEXP_MAGENTA}SQL \(0\.9ms\)#{REGEXP_CLEAR}/i, logger.debugs.last)
end
end
def test_basic_payload_name_logging_coloration_named_sql
- event = Struct.new(:duration, :payload)
logger = TestDebugLogSubscriber.new
logger.colorize_logging = true
SQL_COLORINGS.each do |verb, _|
- logger.sql(event.new(0, sql: verb.to_s, name: "Model Load"))
- assert_match(/#{REGEXP_BOLD}#{REGEXP_CYAN}Model Load \(0.0ms\)#{REGEXP_CLEAR}/i, logger.debugs.last)
+ logger.sql(Event.new(0.9, sql: verb.to_s, name: "Model Load"))
+ assert_match(/#{REGEXP_BOLD}#{REGEXP_CYAN}Model Load \(0\.9ms\)#{REGEXP_CLEAR}/i, logger.debugs.last)
- logger.sql(event.new(0, sql: verb.to_s, name: "Model Exists"))
- assert_match(/#{REGEXP_BOLD}#{REGEXP_CYAN}Model Exists \(0.0ms\)#{REGEXP_CLEAR}/i, logger.debugs.last)
+ logger.sql(Event.new(0.9, sql: verb.to_s, name: "Model Exists"))
+ assert_match(/#{REGEXP_BOLD}#{REGEXP_CYAN}Model Exists \(0\.9ms\)#{REGEXP_CLEAR}/i, logger.debugs.last)
- logger.sql(event.new(0, sql: verb.to_s, name: "ANY SPECIFIC NAME"))
- assert_match(/#{REGEXP_BOLD}#{REGEXP_CYAN}ANY SPECIFIC NAME \(0.0ms\)#{REGEXP_CLEAR}/i, logger.debugs.last)
+ logger.sql(Event.new(0.9, sql: verb.to_s, name: "ANY SPECIFIC NAME"))
+ assert_match(/#{REGEXP_BOLD}#{REGEXP_CYAN}ANY SPECIFIC NAME \(0\.9ms\)#{REGEXP_CLEAR}/i, logger.debugs.last)
end
end
def test_query_logging_coloration_with_nested_select
- event = Struct.new(:duration, :payload)
logger = TestDebugLogSubscriber.new
logger.colorize_logging = true
SQL_COLORINGS.slice(:SELECT, :INSERT, :UPDATE, :DELETE).each do |verb, color_regex|
- logger.sql(event.new(0, sql: "#{verb} WHERE ID IN SELECT"))
- assert_match(/#{REGEXP_BOLD}#{REGEXP_MAGENTA} \(0.0ms\)#{REGEXP_CLEAR} #{REGEXP_BOLD}#{color_regex}#{verb} WHERE ID IN SELECT#{REGEXP_CLEAR}/i, logger.debugs.last)
+ logger.sql(Event.new(0.9, sql: "#{verb} WHERE ID IN SELECT"))
+ assert_match(/#{REGEXP_BOLD}#{REGEXP_MAGENTA} \(0\.9ms\)#{REGEXP_CLEAR} #{REGEXP_BOLD}#{color_regex}#{verb} WHERE ID IN SELECT#{REGEXP_CLEAR}/i, logger.debugs.last)
end
end
def test_query_logging_coloration_with_multi_line_nested_select
- event = Struct.new(:duration, :payload)
logger = TestDebugLogSubscriber.new
logger.colorize_logging = true
SQL_COLORINGS.slice(:SELECT, :INSERT, :UPDATE, :DELETE).each do |verb, color_regex|
@@ -145,13 +142,12 @@ class LogSubscriberTest < ActiveRecord::TestCase
SELECT ID FROM THINGS
)
EOS
- logger.sql(event.new(0, sql: sql))
- assert_match(/#{REGEXP_BOLD}#{REGEXP_MAGENTA} \(0.0ms\)#{REGEXP_CLEAR} #{REGEXP_BOLD}#{color_regex}.*#{verb}.*#{REGEXP_CLEAR}/mi, logger.debugs.last)
+ logger.sql(Event.new(0.9, sql: sql))
+ assert_match(/#{REGEXP_BOLD}#{REGEXP_MAGENTA} \(0\.9ms\)#{REGEXP_CLEAR} #{REGEXP_BOLD}#{color_regex}.*#{verb}.*#{REGEXP_CLEAR}/mi, logger.debugs.last)
end
end
def test_query_logging_coloration_with_lock
- event = Struct.new(:duration, :payload)
logger = TestDebugLogSubscriber.new
logger.colorize_logging = true
sql = <<-EOS
@@ -159,14 +155,14 @@ class LogSubscriberTest < ActiveRecord::TestCase
(SELECT * FROM mytable FOR UPDATE) ss
WHERE col1 = 5;
EOS
- logger.sql(event.new(0, sql: sql))
- assert_match(/#{REGEXP_BOLD}#{REGEXP_MAGENTA} \(0.0ms\)#{REGEXP_CLEAR} #{REGEXP_BOLD}#{SQL_COLORINGS[:LOCK]}.*FOR UPDATE.*#{REGEXP_CLEAR}/mi, logger.debugs.last)
+ logger.sql(Event.new(0.9, sql: sql))
+ assert_match(/#{REGEXP_BOLD}#{REGEXP_MAGENTA} \(0\.9ms\)#{REGEXP_CLEAR} #{REGEXP_BOLD}#{SQL_COLORINGS[:LOCK]}.*FOR UPDATE.*#{REGEXP_CLEAR}/mi, logger.debugs.last)
sql = <<-EOS
LOCK TABLE films IN SHARE MODE;
EOS
- logger.sql(event.new(0, sql: sql))
- assert_match(/#{REGEXP_BOLD}#{REGEXP_MAGENTA} \(0.0ms\)#{REGEXP_CLEAR} #{REGEXP_BOLD}#{SQL_COLORINGS[:LOCK]}.*LOCK TABLE.*#{REGEXP_CLEAR}/mi, logger.debugs.last)
+ logger.sql(Event.new(0.9, sql: sql))
+ assert_match(/#{REGEXP_BOLD}#{REGEXP_MAGENTA} \(0\.9ms\)#{REGEXP_CLEAR} #{REGEXP_BOLD}#{SQL_COLORINGS[:LOCK]}.*LOCK TABLE.*#{REGEXP_CLEAR}/mi, logger.debugs.last)
end
def test_exists_query_logging
@@ -177,6 +173,36 @@ class LogSubscriberTest < ActiveRecord::TestCase
assert_match(/SELECT .*?FROM .?developers.?/i, @logger.logged(:debug).last)
end
+ def test_vebose_query_logs
+ ActiveRecord::Base.verbose_query_logs = true
+
+ logger = TestDebugLogSubscriber.new
+ logger.sql(Event.new(0, sql: "hi mom!"))
+ assert_equal 2, @logger.logged(:debug).size
+ assert_match(/↳/, @logger.logged(:debug).last)
+ ensure
+ ActiveRecord::Base.verbose_query_logs = false
+ end
+
+ def test_verbose_query_with_ignored_callstack
+ ActiveRecord::Base.verbose_query_logs = true
+
+ logger = TestDebugLogSubscriber.new
+ def logger.extract_query_source_location(*); nil; end
+
+ logger.sql(Event.new(0, sql: "hi mom!"))
+ assert_equal 1, @logger.logged(:debug).size
+ assert_no_match(/↳/, @logger.logged(:debug).last)
+ ensure
+ ActiveRecord::Base.verbose_query_logs = false
+ end
+
+ def test_verbose_query_logs_disabled_by_default
+ logger = TestDebugLogSubscriber.new
+ logger.sql(Event.new(0, sql: "hi mom!"))
+ assert_no_match(/↳/, @logger.logged(:debug).last)
+ end
+
def test_cached_queries
ActiveRecord::Base.cache do
Developer.all.load
diff --git a/activerecord/test/cases/migration/change_schema_test.rb b/activerecord/test/cases/migration/change_schema_test.rb
index 1d305fa11f..7777508349 100644
--- a/activerecord/test/cases/migration/change_schema_test.rb
+++ b/activerecord/test/cases/migration/change_schema_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
module ActiveRecord
@@ -82,7 +84,7 @@ module ActiveRecord
columns = connection.columns(:testings)
array_column = columns.detect { |c| c.name == "foo" }
- assert array_column.array?
+ assert_predicate array_column, :array?
end
def test_create_table_with_array_column
@@ -93,7 +95,7 @@ module ActiveRecord
columns = connection.columns(:testings)
array_column = columns.detect { |c| c.name == "foo" }
- assert array_column.array?
+ assert_predicate array_column, :array?
end
end
@@ -194,6 +196,17 @@ module ActiveRecord
assert_equal "you can't redefine the primary key column 'testing_id'. To define a custom primary key, pass { id: false } to create_table.", error.message
end
+ def test_create_table_raises_when_defining_existing_column
+ error = assert_raise(ArgumentError) do
+ connection.create_table :testings do |t|
+ t.column :testing_column, :string
+ t.column :testing_column, :integer
+ end
+ end
+
+ assert_equal "you can't define an already defined column 'testing_column'.", error.message
+ end
+
def test_create_table_with_timestamps_should_create_datetime_columns
connection.create_table table_name do |t|
t.timestamps
@@ -203,8 +216,8 @@ module ActiveRecord
created_at_column = created_columns.detect { |c| c.name == "created_at" }
updated_at_column = created_columns.detect { |c| c.name == "updated_at" }
- assert !created_at_column.null
- assert !updated_at_column.null
+ assert_not created_at_column.null
+ assert_not updated_at_column.null
end
def test_create_table_with_timestamps_should_create_datetime_columns_with_options
@@ -262,17 +275,18 @@ module ActiveRecord
t.column :foo, :timestamp
end
- klass = Class.new(ActiveRecord::Base)
- klass.table_name = "testings"
+ column = connection.columns(:testings).find { |c| c.name == "foo" }
- assert_equal :datetime, klass.columns_hash["foo"].type
+ assert_equal :datetime, column.type
if current_adapter?(:PostgreSQLAdapter)
- assert_equal "timestamp without time zone", klass.columns_hash["foo"].sql_type
+ assert_equal "timestamp without time zone", column.sql_type
elsif current_adapter?(:Mysql2Adapter)
- assert_equal "timestamp", klass.columns_hash["foo"].sql_type
+ assert_equal "timestamp", column.sql_type
+ elsif current_adapter?(:OracleAdapter)
+ assert_equal "TIMESTAMP(6)", column.sql_type
else
- assert_equal klass.connection.type_to_sql("datetime"), klass.columns_hash["foo"].sql_type
+ assert_equal connection.type_to_sql("datetime"), column.sql_type
end
end
@@ -405,7 +419,7 @@ module ActiveRecord
end
connection.change_table :testings do |t|
assert t.column_exists?(:foo)
- assert !(t.column_exists?(:bar))
+ assert_not (t.column_exists?(:bar))
end
end
diff --git a/activerecord/test/cases/migration/change_table_test.rb b/activerecord/test/cases/migration/change_table_test.rb
index ec817a579b..034bf32165 100644
--- a/activerecord/test/cases/migration/change_table_test.rb
+++ b/activerecord/test/cases/migration/change_table_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/migration/helper"
module ActiveRecord
diff --git a/activerecord/test/cases/migration/column_attributes_test.rb b/activerecord/test/cases/migration/column_attributes_test.rb
index 48df931543..3022121f4c 100644
--- a/activerecord/test/cases/migration/column_attributes_test.rb
+++ b/activerecord/test/cases/migration/column_attributes_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/migration/helper"
module ActiveRecord
@@ -43,11 +45,11 @@ module ActiveRecord
assert_nil TestModel.columns_hash["description"].limit
end
- if current_adapter?(:Mysql2Adapter)
+ if current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter)
def test_unabstracted_database_dependent_types
- add_column :test_models, :intelligence_quotient, :tinyint
+ add_column :test_models, :intelligence_quotient, :smallint
TestModel.reset_column_information
- assert_match(/tinyint/, TestModel.columns_hash["intelligence_quotient"].sql_type)
+ assert_match(/smallint/, TestModel.columns_hash["intelligence_quotient"].sql_type)
end
end
@@ -78,7 +80,7 @@ module ActiveRecord
TestModel.delete_all
# Now use the Rails insertion
- TestModel.create wealth: BigDecimal.new("12345678901234567890.0123456789")
+ TestModel.create wealth: BigDecimal("12345678901234567890.0123456789")
# SELECT
row = TestModel.first
@@ -97,7 +99,21 @@ module ActiveRecord
assert_equal 7, wealth_column.scale
end
+ # Test SQLite3 adapter specifically for decimal types with precision and scale
+ # attributes, since these need to be maintained in schema but aren't actually
+ # used in SQLite3 itself
if current_adapter?(:SQLite3Adapter)
+ def test_change_column_with_new_precision_and_scale
+ connection.add_column "test_models", "wealth", :decimal, precision: 9, scale: 7
+
+ connection.change_column "test_models", "wealth", :decimal, precision: 12, scale: 8
+ TestModel.reset_column_information
+
+ wealth_column = TestModel.columns_hash["wealth"]
+ assert_equal 12, wealth_column.precision
+ assert_equal 8, wealth_column.scale
+ end
+
def test_change_column_preserve_other_column_precision_and_scale
connection.add_column "test_models", "last_name", :string
connection.add_column "test_models", "wealth", :decimal, precision: 9, scale: 7
@@ -130,7 +146,7 @@ module ActiveRecord
TestModel.create first_name: "bob", last_name: "bobsen",
bio: "I was born ....", age: 18, height: 1.78,
- wealth: BigDecimal.new("12345678901234567890.0123456789"),
+ wealth: BigDecimal("12345678901234567890.0123456789"),
birthday: 18.years.ago, favorite_day: 10.days.ago,
moment_of_truth: "1782-10-10 21:40:18", male: true
@@ -143,7 +159,7 @@ module ActiveRecord
# Test for 30 significant digits (beyond the 16 of float), 10 of them
# after the decimal place.
- assert_equal BigDecimal.new("0012345678901234567890.0123456789"), bob.wealth
+ assert_equal BigDecimal("0012345678901234567890.0123456789"), bob.wealth
assert_equal true, bob.male?
diff --git a/activerecord/test/cases/migration/column_positioning_test.rb b/activerecord/test/cases/migration/column_positioning_test.rb
index f2162d91b1..1c62a68cf9 100644
--- a/activerecord/test/cases/migration/column_positioning_test.rb
+++ b/activerecord/test/cases/migration/column_positioning_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
module ActiveRecord
@@ -50,6 +52,16 @@ module ActiveRecord
conn.change_column :testings, :second, :integer, after: :third
assert_equal %w(first third second), conn.columns(:testings).map(&:name)
end
+
+ def test_add_reference_with_positioning_first
+ conn.add_reference :testings, :new, polymorphic: true, first: true
+ assert_equal %w(new_id new_type first second third), conn.columns(:testings).map(&:name)
+ end
+
+ def test_add_reference_with_positioning_after
+ conn.add_reference :testings, :new, polymorphic: true, after: :first
+ assert_equal %w(first new_id new_type second third), conn.columns(:testings).map(&:name)
+ end
end
end
end
diff --git a/activerecord/test/cases/migration/columns_test.rb b/activerecord/test/cases/migration/columns_test.rb
index 2329888345..cedd9c44e3 100644
--- a/activerecord/test/cases/migration/columns_test.rb
+++ b/activerecord/test/cases/migration/columns_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/migration/helper"
module ActiveRecord
@@ -65,7 +67,7 @@ module ActiveRecord
if current_adapter?(:Mysql2Adapter)
def test_mysql_rename_column_preserves_auto_increment
rename_column "test_models", "id", "id_test"
- assert connection.columns("test_models").find { |c| c.name == "id_test" }.auto_increment?
+ assert_predicate connection.columns("test_models").find { |c| c.name == "id_test" }, :auto_increment?
TestModel.reset_column_information
ensure
rename_column "test_models", "id_test", "id"
@@ -140,6 +142,10 @@ module ActiveRecord
end
def test_remove_column_with_multi_column_index
+ # MariaDB starting with 10.2.8
+ # Dropping a column that is part of a multi-column UNIQUE constraint is not permitted.
+ skip if current_adapter?(:Mysql2Adapter) && connection.mariadb? && connection.version >= "10.2.8"
+
add_column "test_models", :hat_size, :integer
add_column "test_models", :hat_style, :string, limit: 100
add_index "test_models", ["hat_style", "hat_size"], unique: true
@@ -217,31 +223,31 @@ module ActiveRecord
def test_change_column_with_nil_default
add_column "test_models", "contributor", :boolean, default: true
- assert TestModel.new.contributor?
+ assert_predicate TestModel.new, :contributor?
change_column "test_models", "contributor", :boolean, default: nil
TestModel.reset_column_information
- assert_not TestModel.new.contributor?
+ assert_not_predicate TestModel.new, :contributor?
assert_nil TestModel.new.contributor
end
def test_change_column_to_drop_default_with_null_false
add_column "test_models", "contributor", :boolean, default: true, null: false
- assert TestModel.new.contributor?
+ assert_predicate TestModel.new, :contributor?
change_column "test_models", "contributor", :boolean, default: nil, null: false
TestModel.reset_column_information
- assert_not TestModel.new.contributor?
+ assert_not_predicate TestModel.new, :contributor?
assert_nil TestModel.new.contributor
end
def test_change_column_with_new_default
add_column "test_models", "administrator", :boolean, default: true
- assert TestModel.new.administrator?
+ assert_predicate TestModel.new, :administrator?
change_column "test_models", "administrator", :boolean, default: false
TestModel.reset_column_information
- assert_not TestModel.new.administrator?
+ assert_not_predicate TestModel.new, :administrator?
end
def test_change_column_with_custom_index_name
diff --git a/activerecord/test/cases/migration/command_recorder_test.rb b/activerecord/test/cases/migration/command_recorder_test.rb
index 802a969cb7..1a19b8dafd 100644
--- a/activerecord/test/cases/migration/command_recorder_test.rb
+++ b/activerecord/test/cases/migration/command_recorder_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
module ActiveRecord
@@ -12,7 +14,7 @@ module ActiveRecord
recorder = CommandRecorder.new(Class.new {
def america; end
}.new)
- assert recorder.respond_to?(:america)
+ assert_respond_to recorder, :america
end
def test_send_calls_super
@@ -25,7 +27,7 @@ module ActiveRecord
recorder = CommandRecorder.new(Class.new {
def create_table(name); end
}.new)
- assert recorder.respond_to?(:create_table), "respond_to? create_table"
+ assert_respond_to recorder, :create_table
recorder.send(:create_table, :horses)
assert_equal [[:create_table, [:horses], nil]], recorder.commands
end
@@ -211,9 +213,9 @@ module ActiveRecord
assert_equal [:remove_index, [:table, { name: "new_index" }]], remove
end
- def test_invert_add_index_with_no_options
- remove = @recorder.inverse_of :add_index, [:table, [:one, :two]]
- assert_equal [:remove_index, [:table, { column: [:one, :two] }]], remove
+ def test_invert_add_index_with_algorithm_option
+ remove = @recorder.inverse_of :add_index, [:table, :one, algorithm: :concurrently]
+ assert_equal [:remove_index, [:table, { column: :one, algorithm: :concurrently }]], remove
end
def test_invert_remove_index
@@ -327,11 +329,24 @@ module ActiveRecord
assert_equal [:add_foreign_key, [:dogs, :people, primary_key: "person_id"]], enable
end
+ def test_invert_remove_foreign_key_with_primary_key_and_to_table_in_options
+ enable = @recorder.inverse_of :remove_foreign_key, [:dogs, to_table: :people, primary_key: "uuid"]
+ assert_equal [:add_foreign_key, [:dogs, :people, primary_key: "uuid"]], enable
+ end
+
def test_invert_remove_foreign_key_with_on_delete_on_update
enable = @recorder.inverse_of :remove_foreign_key, [:dogs, :people, on_delete: :nullify, on_update: :cascade]
assert_equal [:add_foreign_key, [:dogs, :people, on_delete: :nullify, on_update: :cascade]], enable
end
+ def test_invert_remove_foreign_key_with_to_table_in_options
+ enable = @recorder.inverse_of :remove_foreign_key, [:dogs, to_table: :people]
+ assert_equal [:add_foreign_key, [:dogs, :people]], enable
+
+ enable = @recorder.inverse_of :remove_foreign_key, [:dogs, to_table: :people, column: :owner_id]
+ assert_equal [:add_foreign_key, [:dogs, :people, column: :owner_id]], enable
+ end
+
def test_invert_remove_foreign_key_is_irreversible_without_to_table
assert_raises ActiveRecord::IrreversibleMigration do
@recorder.inverse_of :remove_foreign_key, [:dogs, column: "owner_id"]
diff --git a/activerecord/test/cases/migration/compatibility_test.rb b/activerecord/test/cases/migration/compatibility_test.rb
index 7a80bfb899..69a50674af 100644
--- a/activerecord/test/cases/migration/compatibility_test.rb
+++ b/activerecord/test/cases/migration/compatibility_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "support/schema_dumping_helper"
@@ -14,7 +16,7 @@ module ActiveRecord
ActiveRecord::Migration.verbose = false
connection.create_table :testings do |t|
- t.column :foo, :string, limit: 100
+ t.column :foo, :string, limit: 5
t.column :bar, :string, limit: 100
end
end
@@ -90,6 +92,21 @@ module ActiveRecord
connection.drop_table :more_testings rescue nil
end
+ def test_timestamps_have_null_constraints_if_not_present_in_migration_of_change_table
+ migration = Class.new(ActiveRecord::Migration[4.2]) {
+ def migrate(x)
+ change_table :testings do |t|
+ t.timestamps
+ end
+ end
+ }.new
+
+ ActiveRecord::Migrator.new(:up, [migration]).migrate
+
+ assert connection.columns(:testings).find { |c| c.name == "created_at" }.null
+ assert connection.columns(:testings).find { |c| c.name == "updated_at" }.null
+ end
+
def test_timestamps_have_null_constraints_if_not_present_in_migration_for_adding_timestamps_to_existing_table
migration = Class.new(ActiveRecord::Migration[4.2]) {
def migrate(x)
@@ -109,15 +126,32 @@ module ActiveRecord
end
assert_match(/LegacyMigration < ActiveRecord::Migration\[4\.2\]/, e.message)
end
+
+ if current_adapter?(:PostgreSQLAdapter)
+ class Testing < ActiveRecord::Base
+ end
+
+ def test_legacy_change_column_with_null_executes_update
+ migration = Class.new(ActiveRecord::Migration[5.1]) {
+ def migrate(x)
+ change_column :testings, :foo, :string, limit: 10, null: false, default: "foobar"
+ end
+ }.new
+
+ Testing.create!
+ ActiveRecord::Migrator.new(:up, [migration]).migrate
+ assert_equal ["foobar"], Testing.all.map(&:foo)
+ ensure
+ ActiveRecord::Base.clear_cache!
+ end
+ end
end
end
end
-class LegacyPrimaryKeyTest < ActiveRecord::TestCase
+module LegacyPrimaryKeyTestCases
include SchemaDumpingHelper
- self.use_transactional_tests = false
-
class LegacyPrimaryKey < ActiveRecord::Base
end
@@ -135,7 +169,7 @@ class LegacyPrimaryKeyTest < ActiveRecord::TestCase
end
def test_legacy_primary_key_should_be_auto_incremented
- @migration = Class.new(ActiveRecord::Migration[5.0]) {
+ @migration = Class.new(migration_class) {
def change
create_table :legacy_primary_keys do |t|
t.references :legacy_ref
@@ -145,12 +179,10 @@ class LegacyPrimaryKeyTest < ActiveRecord::TestCase
@migration.migrate(:up)
- legacy_pk = LegacyPrimaryKey.columns_hash["id"]
- assert_not legacy_pk.bigint?
- assert_not legacy_pk.null
+ assert_legacy_primary_key
legacy_ref = LegacyPrimaryKey.columns_hash["legacy_ref_id"]
- assert_not legacy_ref.bigint?
+ assert_not_predicate legacy_ref, :bigint?
record1 = LegacyPrimaryKey.create!
assert_not_nil record1.id
@@ -165,7 +197,7 @@ class LegacyPrimaryKeyTest < ActiveRecord::TestCase
def test_legacy_integer_primary_key_should_not_be_auto_incremented
skip if current_adapter?(:SQLite3Adapter)
- @migration = Class.new(ActiveRecord::Migration[5.0]) {
+ @migration = Class.new(migration_class) {
def change
create_table :legacy_primary_keys, id: :integer do |t|
end
@@ -182,9 +214,85 @@ class LegacyPrimaryKeyTest < ActiveRecord::TestCase
assert_match %r{create_table "legacy_primary_keys", id: :integer, default: nil}, schema
end
+ def test_legacy_primary_key_in_create_table_should_be_integer
+ @migration = Class.new(migration_class) {
+ def change
+ create_table :legacy_primary_keys, id: false do |t|
+ t.primary_key :id
+ end
+ end
+ }.new
+
+ @migration.migrate(:up)
+
+ assert_legacy_primary_key
+ end
+
+ def test_legacy_primary_key_in_change_table_should_be_integer
+ @migration = Class.new(migration_class) {
+ def change
+ create_table :legacy_primary_keys, id: false do |t|
+ t.integer :dummy
+ end
+ change_table :legacy_primary_keys do |t|
+ t.primary_key :id
+ end
+ end
+ }.new
+
+ @migration.migrate(:up)
+
+ assert_legacy_primary_key
+ end
+
+ def test_add_column_with_legacy_primary_key_should_be_integer
+ @migration = Class.new(migration_class) {
+ def change
+ create_table :legacy_primary_keys, id: false do |t|
+ t.integer :dummy
+ end
+ add_column :legacy_primary_keys, :id, :primary_key
+ end
+ }.new
+
+ @migration.migrate(:up)
+
+ assert_legacy_primary_key
+ end
+
+ def test_legacy_join_table_foreign_keys_should_be_integer
+ @migration = Class.new(migration_class) {
+ def change
+ create_join_table :apples, :bananas do |t|
+ end
+ end
+ }.new
+
+ @migration.migrate(:up)
+
+ schema = dump_table_schema "apples_bananas"
+ assert_match %r{integer "apple_id", null: false}, schema
+ assert_match %r{integer "banana_id", null: false}, schema
+ end
+
+ def test_legacy_join_table_column_options_should_be_overwritten
+ @migration = Class.new(migration_class) {
+ def change
+ create_join_table :apples, :bananas, column_options: { type: :bigint } do |t|
+ end
+ end
+ }.new
+
+ @migration.migrate(:up)
+
+ schema = dump_table_schema "apples_bananas"
+ assert_match %r{bigint "apple_id", null: false}, schema
+ assert_match %r{bigint "banana_id", null: false}, schema
+ end
+
if current_adapter?(:Mysql2Adapter)
def test_legacy_bigint_primary_key_should_be_auto_incremented
- @migration = Class.new(ActiveRecord::Migration[5.0]) {
+ @migration = Class.new(migration_class) {
def change
create_table :legacy_primary_keys, id: :bigint
end
@@ -193,15 +301,15 @@ class LegacyPrimaryKeyTest < ActiveRecord::TestCase
@migration.migrate(:up)
legacy_pk = LegacyPrimaryKey.columns_hash["id"]
- assert legacy_pk.bigint?
- assert legacy_pk.auto_increment?
+ assert_predicate legacy_pk, :bigint?
+ assert_predicate legacy_pk, :auto_increment?
schema = dump_table_schema "legacy_primary_keys"
assert_match %r{create_table "legacy_primary_keys", (?!id: :bigint, default: nil)}, schema
end
else
def test_legacy_bigint_primary_key_should_not_be_auto_incremented
- @migration = Class.new(ActiveRecord::Migration[5.0]) {
+ @migration = Class.new(migration_class) {
def change
create_table :legacy_primary_keys, id: :bigint do |t|
end
@@ -218,4 +326,44 @@ class LegacyPrimaryKeyTest < ActiveRecord::TestCase
assert_match %r{create_table "legacy_primary_keys", id: :bigint, default: nil}, schema
end
end
+
+ private
+ def assert_legacy_primary_key
+ assert_equal "id", LegacyPrimaryKey.primary_key
+
+ legacy_pk = LegacyPrimaryKey.columns_hash["id"]
+
+ assert_equal :integer, legacy_pk.type
+ assert_not_predicate legacy_pk, :bigint?
+ assert_not legacy_pk.null
+
+ if current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter)
+ schema = dump_table_schema "legacy_primary_keys"
+ assert_match %r{create_table "legacy_primary_keys", id: :(?:integer|serial), (?!default: nil)}, schema
+ end
+ end
+end
+
+module LegacyPrimaryKeyTest
+ class V5_0 < ActiveRecord::TestCase
+ include LegacyPrimaryKeyTestCases
+
+ self.use_transactional_tests = false
+
+ private
+ def migration_class
+ ActiveRecord::Migration[5.0]
+ end
+ end
+
+ class V4_2 < ActiveRecord::TestCase
+ include LegacyPrimaryKeyTestCases
+
+ self.use_transactional_tests = false
+
+ private
+ def migration_class
+ ActiveRecord::Migration[4.2]
+ end
+ end
end
diff --git a/activerecord/test/cases/migration/create_join_table_test.rb b/activerecord/test/cases/migration/create_join_table_test.rb
index 26b1bb4419..e0cbb29dcf 100644
--- a/activerecord/test/cases/migration/create_join_table_test.rb
+++ b/activerecord/test/cases/migration/create_join_table_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
module ActiveRecord
@@ -12,7 +14,7 @@ module ActiveRecord
teardown do
%w(artists_musics musics_videos catalog).each do |table_name|
- connection.drop_table table_name if connection.table_exists?(table_name)
+ connection.drop_table table_name, if_exists: true
end
end
@@ -67,7 +69,7 @@ module ActiveRecord
def test_create_join_table_without_indexes
connection.create_join_table :artists, :musics
- assert connection.indexes(:artists_musics).blank?
+ assert_predicate connection.indexes(:artists_musics), :blank?
end
def test_create_join_table_with_index
@@ -78,46 +80,57 @@ module ActiveRecord
assert_equal [%w(artist_id music_id)], connection.indexes(:artists_musics).map(&:columns)
end
+ def test_create_join_table_respects_reference_key_type
+ connection.create_join_table :artists, :musics do |t|
+ t.references :video
+ end
+
+ artist_id, music_id, video_id = connection.columns(:artists_musics).sort_by(&:name)
+
+ assert_equal video_id.sql_type, artist_id.sql_type
+ assert_equal video_id.sql_type, music_id.sql_type
+ end
+
def test_drop_join_table
connection.create_join_table :artists, :musics
connection.drop_join_table :artists, :musics
- assert !connection.table_exists?("artists_musics")
+ assert_not connection.table_exists?("artists_musics")
end
def test_drop_join_table_with_strings
connection.create_join_table :artists, :musics
connection.drop_join_table "artists", "musics"
- assert !connection.table_exists?("artists_musics")
+ assert_not connection.table_exists?("artists_musics")
end
def test_drop_join_table_with_the_proper_order
connection.create_join_table :videos, :musics
connection.drop_join_table :videos, :musics
- assert !connection.table_exists?("musics_videos")
+ assert_not connection.table_exists?("musics_videos")
end
def test_drop_join_table_with_the_table_name
connection.create_join_table :artists, :musics, table_name: :catalog
connection.drop_join_table :artists, :musics, table_name: :catalog
- assert !connection.table_exists?("catalog")
+ assert_not connection.table_exists?("catalog")
end
def test_drop_join_table_with_the_table_name_as_string
connection.create_join_table :artists, :musics, table_name: "catalog"
connection.drop_join_table :artists, :musics, table_name: "catalog"
- assert !connection.table_exists?("catalog")
+ assert_not connection.table_exists?("catalog")
end
def test_drop_join_table_with_column_options
connection.create_join_table :artists, :musics, column_options: { null: true }
connection.drop_join_table :artists, :musics, column_options: { null: true }
- assert !connection.table_exists?("artists_musics")
+ assert_not connection.table_exists?("artists_musics")
end
def test_create_and_drop_join_table_with_common_prefix
@@ -126,7 +139,7 @@ module ActiveRecord
assert connection.table_exists?("audio_artists_musics")
connection.drop_join_table "audio_artists", "audio_musics"
- assert !connection.table_exists?("audio_artists_musics"), "Should have dropped join table, but didn't"
+ assert_not connection.table_exists?("audio_artists_musics"), "Should have dropped join table, but didn't"
end
end
diff --git a/activerecord/test/cases/migration/foreign_key_test.rb b/activerecord/test/cases/migration/foreign_key_test.rb
index 7762d37915..bb233fbf74 100644
--- a/activerecord/test/cases/migration/foreign_key_test.rb
+++ b/activerecord/test/cases/migration/foreign_key_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "support/schema_dumping_helper"
@@ -17,6 +19,85 @@ if ActiveRecord::Base.connection.supports_foreign_keys_in_create?
assert_equal "fk_name", fk.name unless current_adapter?(:SQLite3Adapter)
end
end
+
+ class ForeignKeyChangeColumnTest < ActiveRecord::TestCase
+ self.use_transactional_tests = false
+
+ class Rocket < ActiveRecord::Base
+ has_many :astronauts
+ end
+
+ class Astronaut < ActiveRecord::Base
+ belongs_to :rocket
+ end
+
+ setup do
+ @connection = ActiveRecord::Base.connection
+ @connection.create_table "rockets", force: true do |t|
+ t.string :name
+ end
+
+ @connection.create_table "astronauts", force: true do |t|
+ t.string :name
+ t.references :rocket, foreign_key: true
+ end
+ Rocket.reset_column_information
+ Astronaut.reset_column_information
+ end
+
+ teardown do
+ @connection.drop_table "astronauts", if_exists: true
+ @connection.drop_table "rockets", if_exists: true
+ Rocket.reset_column_information
+ Astronaut.reset_column_information
+ end
+
+ def test_change_column_of_parent_table
+ rocket = Rocket.create!(name: "myrocket")
+ rocket.astronauts << Astronaut.create!
+
+ @connection.change_column_null :rockets, :name, false
+
+ foreign_keys = @connection.foreign_keys("astronauts")
+ assert_equal 1, foreign_keys.size
+
+ fk = foreign_keys.first
+ assert_equal "myrocket", Rocket.first.name
+ assert_equal "astronauts", fk.from_table
+ assert_equal "rockets", fk.to_table
+ end
+
+ def test_rename_column_of_child_table
+ rocket = Rocket.create!(name: "myrocket")
+ rocket.astronauts << Astronaut.create!
+
+ @connection.rename_column :astronauts, :name, :astronaut_name
+
+ foreign_keys = @connection.foreign_keys("astronauts")
+ assert_equal 1, foreign_keys.size
+
+ fk = foreign_keys.first
+ assert_equal "myrocket", Rocket.first.name
+ assert_equal "astronauts", fk.from_table
+ assert_equal "rockets", fk.to_table
+ end
+
+ def test_rename_reference_column_of_child_table
+ rocket = Rocket.create!(name: "myrocket")
+ rocket.astronauts << Astronaut.create!
+
+ @connection.rename_column :astronauts, :rocket_id, :new_rocket_id
+
+ foreign_keys = @connection.foreign_keys("astronauts")
+ assert_equal 1, foreign_keys.size
+
+ fk = foreign_keys.first
+ assert_equal "myrocket", Rocket.first.name
+ assert_equal "astronauts", fk.from_table
+ assert_equal "rockets", fk.to_table
+ assert_equal "new_rocket_id", fk.options[:column]
+ end
+ end
end
end
end
@@ -225,6 +306,74 @@ if ActiveRecord::Base.connection.supports_foreign_keys?
end
end
+ if ActiveRecord::Base.connection.supports_validate_constraints?
+ def test_add_invalid_foreign_key
+ @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", validate: false
+
+ foreign_keys = @connection.foreign_keys("astronauts")
+ assert_equal 1, foreign_keys.size
+
+ fk = foreign_keys.first
+ assert_not_predicate fk, :validated?
+ end
+
+ def test_validate_foreign_key_infers_column
+ @connection.add_foreign_key :astronauts, :rockets, validate: false
+ assert_not_predicate @connection.foreign_keys("astronauts").first, :validated?
+
+ @connection.validate_foreign_key :astronauts, :rockets
+ assert_predicate @connection.foreign_keys("astronauts").first, :validated?
+ end
+
+ def test_validate_foreign_key_by_column
+ @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", validate: false
+ assert_not_predicate @connection.foreign_keys("astronauts").first, :validated?
+
+ @connection.validate_foreign_key :astronauts, column: "rocket_id"
+ assert_predicate @connection.foreign_keys("astronauts").first, :validated?
+ end
+
+ def test_validate_foreign_key_by_symbol_column
+ @connection.add_foreign_key :astronauts, :rockets, column: :rocket_id, validate: false
+ assert_not_predicate @connection.foreign_keys("astronauts").first, :validated?
+
+ @connection.validate_foreign_key :astronauts, column: :rocket_id
+ assert_predicate @connection.foreign_keys("astronauts").first, :validated?
+ end
+
+ def test_validate_foreign_key_by_name
+ @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", name: "fancy_named_fk", validate: false
+ assert_not_predicate @connection.foreign_keys("astronauts").first, :validated?
+
+ @connection.validate_foreign_key :astronauts, name: "fancy_named_fk"
+ assert_predicate @connection.foreign_keys("astronauts").first, :validated?
+ end
+
+ def test_validate_foreign_non_existing_foreign_key_raises
+ assert_raises ArgumentError do
+ @connection.validate_foreign_key :astronauts, :rockets
+ end
+ end
+
+ def test_validate_constraint_by_name
+ @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", name: "fancy_named_fk", validate: false
+
+ @connection.validate_constraint :astronauts, "fancy_named_fk"
+ assert_predicate @connection.foreign_keys("astronauts").first, :validated?
+ end
+ else
+ # Foreign key should still be created, but should not be invalid
+ def test_add_invalid_foreign_key
+ @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", validate: false
+
+ foreign_keys = @connection.foreign_keys("astronauts")
+ assert_equal 1, foreign_keys.size
+
+ fk = foreign_keys.first
+ assert_predicate fk, :validated?
+ end
+ end
+
def test_schema_dumping
@connection.add_foreign_key :astronauts, :rockets
output = dump_table_schema "astronauts"
@@ -236,6 +385,17 @@ if ActiveRecord::Base.connection.supports_foreign_keys?
assert_match %r{\s+add_foreign_key "fk_test_has_fk", "fk_test_has_pk", column: "fk_id", primary_key: "pk_id", name: "fk_name"$}, output
end
+ def test_schema_dumping_with_custom_fk_ignore_pattern
+ original_pattern = ActiveRecord::SchemaDumper.fk_ignore_pattern
+ ActiveRecord::SchemaDumper.fk_ignore_pattern = /^ignored_/
+ @connection.add_foreign_key :astronauts, :rockets, name: :ignored_fk_astronauts_rockets
+
+ output = dump_table_schema "astronauts"
+ assert_match %r{\s+add_foreign_key "astronauts", "rockets"$}, output
+
+ ActiveRecord::SchemaDumper.fk_ignore_pattern = original_pattern
+ end
+
def test_schema_dumping_on_delete_and_on_update_options
@connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", on_delete: :nullify, on_update: :cascade
diff --git a/activerecord/test/cases/migration/helper.rb b/activerecord/test/cases/migration/helper.rb
index 9c0fa7339d..c056199140 100644
--- a/activerecord/test/cases/migration/helper.rb
+++ b/activerecord/test/cases/migration/helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
module ActiveRecord
diff --git a/activerecord/test/cases/migration/index_test.rb b/activerecord/test/cases/migration/index_test.rb
index f10fcf1398..f8fecc83cd 100644
--- a/activerecord/test/cases/migration/index_test.rb
+++ b/activerecord/test/cases/migration/index_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
module ActiveRecord
@@ -31,10 +33,8 @@ module ActiveRecord
connection.add_index(table_name, [:foo], name: "old_idx")
connection.rename_index(table_name, "old_idx", "new_idx")
- assert_deprecated do
- assert_not connection.index_name_exists?(table_name, "old_idx", false)
- assert connection.index_name_exists?(table_name, "new_idx", true)
- end
+ assert_not connection.index_name_exists?(table_name, "old_idx")
+ assert connection.index_name_exists?(table_name, "new_idx")
end
def test_rename_index_too_long
@@ -99,7 +99,7 @@ module ActiveRecord
connection.add_index :testings, :foo
assert connection.index_exists?(:testings, :foo)
- assert !connection.index_exists?(:testings, :bar)
+ assert_not connection.index_exists?(:testings, :bar)
end
def test_index_exists_on_multiple_columns
@@ -131,15 +131,18 @@ module ActiveRecord
assert connection.index_exists?(:testings, :foo)
assert connection.index_exists?(:testings, :foo, name: "custom_index_name")
- assert !connection.index_exists?(:testings, :foo, name: "other_index_name")
+ assert_not connection.index_exists?(:testings, :foo, name: "other_index_name")
end
def test_remove_named_index
- connection.add_index :testings, :foo, name: "custom_index_name"
+ connection.add_index :testings, :foo, name: "index_testings_on_custom_index_name"
assert connection.index_exists?(:testings, :foo)
+
+ assert_raise(ArgumentError) { connection.remove_index(:testings, "custom_index_name") }
+
connection.remove_index :testings, :foo
- assert !connection.index_exists?(:testings, :foo)
+ assert_not connection.index_exists?(:testings, :foo)
end
def test_add_index_attribute_length_limit
@@ -203,7 +206,7 @@ module ActiveRecord
assert connection.index_exists?("testings", "last_name")
connection.remove_index("testings", "last_name")
- assert !connection.index_exists?("testings", "last_name")
+ assert_not connection.index_exists?("testings", "last_name")
end
end
diff --git a/activerecord/test/cases/migration/logger_test.rb b/activerecord/test/cases/migration/logger_test.rb
index 3d7c7ad469..28f4cc124b 100644
--- a/activerecord/test/cases/migration/logger_test.rb
+++ b/activerecord/test/cases/migration/logger_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
module ActiveRecord
diff --git a/activerecord/test/cases/migration/pending_migrations_test.rb b/activerecord/test/cases/migration/pending_migrations_test.rb
index 6970fdcc87..dedb5ea502 100644
--- a/activerecord/test/cases/migration/pending_migrations_test.rb
+++ b/activerecord/test/cases/migration/pending_migrations_test.rb
@@ -1,38 +1,40 @@
+# frozen_string_literal: true
+
require "cases/helper"
module ActiveRecord
class Migration
- class PendingMigrationsTest < ActiveRecord::TestCase
- def setup
- super
- @connection = Minitest::Mock.new
- @app = Minitest::Mock.new
- conn = @connection
- @pending = Class.new(CheckPending) {
- define_method(:connection) { conn }
- }.new(@app)
- @pending.instance_variable_set :@last_check, -1 # Force checking
- end
+ if current_adapter?(:SQLite3Adapter) && !in_memory_db?
+ class PendingMigrationsTest < ActiveRecord::TestCase
+ setup do
+ file = ActiveRecord::Base.connection.raw_connection.filename
+ @conn = ActiveRecord::Base.establish_connection adapter: "sqlite3", database: ":memory:", migrations_paths: MIGRATIONS_ROOT + "/valid"
+ source_db = SQLite3::Database.new file
+ dest_db = ActiveRecord::Base.connection.raw_connection
+ backup = SQLite3::Backup.new(dest_db, "main", source_db, "main")
+ backup.step(-1)
+ backup.finish
+ end
- def teardown
- assert @connection.verify
- assert @app.verify
- super
- end
+ teardown do
+ @conn.release_connection if @conn
+ ActiveRecord::Base.establish_connection :arunit
+ end
+
+ def test_errors_if_pending
+ ActiveRecord::Base.connection.drop_table "schema_migrations", if_exists: true
- def test_errors_if_pending
- ActiveRecord::Migrator.stub :needs_migration?, true do
- assert_raise ActiveRecord::PendingMigrationError do
- @pending.call(nil)
+ assert_raises ActiveRecord::PendingMigrationError do
+ CheckPending.new(Proc.new {}).call({})
end
end
- end
- def test_checks_if_supported
- @app.expect :call, nil, [:foo]
+ def test_checks_if_supported
+ ActiveRecord::SchemaMigration.create_table
+ migrator = Base.connection.migration_context
+ capture(:stdout) { migrator.migrate }
- ActiveRecord::Migrator.stub :needs_migration?, false do
- @pending.call(:foo)
+ assert_nil CheckPending.new(Proc.new {}).call({})
end
end
end
diff --git a/activerecord/test/cases/migration/references_foreign_key_test.rb b/activerecord/test/cases/migration/references_foreign_key_test.rb
index f1ddac1ee2..7a092103c7 100644
--- a/activerecord/test/cases/migration/references_foreign_key_test.rb
+++ b/activerecord/test/cases/migration/references_foreign_key_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
if ActiveRecord::Base.connection.supports_foreign_keys_in_create?
@@ -139,6 +141,16 @@ if ActiveRecord::Base.connection.supports_foreign_keys?
end
end
+ test "removing column removes foreign key" do
+ @connection.create_table :testings do |t|
+ t.references :testing_parent, index: true, foreign_key: true
+ end
+
+ assert_difference "@connection.foreign_keys('testings').size", -1 do
+ @connection.remove_column :testings, :testing_parent_id
+ end
+ end
+
test "foreign key methods respect pluralize_table_names" do
begin
original_pluralize_table_names = ActiveRecord::Base.pluralize_table_names
diff --git a/activerecord/test/cases/migration/references_index_test.rb b/activerecord/test/cases/migration/references_index_test.rb
index 2866cabab6..e41377d817 100644
--- a/activerecord/test/cases/migration/references_index_test.rb
+++ b/activerecord/test/cases/migration/references_index_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
module ActiveRecord
diff --git a/activerecord/test/cases/migration/references_statements_test.rb b/activerecord/test/cases/migration/references_statements_test.rb
index df15d7cb45..769241ba12 100644
--- a/activerecord/test/cases/migration/references_statements_test.rb
+++ b/activerecord/test/cases/migration/references_statements_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/migration/helper"
module ActiveRecord
@@ -50,6 +52,21 @@ module ActiveRecord
assert column_exists?(table_name, :taggable_type, :string, default: "Photo")
end
+ def test_creates_reference_type_column_with_not_null
+ connection.create_table table_name, force: true do |t|
+ t.references :taggable, null: false, polymorphic: true
+ end
+ assert column_exists?(table_name, :taggable_id, :integer, null: false)
+ assert column_exists?(table_name, :taggable_type, :string, null: false)
+ end
+
+ def test_does_not_share_options_with_reference_type_column
+ add_reference table_name, :taggable, type: :integer, limit: 2, polymorphic: true
+ assert column_exists?(table_name, :taggable_id, :integer, limit: 2)
+ assert column_exists?(table_name, :taggable_type, :string)
+ assert_not column_exists?(table_name, :taggable_type, :string, limit: 2)
+ end
+
def test_creates_named_index
add_reference table_name, :tag, index: { name: "index_taggings_on_tag_id" }
assert index_exists?(table_name, :tag_id, name: "index_taggings_on_tag_id")
diff --git a/activerecord/test/cases/migration/rename_table_test.rb b/activerecord/test/cases/migration/rename_table_test.rb
index 19588d28a2..a9deb92585 100644
--- a/activerecord/test/cases/migration/rename_table_test.rb
+++ b/activerecord/test/cases/migration/rename_table_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/migration/helper"
module ActiveRecord
@@ -79,10 +81,33 @@ module ActiveRecord
assert_equal ConnectionAdapters::PostgreSQL::Name.new("public", "octopi_#{pk}_seq"), seq
end
+ def test_renaming_table_renames_primary_key
+ connection.create_table :cats, id: :uuid, default: "uuid_generate_v4()"
+ rename_table :cats, :felines
+
+ assert connection.table_exists? :felines
+ assert_not connection.table_exists? :cats
+
+ primary_key_name = connection.select_values(<<~SQL, "SCHEMA")[0]
+ SELECT c.relname
+ FROM pg_class c
+ JOIN pg_index i
+ ON c.oid = i.indexrelid
+ WHERE i.indisprimary
+ AND i.indrelid = 'felines'::regclass
+ SQL
+
+ assert_equal "felines_pkey", primary_key_name
+ ensure
+ connection.drop_table :cats, if_exists: true
+ connection.drop_table :felines, if_exists: true
+ end
+
def test_renaming_table_doesnt_attempt_to_rename_non_existent_sequences
- connection.create_table :cats, id: :uuid
+ connection.create_table :cats, id: :uuid, default: "uuid_generate_v4()"
assert_nothing_raised { rename_table :cats, :felines }
assert connection.table_exists? :felines
+ assert_not connection.table_exists? :cats
ensure
connection.drop_table :cats, if_exists: true
connection.drop_table :felines, if_exists: true
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index fabb1662a3..868bb40ab2 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "cases/migration/helper"
require "bigdecimal/util"
@@ -69,6 +71,16 @@ class MigrationTest < ActiveRecord::TestCase
ActiveRecord::Migration.verbose = @verbose_was
end
+ def test_migrator_migrations_path_is_deprecated
+ assert_deprecated do
+ ActiveRecord::Migrator.migrations_path = "/whatever"
+ end
+ ensure
+ assert_deprecated do
+ ActiveRecord::Migrator.migrations_path = "db/migrate"
+ end
+ end
+
def test_migration_version_matches_component_version
assert_equal ActiveRecord::VERSION::STRING.to_f, ActiveRecord::Migration.current_version
end
@@ -76,20 +88,20 @@ class MigrationTest < ActiveRecord::TestCase
def test_migrator_versions
migrations_path = MIGRATIONS_ROOT + "/valid"
old_path = ActiveRecord::Migrator.migrations_paths
- ActiveRecord::Migrator.migrations_paths = migrations_path
+ migrator = ActiveRecord::MigrationContext.new(migrations_path)
- ActiveRecord::Migrator.up(migrations_path)
- assert_equal 3, ActiveRecord::Migrator.current_version
- assert_equal false, ActiveRecord::Migrator.needs_migration?
+ migrator.up
+ assert_equal 3, migrator.current_version
+ assert_equal false, migrator.needs_migration?
- ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid")
- assert_equal 0, ActiveRecord::Migrator.current_version
- assert_equal true, ActiveRecord::Migrator.needs_migration?
+ migrator.down
+ assert_equal 0, migrator.current_version
+ assert_equal true, migrator.needs_migration?
ActiveRecord::SchemaMigration.create!(version: 3)
- assert_equal true, ActiveRecord::Migrator.needs_migration?
+ assert_equal true, migrator.needs_migration?
ensure
- ActiveRecord::Migrator.migrations_paths = old_path
+ ActiveRecord::MigrationContext.new(old_path)
end
def test_migration_detection_without_schema_migration_table
@@ -97,28 +109,31 @@ class MigrationTest < ActiveRecord::TestCase
migrations_path = MIGRATIONS_ROOT + "/valid"
old_path = ActiveRecord::Migrator.migrations_paths
- ActiveRecord::Migrator.migrations_paths = migrations_path
+ migrator = ActiveRecord::MigrationContext.new(migrations_path)
- assert_equal true, ActiveRecord::Migrator.needs_migration?
+ assert_equal true, migrator.needs_migration?
ensure
- ActiveRecord::Migrator.migrations_paths = old_path
+ ActiveRecord::MigrationContext.new(old_path)
end
def test_any_migrations
old_path = ActiveRecord::Migrator.migrations_paths
- ActiveRecord::Migrator.migrations_paths = MIGRATIONS_ROOT + "/valid"
+ migrator = ActiveRecord::MigrationContext.new(MIGRATIONS_ROOT + "/valid")
- assert ActiveRecord::Migrator.any_migrations?
+ assert_predicate migrator, :any_migrations?
- ActiveRecord::Migrator.migrations_paths = MIGRATIONS_ROOT + "/empty"
+ migrator_empty = ActiveRecord::MigrationContext.new(MIGRATIONS_ROOT + "/empty")
- assert_not ActiveRecord::Migrator.any_migrations?
+ assert_not_predicate migrator_empty, :any_migrations?
ensure
- ActiveRecord::Migrator.migrations_paths = old_path
+ ActiveRecord::MigrationContext.new(old_path)
end
def test_migration_version
- assert_nothing_raised { ActiveRecord::Migrator.run(:up, MIGRATIONS_ROOT + "/version_check", 20131219224947) }
+ migrator = ActiveRecord::MigrationContext.new(MIGRATIONS_ROOT + "/version_check")
+ assert_equal 0, migrator.current_version
+ migrator.up(20131219224947)
+ assert_equal 20131219224947, migrator.current_version
end
def test_create_table_with_force_true_does_not_drop_nonexisting_table
@@ -155,14 +170,14 @@ class MigrationTest < ActiveRecord::TestCase
def test_add_table_with_decimals
Person.connection.drop_table :big_numbers rescue nil
- assert !BigNumber.table_exists?
+ assert_not_predicate BigNumber, :table_exists?
GiveMeBigNumbers.up
BigNumber.reset_column_information
assert BigNumber.create(
bank_balance: 1586.43,
big_bank_balance: BigDecimal("1000234000567.95"),
- world_population: 6000000000,
+ world_population: 2**62,
my_house_population: 3,
value_of_e: BigDecimal("2.7182818284590452353602875")
)
@@ -176,10 +191,8 @@ class MigrationTest < ActiveRecord::TestCase
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_equal 2**62, b.world_population
assert_kind_of Integer, b.my_house_population
assert_equal 3, b.my_house_population
assert_kind_of BigDecimal, b.bank_balance
@@ -214,15 +227,16 @@ class MigrationTest < ActiveRecord::TestCase
def test_filtering_migrations
assert_no_column Person, :last_name
- assert !Reminder.table_exists?
+ assert_not_predicate Reminder, :table_exists?
name_filter = lambda { |migration| migration.name == "ValidPeopleHaveLastNames" }
- ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", &name_filter)
+ migrator = ActiveRecord::MigrationContext.new(MIGRATIONS_ROOT + "/valid")
+ migrator.up(&name_filter)
assert_column Person, :last_name
assert_raise(ActiveRecord::StatementInvalid) { Reminder.first }
- ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid", &name_filter)
+ migrator.down(&name_filter)
assert_no_column Person, :last_name
assert_raise(ActiveRecord::StatementInvalid) { Reminder.first }
@@ -248,21 +262,21 @@ class MigrationTest < ActiveRecord::TestCase
def test_instance_based_migration_up
migration = MockMigration.new
- assert !migration.went_up, "have not gone up"
- assert !migration.went_down, "have not gone down"
+ assert_not migration.went_up, "have not gone up"
+ assert_not migration.went_down, "have not gone down"
migration.migrate :up
assert migration.went_up, "have gone up"
- assert !migration.went_down, "have not gone down"
+ assert_not migration.went_down, "have not gone down"
end
def test_instance_based_migration_down
migration = MockMigration.new
- assert !migration.went_up, "have not gone up"
- assert !migration.went_down, "have not gone down"
+ assert_not migration.went_up, "have not gone up"
+ assert_not migration.went_down, "have not gone down"
migration.migrate :down
- assert !migration.went_up, "have gone up"
+ assert_not migration.went_up, "have gone up"
assert migration.went_down, "have not gone down"
end
@@ -337,20 +351,20 @@ class MigrationTest < ActiveRecord::TestCase
end
def test_schema_migrations_table_name
- original_schema_migrations_table_name = ActiveRecord::Migrator.schema_migrations_table_name
+ original_schema_migrations_table_name = ActiveRecord::Base.schema_migrations_table_name
- assert_equal "schema_migrations", ActiveRecord::Migrator.schema_migrations_table_name
+ assert_equal "schema_migrations", ActiveRecord::SchemaMigration.table_name
ActiveRecord::Base.table_name_prefix = "prefix_"
ActiveRecord::Base.table_name_suffix = "_suffix"
Reminder.reset_table_name
- assert_equal "prefix_schema_migrations_suffix", ActiveRecord::Migrator.schema_migrations_table_name
+ assert_equal "prefix_schema_migrations_suffix", ActiveRecord::SchemaMigration.table_name
ActiveRecord::Base.schema_migrations_table_name = "changed"
Reminder.reset_table_name
- assert_equal "prefix_changed_suffix", ActiveRecord::Migrator.schema_migrations_table_name
+ assert_equal "prefix_changed_suffix", ActiveRecord::SchemaMigration.table_name
ActiveRecord::Base.table_name_prefix = ""
ActiveRecord::Base.table_name_suffix = ""
Reminder.reset_table_name
- assert_equal "changed", ActiveRecord::Migrator.schema_migrations_table_name
+ assert_equal "changed", ActiveRecord::SchemaMigration.table_name
ensure
ActiveRecord::Base.schema_migrations_table_name = original_schema_migrations_table_name
Reminder.reset_table_name
@@ -380,9 +394,9 @@ class MigrationTest < ActiveRecord::TestCase
current_env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
migrations_path = MIGRATIONS_ROOT + "/valid"
old_path = ActiveRecord::Migrator.migrations_paths
- ActiveRecord::Migrator.migrations_paths = migrations_path
+ migrator = ActiveRecord::MigrationContext.new(migrations_path)
- ActiveRecord::Migrator.up(migrations_path)
+ migrator.up
assert_equal current_env, ActiveRecord::InternalMetadata[:environment]
original_rails_env = ENV["RAILS_ENV"]
@@ -390,43 +404,16 @@ class MigrationTest < ActiveRecord::TestCase
ENV["RAILS_ENV"] = ENV["RACK_ENV"] = "foofoo"
new_env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
- refute_equal current_env, new_env
+ assert_not_equal current_env, new_env
sleep 1 # mysql by default does not store fractional seconds in the database
- ActiveRecord::Migrator.up(migrations_path)
+ migrator.up
assert_equal new_env, ActiveRecord::InternalMetadata[:environment]
ensure
- ActiveRecord::Migrator.migrations_paths = old_path
+ migrator = ActiveRecord::MigrationContext.new(old_path)
ENV["RAILS_ENV"] = original_rails_env
ENV["RACK_ENV"] = original_rack_env
- ActiveRecord::Migrator.up(migrations_path)
- end
-
- def test_migration_sets_internal_metadata_even_when_fully_migrated
- current_env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
- migrations_path = MIGRATIONS_ROOT + "/valid"
- old_path = ActiveRecord::Migrator.migrations_paths
- ActiveRecord::Migrator.migrations_paths = migrations_path
-
- ActiveRecord::Migrator.up(migrations_path)
- assert_equal current_env, ActiveRecord::InternalMetadata[:environment]
-
- original_rails_env = ENV["RAILS_ENV"]
- original_rack_env = ENV["RACK_ENV"]
- ENV["RAILS_ENV"] = ENV["RACK_ENV"] = "foofoo"
- new_env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
-
- refute_equal current_env, new_env
-
- sleep 1 # mysql by default does not store fractional seconds in the database
-
- ActiveRecord::Migrator.up(migrations_path)
- assert_equal new_env, ActiveRecord::InternalMetadata[:environment]
- ensure
- ActiveRecord::Migrator.migrations_paths = old_path
- ENV["RAILS_ENV"] = original_rails_env
- ENV["RACK_ENV"] = original_rack_env
- ActiveRecord::Migrator.up(migrations_path)
+ migrator.up
end
def test_internal_metadata_stores_environment_when_other_data_exists
@@ -436,14 +423,15 @@ class MigrationTest < ActiveRecord::TestCase
current_env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
migrations_path = MIGRATIONS_ROOT + "/valid"
old_path = ActiveRecord::Migrator.migrations_paths
- ActiveRecord::Migrator.migrations_paths = migrations_path
current_env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
- ActiveRecord::Migrator.up(migrations_path)
+ migrator = ActiveRecord::MigrationContext.new(migrations_path)
+ migrator.up
assert_equal current_env, ActiveRecord::InternalMetadata[:environment]
assert_equal "bar", ActiveRecord::InternalMetadata[:foo]
ensure
- ActiveRecord::Migrator.migrations_paths = old_path
+ migrator = ActiveRecord::MigrationContext.new(old_path)
+ migrator.up
end
def test_proper_table_name_on_migration
@@ -475,7 +463,7 @@ class MigrationTest < ActiveRecord::TestCase
end
def test_rename_table_with_prefix_and_suffix
- assert !Thing.table_exists?
+ assert_not_predicate Thing, :table_exists?
ActiveRecord::Base.table_name_prefix = "p_"
ActiveRecord::Base.table_name_suffix = "_s"
Thing.reset_table_name
@@ -496,7 +484,7 @@ class MigrationTest < ActiveRecord::TestCase
end
def test_add_drop_table_with_prefix_and_suffix
- assert !Reminder.table_exists?
+ assert_not_predicate Reminder, :table_exists?
ActiveRecord::Base.table_name_prefix = "prefix_"
ActiveRecord::Base.table_name_suffix = "_suffix"
Reminder.reset_table_name
@@ -529,11 +517,10 @@ class MigrationTest < ActiveRecord::TestCase
unless mysql_enforcing_gtid_consistency?
def test_create_table_with_query
- Person.connection.create_table(:person, force: true)
-
- Person.connection.create_table :table_from_query_testings, as: "SELECT id FROM person"
+ Person.connection.create_table :table_from_query_testings, as: "SELECT id FROM people WHERE id = 1"
columns = Person.connection.columns(:table_from_query_testings)
+ assert_equal [1], Person.connection.select_values("SELECT * FROM table_from_query_testings")
assert_equal 1, columns.length
assert_equal "id", columns.first.name
ensure
@@ -541,11 +528,10 @@ class MigrationTest < ActiveRecord::TestCase
end
def test_create_table_with_query_from_relation
- Person.connection.create_table(:person, force: true)
-
- Person.connection.create_table :table_from_query_testings, as: Person.select(:id)
+ Person.connection.create_table :table_from_query_testings, as: Person.select(:id).where(id: 1)
columns = Person.connection.columns(:table_from_query_testings)
+ assert_equal [1], Person.connection.select_values("SELECT * FROM table_from_query_testings")
assert_equal 1, columns.length
assert_equal "id", columns.first.name
ensure
@@ -562,7 +548,7 @@ class MigrationTest < ActiveRecord::TestCase
end
assert Person.connection.column_exists?(:something, :foo)
assert_nothing_raised { Person.connection.remove_column :something, :foo, :bar }
- assert !Person.connection.column_exists?(:something, :foo)
+ assert_not Person.connection.column_exists?(:something, :foo)
assert Person.connection.column_exists?(:something, :name)
assert Person.connection.column_exists?(:something, :number)
ensure
@@ -705,6 +691,25 @@ class MigrationTest < ActiveRecord::TestCase
assert_no_column Person, :last_name,
"without an advisory lock, the Migrator should not make any changes, but it did."
end
+
+ def test_with_advisory_lock_raises_the_right_error_when_it_fails_to_release_lock
+ migration = Class.new(ActiveRecord::Migration::Current).new
+ migrator = ActiveRecord::Migrator.new(:up, [migration], 100)
+ lock_id = migrator.send(:generate_migrator_advisory_lock_id)
+
+ e = assert_raises(ActiveRecord::ConcurrentMigrationError) do
+ silence_stream($stderr) do
+ migrator.send(:with_advisory_lock) do
+ ActiveRecord::Base.connection.release_advisory_lock(lock_id)
+ end
+ end
+ end
+
+ assert_match(
+ /#{ActiveRecord::ConcurrentMigrationError::RELEASE_LOCK_FAILED_MESSAGE}/,
+ e.message
+ )
+ end
end
private
@@ -788,12 +793,20 @@ if ActiveRecord::Base.connection.supports_bulk_alter?
end
def test_adding_multiple_columns
- assert_queries(1) do
+ classname = ActiveRecord::Base.connection.class.name[/[^:]*$/]
+ expected_query_count = {
+ "Mysql2Adapter" => 1,
+ "PostgreSQLAdapter" => 2, # one for bulk change, one for comment
+ }.fetch(classname) {
+ raise "need an expected query count for #{classname}"
+ }
+
+ assert_queries(expected_query_count) do
with_bulk_change_table do |t|
t.column :name, :string
t.string :qualification, :experience
t.integer :age, default: 0
- t.date :birthdate
+ t.date :birthdate, comment: "This is a comment"
t.timestamps null: true
end
end
@@ -801,6 +814,7 @@ if ActiveRecord::Base.connection.supports_bulk_alter?
assert_equal 8, columns.size
[:name, :qualification, :experience].each { |s| assert_equal :string, column(s).type }
assert_equal "0", column(:age).default
+ assert_equal "This is a comment", column(:birthdate).comment
end
def test_removing_columns
@@ -817,7 +831,7 @@ if ActiveRecord::Base.connection.supports_bulk_alter?
end
end
- [:qualification, :experience].each { |c| assert ! column(c) }
+ [:qualification, :experience].each { |c| assert_not column(c) }
assert column(:qualification_experience)
end
@@ -828,8 +842,15 @@ if ActiveRecord::Base.connection.supports_bulk_alter?
t.integer :age
end
- # Adding an index fires a query every time to check if an index already exists or not
- assert_queries(3) do
+ classname = ActiveRecord::Base.connection.class.name[/[^:]*$/]
+ expected_query_count = {
+ "Mysql2Adapter" => 3, # Adding an index fires a query every time to check if an index already exists or not
+ "PostgreSQLAdapter" => 2,
+ }.fetch(classname) {
+ raise "need an expected query count for #{classname}"
+ }
+
+ assert_queries(expected_query_count) do
with_bulk_change_table do |t|
t.index :username, unique: true, name: :awesome_username_index
t.index [:name, :age]
@@ -840,7 +861,7 @@ if ActiveRecord::Base.connection.supports_bulk_alter?
name_age_index = index(:index_delete_me_on_name_and_age)
assert_equal ["name", "age"].sort, name_age_index.columns.sort
- assert ! name_age_index.unique
+ assert_not name_age_index.unique
assert index(:awesome_username_index).unique
end
@@ -853,14 +874,22 @@ if ActiveRecord::Base.connection.supports_bulk_alter?
assert index(:index_delete_me_on_name)
- assert_queries(3) do
+ classname = ActiveRecord::Base.connection.class.name[/[^:]*$/]
+ expected_query_count = {
+ "Mysql2Adapter" => 3, # Adding an index fires a query every time to check if an index already exists or not
+ "PostgreSQLAdapter" => 2,
+ }.fetch(classname) {
+ raise "need an expected query count for #{classname}"
+ }
+
+ assert_queries(expected_query_count) do
with_bulk_change_table do |t|
t.remove_index :name
t.index :name, name: :new_name_index, unique: true
end
end
- assert ! index(:index_delete_me_on_name)
+ assert_not index(:index_delete_me_on_name)
new_name_index = index(:new_name_index)
assert new_name_index.unique
@@ -872,21 +901,27 @@ if ActiveRecord::Base.connection.supports_bulk_alter?
t.date :birthdate
end
- assert ! column(:name).default
+ assert_not column(:name).default
assert_equal :date, column(:birthdate).type
- # One query for columns (delete_me table)
- # One query for primary key (delete_me table)
- # One query to do the bulk change
- assert_queries(3, ignore_none: true) do
+ classname = ActiveRecord::Base.connection.class.name[/[^:]*$/]
+ expected_query_count = {
+ "Mysql2Adapter" => 3, # one query for columns, one query for primary key, one query to do the bulk change
+ "PostgreSQLAdapter" => 3, # one query for columns, one for bulk change, one for comment
+ }.fetch(classname) {
+ raise "need an expected query count for #{classname}"
+ }
+
+ assert_queries(expected_query_count, ignore_none: true) do
with_bulk_change_table do |t|
t.change :name, :string, default: "NONAME"
- t.change :birthdate, :datetime
+ t.change :birthdate, :datetime, comment: "This is a comment"
end
end
assert_equal "NONAME", column(:name).default
assert_equal :datetime, column(:birthdate).type
+ assert_equal "This is a comment", column(:birthdate).comment
end
private
@@ -941,12 +976,12 @@ class CopyMigrationsTest < ActiveRecord::TestCase
assert_equal [@migrations_path + "/4_people_have_hobbies.bukkits.rb", @migrations_path + "/5_people_have_descriptions.bukkits.rb"], copied.map(&:filename)
expected = "# This migration comes from bukkits (originally 1)"
- assert_equal expected, IO.readlines(@migrations_path + "/4_people_have_hobbies.bukkits.rb")[0].chomp
+ assert_equal expected, IO.readlines(@migrations_path + "/4_people_have_hobbies.bukkits.rb")[1].chomp
files_count = Dir[@migrations_path + "/*.rb"].length
copied = ActiveRecord::Migration.copy(@migrations_path, bukkits: MIGRATIONS_ROOT + "/to_copy")
assert_equal files_count, Dir[@migrations_path + "/*.rb"].length
- assert copied.empty?
+ assert_empty copied
ensure
clear
end
@@ -987,7 +1022,7 @@ class CopyMigrationsTest < ActiveRecord::TestCase
files_count = Dir[@migrations_path + "/*.rb"].length
copied = ActiveRecord::Migration.copy(@migrations_path, bukkits: MIGRATIONS_ROOT + "/to_copy_with_timestamps")
assert_equal files_count, Dir[@migrations_path + "/*.rb"].length
- assert copied.empty?
+ assert_empty copied
end
ensure
clear
@@ -1029,7 +1064,7 @@ class CopyMigrationsTest < ActiveRecord::TestCase
files_count = Dir[@migrations_path + "/*.rb"].length
copied = ActiveRecord::Migration.copy(@migrations_path, bukkits: MIGRATIONS_ROOT + "/to_copy_with_timestamps")
assert_equal files_count, Dir[@migrations_path + "/*.rb"].length
- assert copied.empty?
+ assert_empty copied
end
ensure
clear
@@ -1044,13 +1079,13 @@ class CopyMigrationsTest < ActiveRecord::TestCase
assert File.exist?(@migrations_path + "/4_currencies_have_symbols.bukkits.rb")
assert_equal [@migrations_path + "/4_currencies_have_symbols.bukkits.rb"], copied.map(&:filename)
- expected = "# coding: ISO-8859-15\n# This migration comes from bukkits (originally 1)"
- assert_equal expected, IO.readlines(@migrations_path + "/4_currencies_have_symbols.bukkits.rb")[0..1].join.chomp
+ expected = "# frozen_string_literal: true\n# coding: ISO-8859-15\n# This migration comes from bukkits (originally 1)"
+ assert_equal expected, IO.readlines(@migrations_path + "/4_currencies_have_symbols.bukkits.rb")[0..2].join.chomp
files_count = Dir[@migrations_path + "/*.rb"].length
copied = ActiveRecord::Migration.copy(@migrations_path, bukkits: MIGRATIONS_ROOT + "/magic")
assert_equal files_count, Dir[@migrations_path + "/*.rb"].length
- assert copied.empty?
+ assert_empty copied
ensure
clear
end
@@ -1133,17 +1168,4 @@ class CopyMigrationsTest < ActiveRecord::TestCase
def test_unknown_migration_version_should_raise_an_argument_error
assert_raise(ArgumentError) { ActiveRecord::Migration[1.0] }
end
-
- def test_deprecate_initialize_internal_tables
- assert_deprecated { ActiveRecord::Base.connection.initialize_schema_migrations_table }
- assert_deprecated { ActiveRecord::Base.connection.initialize_internal_metadata_table }
- end
-
- def test_deprecate_migration_keys
- assert_deprecated { ActiveRecord::Base.connection.migration_keys }
- end
-
- def test_deprecate_supports_migrations
- assert_deprecated { ActiveRecord::Base.connection.supports_migrations? }
- end
end
diff --git a/activerecord/test/cases/migrator_test.rb b/activerecord/test/cases/migrator_test.rb
index 20d70b75ac..873455cf67 100644
--- a/activerecord/test/cases/migrator_test.rb
+++ b/activerecord/test/cases/migrator_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "cases/migration/helper"
@@ -64,10 +66,30 @@ class MigratorTest < ActiveRecord::TestCase
list = [ActiveRecord::Migration.new("Foo", 1), ActiveRecord::Migration.new("Bar", 2)]
ActiveRecord::Migrator.new(:up, list, 3).run
end
+
+ assert_raises(ActiveRecord::UnknownMigrationVersionError) do
+ list = [ActiveRecord::Migration.new("Foo", 1), ActiveRecord::Migration.new("Bar", 2)]
+ ActiveRecord::Migrator.new(:up, list, -1).run
+ end
+
+ assert_raises(ActiveRecord::UnknownMigrationVersionError) do
+ list = [ActiveRecord::Migration.new("Foo", 1), ActiveRecord::Migration.new("Bar", 2)]
+ ActiveRecord::Migrator.new(:up, list, 0).run
+ end
+
+ assert_raises(ActiveRecord::UnknownMigrationVersionError) do
+ list = [ActiveRecord::Migration.new("Foo", 1), ActiveRecord::Migration.new("Bar", 2)]
+ ActiveRecord::Migrator.new(:up, list, 3).migrate
+ end
+
+ assert_raises(ActiveRecord::UnknownMigrationVersionError) do
+ list = [ActiveRecord::Migration.new("Foo", 1), ActiveRecord::Migration.new("Bar", 2)]
+ ActiveRecord::Migrator.new(:up, list, -1).migrate
+ end
end
def test_finds_migrations
- migrations = ActiveRecord::Migrator.migrations(MIGRATIONS_ROOT + "/valid")
+ migrations = ActiveRecord::MigrationContext.new(MIGRATIONS_ROOT + "/valid").migrations
[[1, "ValidPeopleHaveLastNames"], [2, "WeNeedReminders"], [3, "InnocentJointable"]].each_with_index do |pair, i|
assert_equal migrations[i].version, pair.first
@@ -76,7 +98,8 @@ class MigratorTest < ActiveRecord::TestCase
end
def test_finds_migrations_in_subdirectories
- migrations = ActiveRecord::Migrator.migrations(MIGRATIONS_ROOT + "/valid_with_subdirectories")
+ migrations = ActiveRecord::MigrationContext.new(MIGRATIONS_ROOT + "/valid_with_subdirectories").migrations
+
[[1, "ValidPeopleHaveLastNames"], [2, "WeNeedReminders"], [3, "InnocentJointable"]].each_with_index do |pair, i|
assert_equal migrations[i].version, pair.first
@@ -86,7 +109,7 @@ class MigratorTest < ActiveRecord::TestCase
def test_finds_migrations_from_two_directories
directories = [MIGRATIONS_ROOT + "/valid_with_timestamps", MIGRATIONS_ROOT + "/to_copy_with_timestamps"]
- migrations = ActiveRecord::Migrator.migrations directories
+ migrations = ActiveRecord::MigrationContext.new(directories).migrations
[[20090101010101, "PeopleHaveHobbies"],
[20090101010202, "PeopleHaveDescriptions"],
@@ -99,14 +122,14 @@ class MigratorTest < ActiveRecord::TestCase
end
def test_finds_migrations_in_numbered_directory
- migrations = ActiveRecord::Migrator.migrations [MIGRATIONS_ROOT + "/10_urban"]
+ migrations = ActiveRecord::MigrationContext.new(MIGRATIONS_ROOT + "/10_urban").migrations
assert_equal 9, migrations[0].version
assert_equal "AddExpressions", migrations[0].name
end
def test_relative_migrations
list = Dir.chdir(MIGRATIONS_ROOT) do
- ActiveRecord::Migrator.migrations("valid")
+ ActiveRecord::MigrationContext.new("valid").migrations
end
migration_proxy = list.find { |item|
@@ -124,6 +147,67 @@ class MigratorTest < ActiveRecord::TestCase
assert_equal migration_list.last, migrations.first
end
+ def test_migrations_status
+ path = MIGRATIONS_ROOT + "/valid"
+
+ ActiveRecord::SchemaMigration.create(version: 2)
+ ActiveRecord::SchemaMigration.create(version: 10)
+
+ assert_equal [
+ ["down", "001", "Valid people have last names"],
+ ["up", "002", "We need reminders"],
+ ["down", "003", "Innocent jointable"],
+ ["up", "010", "********** NO FILE **********"],
+ ], ActiveRecord::MigrationContext.new(path).migrations_status
+ end
+
+ def test_migrations_status_in_subdirectories
+ path = MIGRATIONS_ROOT + "/valid_with_subdirectories"
+
+ ActiveRecord::SchemaMigration.create(version: 2)
+ ActiveRecord::SchemaMigration.create(version: 10)
+
+ assert_equal [
+ ["down", "001", "Valid people have last names"],
+ ["up", "002", "We need reminders"],
+ ["down", "003", "Innocent jointable"],
+ ["up", "010", "********** NO FILE **********"],
+ ], ActiveRecord::MigrationContext.new(path).migrations_status
+ end
+
+ def test_migrations_status_with_schema_define_in_subdirectories
+ path = MIGRATIONS_ROOT + "/valid_with_subdirectories"
+ prev_paths = ActiveRecord::Migrator.migrations_paths
+ ActiveRecord::Migrator.migrations_paths = path
+
+ ActiveRecord::Schema.define(version: 3) do
+ end
+
+ assert_equal [
+ ["up", "001", "Valid people have last names"],
+ ["up", "002", "We need reminders"],
+ ["up", "003", "Innocent jointable"],
+ ], ActiveRecord::MigrationContext.new(path).migrations_status
+ ensure
+ ActiveRecord::Migrator.migrations_paths = prev_paths
+ end
+
+ def test_migrations_status_from_two_directories
+ paths = [MIGRATIONS_ROOT + "/valid_with_timestamps", MIGRATIONS_ROOT + "/to_copy_with_timestamps"]
+
+ ActiveRecord::SchemaMigration.create(version: "20100101010101")
+ ActiveRecord::SchemaMigration.create(version: "20160528010101")
+
+ assert_equal [
+ ["down", "20090101010101", "People have hobbies"],
+ ["down", "20090101010202", "People have descriptions"],
+ ["up", "20100101010101", "Valid with timestamps people have last names"],
+ ["down", "20100201010101", "Valid with timestamps we need reminders"],
+ ["down", "20100301010101", "Valid with timestamps innocent jointable"],
+ ["up", "20160528010101", "********** NO FILE **********"],
+ ], ActiveRecord::MigrationContext.new(paths).migrations_status
+ end
+
def test_migrator_interleaved_migrations
pass_one = [Sensor.new("One", 1)]
@@ -149,25 +233,28 @@ class MigratorTest < ActiveRecord::TestCase
def test_up_calls_up
migrations = [Sensor.new(nil, 0), Sensor.new(nil, 1), Sensor.new(nil, 2)]
- ActiveRecord::Migrator.new(:up, migrations).migrate
+ migrator = ActiveRecord::Migrator.new(:up, migrations)
+ migrator.migrate
assert migrations.all?(&:went_up)
assert migrations.all? { |m| !m.went_down }
- assert_equal 2, ActiveRecord::Migrator.current_version
+ assert_equal 2, migrator.current_version
end
def test_down_calls_down
test_up_calls_up
migrations = [Sensor.new(nil, 0), Sensor.new(nil, 1), Sensor.new(nil, 2)]
- ActiveRecord::Migrator.new(:down, migrations).migrate
+ migrator = ActiveRecord::Migrator.new(:down, migrations)
+ migrator.migrate
assert migrations.all? { |m| !m.went_up }
assert migrations.all?(&:went_down)
- assert_equal 0, ActiveRecord::Migrator.current_version
+ assert_equal 0, migrator.current_version
end
def test_current_version
ActiveRecord::SchemaMigration.create!(version: "1000")
- assert_equal 1000, ActiveRecord::Migrator.current_version
+ migrator = ActiveRecord::MigrationContext.new("db/migrate")
+ assert_equal 1000, migrator.current_version
end
def test_migrator_one_up
@@ -206,38 +293,42 @@ class MigratorTest < ActiveRecord::TestCase
def test_migrator_double_up
calls, migrations = sensors(3)
- assert_equal(0, ActiveRecord::Migrator.current_version)
+ migrator = ActiveRecord::Migrator.new(:up, migrations, 1)
+ assert_equal(0, migrator.current_version)
- ActiveRecord::Migrator.new(:up, migrations, 1).migrate
+ migrator.migrate
assert_equal [[:up, 1]], calls
calls.clear
- ActiveRecord::Migrator.new(:up, migrations, 1).migrate
+ migrator.migrate
assert_equal [], calls
end
def test_migrator_double_down
calls, migrations = sensors(3)
+ migrator = ActiveRecord::Migrator.new(:up, migrations, 1)
- assert_equal(0, ActiveRecord::Migrator.current_version)
+ assert_equal 0, migrator.current_version
- ActiveRecord::Migrator.new(:up, migrations, 1).run
+ migrator.run
assert_equal [[:up, 1]], calls
calls.clear
- ActiveRecord::Migrator.new(:down, migrations, 1).run
+ migrator = ActiveRecord::Migrator.new(:down, migrations, 1)
+ migrator.run
assert_equal [[:down, 1]], calls
calls.clear
- ActiveRecord::Migrator.new(:down, migrations, 1).run
+ migrator.run
assert_equal [], calls
- assert_equal(0, ActiveRecord::Migrator.current_version)
+ assert_equal 0, migrator.current_version
end
def test_migrator_verbosity
_, migrations = sensors(3)
+ ActiveRecord::Migration.verbose = true
ActiveRecord::Migrator.new(:up, migrations, 1).migrate
assert_not_equal 0, ActiveRecord::Migration.message_count
@@ -250,7 +341,6 @@ class MigratorTest < ActiveRecord::TestCase
def test_migrator_verbosity_off
_, migrations = sensors(3)
- ActiveRecord::Migration.message_count = 0
ActiveRecord::Migration.verbose = false
ActiveRecord::Migrator.new(:up, migrations, 1).migrate
assert_equal 0, ActiveRecord::Migration.message_count
@@ -278,78 +368,85 @@ class MigratorTest < ActiveRecord::TestCase
def test_migrator_going_down_due_to_version_target
calls, migrator = migrator_class(3)
+ migrator = migrator.new("valid")
- migrator.up("valid", 1)
+ migrator.up(1)
assert_equal [[:up, 1]], calls
calls.clear
- migrator.migrate("valid", 0)
+ migrator.migrate(0)
assert_equal [[:down, 1]], calls
calls.clear
- migrator.migrate("valid")
+ migrator.migrate
assert_equal [[:up, 1], [:up, 2], [:up, 3]], calls
end
def test_migrator_output_when_running_multiple_migrations
_, migrator = migrator_class(3)
+ migrator = migrator.new("valid")
- result = migrator.migrate("valid")
+ result = migrator.migrate
assert_equal(3, result.count)
# Nothing migrated from duplicate run
- result = migrator.migrate("valid")
+ result = migrator.migrate
assert_equal(0, result.count)
- result = migrator.rollback("valid")
+ result = migrator.rollback
assert_equal(1, result.count)
end
def test_migrator_output_when_running_single_migration
_, migrator = migrator_class(1)
- result = migrator.run(:up, "valid", 1)
+ migrator = migrator.new("valid")
+
+ result = migrator.run(:up, 1)
assert_equal(1, result.version)
end
def test_migrator_rollback
_, migrator = migrator_class(3)
+ migrator = migrator.new("valid")
- migrator.migrate("valid")
- assert_equal(3, ActiveRecord::Migrator.current_version)
+ migrator.migrate
+ assert_equal(3, migrator.current_version)
- migrator.rollback("valid")
- assert_equal(2, ActiveRecord::Migrator.current_version)
+ migrator.rollback
+ assert_equal(2, migrator.current_version)
- migrator.rollback("valid")
- assert_equal(1, ActiveRecord::Migrator.current_version)
+ migrator.rollback
+ assert_equal(1, migrator.current_version)
- migrator.rollback("valid")
- assert_equal(0, ActiveRecord::Migrator.current_version)
+ migrator.rollback
+ assert_equal(0, migrator.current_version)
- migrator.rollback("valid")
- assert_equal(0, ActiveRecord::Migrator.current_version)
+ migrator.rollback
+ assert_equal(0, migrator.current_version)
end
def test_migrator_db_has_no_schema_migrations_table
_, migrator = migrator_class(3)
+ migrator = migrator.new("valid")
ActiveRecord::Base.connection.drop_table "schema_migrations", if_exists: true
assert_not ActiveRecord::Base.connection.table_exists?("schema_migrations")
- migrator.migrate("valid", 1)
+ migrator.migrate(1)
assert ActiveRecord::Base.connection.table_exists?("schema_migrations")
end
def test_migrator_forward
_, migrator = migrator_class(3)
- migrator.migrate("/valid", 1)
- assert_equal(1, ActiveRecord::Migrator.current_version)
+ migrator = migrator.new("/valid")
+ migrator.migrate(1)
+ assert_equal(1, migrator.current_version)
- migrator.forward("/valid", 2)
- assert_equal(3, ActiveRecord::Migrator.current_version)
+ migrator.forward(2)
+ assert_equal(3, migrator.current_version)
- migrator.forward("/valid")
- assert_equal(3, ActiveRecord::Migrator.current_version)
+ migrator.forward
+ assert_equal(3, migrator.current_version)
end
def test_only_loads_pending_migrations
@@ -357,25 +454,27 @@ class MigratorTest < ActiveRecord::TestCase
ActiveRecord::SchemaMigration.create!(version: "1")
calls, migrator = migrator_class(3)
- migrator.migrate("valid", nil)
+ migrator = migrator.new("valid")
+ migrator.migrate
assert_equal [[:up, 2], [:up, 3]], calls
end
def test_get_all_versions
_, migrator = migrator_class(3)
+ migrator = migrator.new("valid")
- migrator.migrate("valid")
- assert_equal([1, 2, 3], ActiveRecord::Migrator.get_all_versions)
+ migrator.migrate
+ assert_equal([1, 2, 3], migrator.get_all_versions)
- migrator.rollback("valid")
- assert_equal([1, 2], ActiveRecord::Migrator.get_all_versions)
+ migrator.rollback
+ assert_equal([1, 2], migrator.get_all_versions)
- migrator.rollback("valid")
- assert_equal([1], ActiveRecord::Migrator.get_all_versions)
+ migrator.rollback
+ assert_equal([1], migrator.get_all_versions)
- migrator.rollback("valid")
- assert_equal([], ActiveRecord::Migrator.get_all_versions)
+ migrator.rollback
+ assert_equal([], migrator.get_all_versions)
end
private
@@ -400,11 +499,11 @@ class MigratorTest < ActiveRecord::TestCase
def migrator_class(count)
calls, migrations = sensors(count)
- migrator = Class.new(ActiveRecord::Migrator).extend(Module.new {
- define_method(:migrations) { |paths|
+ migrator = Class.new(ActiveRecord::MigrationContext) {
+ define_method(:migrations) { |*|
migrations
}
- })
+ }
[calls, migrator]
end
end
diff --git a/activerecord/test/cases/mixin_test.rb b/activerecord/test/cases/mixin_test.rb
index a8af8e30f7..fdb8ac6ab3 100644
--- a/activerecord/test/cases/mixin_test.rb
+++ b/activerecord/test/cases/mixin_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
class Mixin < ActiveRecord::Base
@@ -10,10 +12,6 @@ class TouchTest < ActiveRecord::TestCase
travel_to Time.now
end
- teardown do
- travel_back
- end
-
def test_update
stamped = Mixin.new
diff --git a/activerecord/test/cases/modules_test.rb b/activerecord/test/cases/modules_test.rb
index f8a7bab35f..87455e4fcb 100644
--- a/activerecord/test/cases/modules_test.rb
+++ b/activerecord/test/cases/modules_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/company_in_module"
require "models/shop"
@@ -30,7 +32,7 @@ class ModulesTest < ActiveRecord::TestCase
def test_module_spanning_associations
firm = MyApplication::Business::Firm.first
- assert !firm.clients.empty?, "Firm should have clients"
+ assert_not 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
@@ -153,7 +155,7 @@ class ModulesTest < ActiveRecord::TestCase
ActiveRecord::Base.store_full_sti_class = true
collection = Shop::Collection.first
- assert !collection.products.empty?, "Collection should have products"
+ assert_not collection.products.empty?, "Collection should have products"
assert_nothing_raised { collection.destroy }
ensure
ActiveRecord::Base.store_full_sti_class = old
@@ -164,7 +166,7 @@ class ModulesTest < ActiveRecord::TestCase
ActiveRecord::Base.store_full_sti_class = true
product = Shop::Product.first
- assert !product.variants.empty?, "Product should have variants"
+ assert_not product.variants.empty?, "Product should have variants"
assert_nothing_raised { product.destroy }
ensure
ActiveRecord::Base.store_full_sti_class = old
diff --git a/activerecord/test/cases/multiparameter_attributes_test.rb b/activerecord/test/cases/multiparameter_attributes_test.rb
index ceb5724377..6f3903eed4 100644
--- a/activerecord/test/cases/multiparameter_attributes_test.rb
+++ b/activerecord/test/cases/multiparameter_attributes_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/topic"
require "models/customer"
@@ -225,7 +227,7 @@ class MultiParameterAttributeTest < ActiveRecord::TestCase
topic = Topic.find(1)
topic.attributes = attributes
assert_equal Time.local(2004, 6, 24, 16, 24, 0), topic.written_on
- assert_equal false, topic.written_on.respond_to?(:time_zone)
+ assert_not_respond_to topic.written_on, :time_zone
end
end
@@ -240,7 +242,7 @@ class MultiParameterAttributeTest < ActiveRecord::TestCase
topic = Topic.find(1)
topic.attributes = attributes
assert_equal Time.utc(2004, 6, 24, 16, 24, 0), topic.written_on
- assert_equal false, topic.written_on.respond_to?(:time_zone)
+ assert_not_respond_to topic.written_on, :time_zone
end
ensure
Topic.skip_time_zone_conversion_for_attributes = []
@@ -259,7 +261,7 @@ class MultiParameterAttributeTest < ActiveRecord::TestCase
topic = Topic.find(1)
topic.attributes = attributes
assert_equal Time.zone.local(2000, 1, 1, 16, 24, 0), topic.bonus_time
- assert_not topic.bonus_time.utc?
+ assert_not_predicate topic.bonus_time, :utc?
attributes = {
"written_on(1i)" => "2000", "written_on(2i)" => "", "written_on(3i)" => "",
@@ -271,6 +273,12 @@ class MultiParameterAttributeTest < ActiveRecord::TestCase
ensure
Topic.reset_column_information
end
+
+ def test_multiparameter_attributes_setting_time_attribute
+ topic = Topic.new("bonus_time(4i)" => "01", "bonus_time(5i)" => "05")
+ assert_equal 1, topic.bonus_time.hour
+ assert_equal 5, topic.bonus_time.min
+ end
end
def test_multiparameter_attributes_on_time_with_empty_seconds
@@ -285,14 +293,6 @@ class MultiParameterAttributeTest < ActiveRecord::TestCase
end
end
- unless current_adapter? :OracleAdapter
- def test_multiparameter_attributes_setting_time_attribute
- topic = Topic.new("bonus_time(4i)" => "01", "bonus_time(5i)" => "05")
- assert_equal 1, topic.bonus_time.hour
- assert_equal 5, topic.bonus_time.min
- end
- end
-
def test_multiparameter_attributes_setting_date_attribute
topic = Topic.new("written_on(1i)" => "1952", "written_on(2i)" => "3", "written_on(3i)" => "11")
assert_equal 1952, topic.written_on.year
@@ -300,13 +300,34 @@ class MultiParameterAttributeTest < ActiveRecord::TestCase
assert_equal 11, topic.written_on.day
end
+ def test_create_with_multiparameter_attributes_setting_date_attribute
+ topic = Topic.create_with("written_on(1i)" => "1952", "written_on(2i)" => "3", "written_on(3i)" => "11").new
+ assert_equal 1952, topic.written_on.year
+ assert_equal 3, topic.written_on.month
+ assert_equal 11, topic.written_on.day
+ end
+
def test_multiparameter_attributes_setting_date_and_time_attribute
topic = Topic.new(
- "written_on(1i)" => "1952",
- "written_on(2i)" => "3",
- "written_on(3i)" => "11",
- "written_on(4i)" => "13",
- "written_on(5i)" => "55")
+ "written_on(1i)" => "1952",
+ "written_on(2i)" => "3",
+ "written_on(3i)" => "11",
+ "written_on(4i)" => "13",
+ "written_on(5i)" => "55")
+ assert_equal 1952, topic.written_on.year
+ assert_equal 3, topic.written_on.month
+ assert_equal 11, topic.written_on.day
+ assert_equal 13, topic.written_on.hour
+ assert_equal 55, topic.written_on.min
+ end
+
+ def test_create_with_multiparameter_attributes_setting_date_and_time_attribute
+ topic = Topic.create_with(
+ "written_on(1i)" => "1952",
+ "written_on(2i)" => "3",
+ "written_on(3i)" => "11",
+ "written_on(4i)" => "13",
+ "written_on(5i)" => "55").new
assert_equal 1952, topic.written_on.year
assert_equal 3, topic.written_on.month
assert_equal 11, topic.written_on.day
@@ -364,4 +385,15 @@ class MultiParameterAttributeTest < ActiveRecord::TestCase
assert_equal("address", ex.errors[0].attribute)
end
+
+ def test_multiparameter_assigned_attributes_did_not_come_from_user
+ topic = Topic.new(
+ "written_on(1i)" => "1952",
+ "written_on(2i)" => "3",
+ "written_on(3i)" => "11",
+ "written_on(4i)" => "13",
+ "written_on(5i)" => "55",
+ )
+ assert_not_predicate topic, :written_on_came_from_user?
+ end
end
diff --git a/activerecord/test/cases/multiple_db_test.rb b/activerecord/test/cases/multiple_db_test.rb
index e3bb51bd77..192d2f5251 100644
--- a/activerecord/test/cases/multiple_db_test.rb
+++ b/activerecord/test/cases/multiple_db_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/entrant"
require "models/bird"
@@ -90,14 +92,9 @@ class MultipleDbTest < ActiveRecord::TestCase
assert_equal "Ruby Developer", Entrant.find(1).name
end
- def test_arel_table_engines
- assert_not_equal Entrant.arel_engine, Bird.arel_engine
- assert_not_equal Entrant.arel_engine, Course.arel_engine
- end
-
def test_connection
- assert_equal Entrant.arel_engine.connection.object_id, Bird.arel_engine.connection.object_id
- assert_not_equal Entrant.arel_engine.connection.object_id, Course.arel_engine.connection.object_id
+ assert_same Entrant.connection, Bird.connection
+ assert_not_same Entrant.connection, Course.connection
end
unless in_memory_db?
diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb
index b87419d203..bb1c1ea17d 100644
--- a/activerecord/test/cases/nested_attributes_test.rb
+++ b/activerecord/test/cases/nested_attributes_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/pirate"
require "models/ship"
@@ -34,7 +36,7 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase
pirate.birds_with_reject_all_blank_attributes = [{ name: "", color: "", _destroy: "0" }]
pirate.save!
- assert pirate.birds_with_reject_all_blank.empty?
+ assert_empty pirate.birds_with_reject_all_blank
end
def test_should_not_build_a_new_record_if_reject_all_blank_returns_false
@@ -42,7 +44,7 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase
pirate.birds_with_reject_all_blank_attributes = [{ name: "", color: "" }]
pirate.save!
- assert pirate.birds_with_reject_all_blank.empty?
+ assert_empty pirate.birds_with_reject_all_blank
end
def test_should_build_a_new_record_if_reject_all_blank_does_not_return_false
@@ -81,7 +83,7 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase
def test_a_model_should_respond_to_underscore_destroy_and_return_if_it_is_marked_for_destruction
ship = Ship.create!(name: "Nights Dirty Lightning")
- assert !ship._destroy
+ assert_not ship._destroy
ship.mark_for_destruction
assert ship._destroy
end
@@ -117,7 +119,7 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase
def test_reject_if_with_a_proc_which_returns_true_always_for_has_one
Pirate.accepts_nested_attributes_for :ship, reject_if: proc { |attributes| true }
- pirate = Pirate.new(catchphrase: "Stop wastin' me time")
+ pirate = Pirate.create(catchphrase: "Stop wastin' me time")
ship = pirate.create_ship(name: "s1")
pirate.update(ship_attributes: { name: "s2", id: ship.id })
assert_equal "s1", ship.reload.name
@@ -150,7 +152,7 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase
man = Man.create(name: "Jon")
interest = man.interests.create(topic: "the ladies")
man.update(interests_attributes: { _destroy: "1", id: interest.id })
- assert man.reload.interests.empty?
+ assert_empty man.reload.interests
end
def test_reject_if_is_not_short_circuited_if_allow_destroy_is_false
@@ -215,6 +217,18 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase
mean_pirate.parrot_attributes = { name: "James" }
assert_equal "James", mean_pirate.parrot.name
end
+
+ def test_should_not_create_duplicates_with_create_with
+ Man.accepts_nested_attributes_for(:interests)
+
+ assert_difference("Interest.count", 1) do
+ Man.create_with(
+ interests_attributes: [{ topic: "Pirate king" }]
+ ).find_or_create_by!(
+ name: "Monkey D. Luffy"
+ )
+ end
+ end
end
class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase
@@ -238,7 +252,7 @@ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase
@ship.destroy
@pirate.reload.ship_attributes = { name: "Davy Jones Gold Dagger" }
- assert !@pirate.ship.persisted?
+ assert_not_predicate @pirate.ship, :persisted?
assert_equal "Davy Jones Gold Dagger", @pirate.ship.name
end
@@ -259,7 +273,7 @@ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase
def test_should_replace_an_existing_record_if_there_is_no_id
@pirate.reload.ship_attributes = { name: "Davy Jones Gold Dagger" }
- assert !@pirate.ship.persisted?
+ assert_not_predicate @pirate.ship, :persisted?
assert_equal "Davy Jones Gold Dagger", @pirate.ship.name
assert_equal "Nights Dirty Lightning", @ship.name
end
@@ -333,7 +347,7 @@ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase
def test_should_also_work_with_a_HashWithIndifferentAccess
@pirate.ship_attributes = ActiveSupport::HashWithIndifferentAccess.new(id: @ship.id, name: "Davy Jones Gold Dagger")
- assert @pirate.ship.persisted?
+ assert_predicate @pirate.ship, :persisted?
assert_equal "Davy Jones Gold Dagger", @pirate.ship.name
end
@@ -348,12 +362,12 @@ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase
def test_should_not_destroy_the_associated_model_until_the_parent_is_saved
@pirate.attributes = { ship_attributes: { id: @ship.id, _destroy: "1" } }
- assert !@pirate.ship.destroyed?
- assert @pirate.ship.marked_for_destruction?
+ assert_not_predicate @pirate.ship, :destroyed?
+ assert_predicate @pirate.ship, :marked_for_destruction?
@pirate.save
- assert @pirate.ship.destroyed?
+ assert_predicate @pirate.ship, :destroyed?
assert_nil @pirate.reload.ship
end
@@ -422,7 +436,7 @@ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase
@pirate.destroy
@ship.reload.pirate_attributes = { catchphrase: "Arr" }
- assert !@ship.pirate.persisted?
+ assert_not_predicate @ship.pirate, :persisted?
assert_equal "Arr", @ship.pirate.catchphrase
end
@@ -443,7 +457,7 @@ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase
def test_should_replace_an_existing_record_if_there_is_no_id
@ship.reload.pirate_attributes = { catchphrase: "Arr" }
- assert !@ship.pirate.persisted?
+ assert_not_predicate @ship.pirate, :persisted?
assert_equal "Arr", @ship.pirate.catchphrase
assert_equal "Aye", @pirate.catchphrase
end
@@ -548,7 +562,7 @@ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase
@pirate.delete
@ship.reload.attributes = { update_only_pirate_attributes: { catchphrase: "Arr" } }
- assert !@ship.update_only_pirate.persisted?
+ assert_not_predicate @ship.update_only_pirate, :persisted?
end
def test_should_update_existing_when_update_only_is_true_and_no_id_is_given
@@ -630,10 +644,10 @@ module NestedAttributesOnACollectionAssociationTests
def test_should_not_load_association_when_updating_existing_records
@pirate.reload
@pirate.send(association_setter, [{ id: @child_1.id, name: "Grace OMalley" }])
- assert ! @pirate.send(@association_name).loaded?
+ assert_not_predicate @pirate.send(@association_name), :loaded?
@pirate.save
- assert ! @pirate.send(@association_name).loaded?
+ assert_not_predicate @pirate.send(@association_name), :loaded?
assert_equal "Grace OMalley", @child_1.reload.name
end
@@ -661,13 +675,12 @@ module NestedAttributesOnACollectionAssociationTests
def test_should_not_remove_scheduled_destroys_when_loading_association
@pirate.reload
@pirate.send(association_setter, [{ id: @child_1.id, _destroy: "1" }])
- assert @pirate.send(@association_name).load_target.find { |r| r.id == @child_1.id }.marked_for_destruction?
+ assert_predicate @pirate.send(@association_name).load_target.find { |r| r.id == @child_1.id }, :marked_for_destruction?
end
def test_should_take_a_hash_with_composite_id_keys_and_assign_the_attributes_to_the_associated_models
@child_1.stub(:id, "ABC1X") do
@child_2.stub(:id, "ABC2X") do
-
@pirate.attributes = {
association_getter => [
{ id: @child_1.id, name: "Grace OMalley" },
@@ -703,10 +716,10 @@ module NestedAttributesOnACollectionAssociationTests
association_getter => { "foo" => { name: "Grace OMalley" }, "bar" => { name: "Privateers Greed" } }
}
- assert !@pirate.send(@association_name).first.persisted?
+ assert_not_predicate @pirate.send(@association_name).first, :persisted?
assert_equal "Grace OMalley", @pirate.send(@association_name).first.name
- assert !@pirate.send(@association_name).last.persisted?
+ assert_not_predicate @pirate.send(@association_name).last, :persisted?
assert_equal "Privateers Greed", @pirate.send(@association_name).last.name
end
@@ -752,7 +765,7 @@ module NestedAttributesOnACollectionAssociationTests
exception = assert_raise ArgumentError do
@pirate.send(association_setter, "foo")
end
- assert_equal 'Hash or Array expected, got String ("foo")', exception.message
+ assert_equal %{Hash or Array expected for attribute `#{@association_name}`, got String ("foo")}, exception.message
end
def test_should_work_with_update_as_well
@@ -833,7 +846,7 @@ module NestedAttributesOnACollectionAssociationTests
man = Man.create(name: "John")
interest = man.interests.create(topic: "bar", zine_id: 0)
assert interest.save
- assert !man.update(interests_attributes: { id: interest.id, zine_id: "foo" })
+ assert_not man.update(interests_attributes: { id: interest.id, zine_id: "foo" })
end
end
@@ -1089,7 +1102,19 @@ class TestHasManyAutosaveAssociationWhichItselfHasAutosaveAssociations < ActiveR
test "nested singular associations are validated" do
part = ShipPart.new(name: "Stern", ship_attributes: { name: nil })
- assert_not part.valid?
+ assert_not_predicate part, :valid?
assert_equal ["Ship name can't be blank"], part.errors.full_messages
end
end
+
+class TestNestedAttributesWithExtend < ActiveRecord::TestCase
+ setup do
+ Pirate.accepts_nested_attributes_for :treasures
+ end
+
+ def test_extend_affects_nested_attributes
+ pirate = Pirate.create!(catchphrase: "Don' botharrr talkin' like one, savvy?")
+ pirate.treasures_attributes = [{ id: nil }]
+ assert_equal "from extension", pirate.treasures[0].name
+ end
+end
diff --git a/activerecord/test/cases/nested_attributes_with_callbacks_test.rb b/activerecord/test/cases/nested_attributes_with_callbacks_test.rb
index b9d2acbed2..1d26057fdc 100644
--- a/activerecord/test/cases/nested_attributes_with_callbacks_test.rb
+++ b/activerecord/test/cases/nested_attributes_with_callbacks_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/pirate"
require "models/bird"
@@ -61,7 +63,7 @@ class NestedAttributesWithCallbacksTest < ActiveRecord::TestCase
# Characterizing when :before_add callback is called
test ":before_add called for new bird when not loaded" do
- assert_not @pirate.birds_with_add.loaded?
+ assert_not_predicate @pirate.birds_with_add, :loaded?
@pirate.birds_with_add_attributes = new_bird_attributes
assert_new_bird_with_callback_called
end
@@ -78,7 +80,7 @@ class NestedAttributesWithCallbacksTest < ActiveRecord::TestCase
end
test ":before_add not called for identical assignment when not loaded" do
- assert_not @pirate.birds_with_add.loaded?
+ assert_not_predicate @pirate.birds_with_add, :loaded?
@pirate.birds_with_add_attributes = existing_birds_attributes
assert_callbacks_not_called
end
@@ -90,7 +92,7 @@ class NestedAttributesWithCallbacksTest < ActiveRecord::TestCase
end
test ":before_add not called for destroy assignment when not loaded" do
- assert_not @pirate.birds_with_add.loaded?
+ assert_not_predicate @pirate.birds_with_add, :loaded?
@pirate.birds_with_add_attributes = destroy_bird_attributes
assert_callbacks_not_called
end
@@ -109,7 +111,7 @@ class NestedAttributesWithCallbacksTest < ActiveRecord::TestCase
# Ensuring that the records in the association target are updated,
# whether the association is loaded before or not
test "Assignment updates records in target when not loaded" do
- assert_not @pirate.birds_with_add.loaded?
+ assert_not_predicate @pirate.birds_with_add, :loaded?
@pirate.birds_with_add_attributes = update_new_and_destroy_bird_attributes
assert_assignment_affects_records_in_target(:birds_with_add)
end
@@ -122,7 +124,7 @@ class NestedAttributesWithCallbacksTest < ActiveRecord::TestCase
test("Assignment updates records in target when not loaded" \
" and callback loads target") do
- assert_not @pirate.birds_with_add_load.loaded?
+ assert_not_predicate @pirate.birds_with_add_load, :loaded?
@pirate.birds_with_add_load_attributes = update_new_and_destroy_bird_attributes
assert_assignment_affects_records_in_target(:birds_with_add_load)
end
diff --git a/activerecord/test/cases/null_relation_test.rb b/activerecord/test/cases/null_relation_test.rb
new file mode 100644
index 0000000000..17527568f8
--- /dev/null
+++ b/activerecord/test/cases/null_relation_test.rb
@@ -0,0 +1,84 @@
+# frozen_string_literal: true
+
+require "cases/helper"
+require "models/developer"
+require "models/comment"
+require "models/post"
+require "models/topic"
+
+class NullRelationTest < ActiveRecord::TestCase
+ fixtures :posts, :comments
+
+ def test_none
+ assert_no_queries(ignore_none: false) do
+ assert_equal [], Developer.none
+ assert_equal [], Developer.all.none
+ end
+ end
+
+ def test_none_chainable
+ assert_no_queries(ignore_none: false) do
+ assert_equal [], Developer.none.where(name: "David")
+ end
+ end
+
+ def test_none_chainable_to_existing_scope_extension_method
+ assert_no_queries(ignore_none: false) do
+ assert_equal 1, Topic.anonymous_extension.none.one
+ end
+ end
+
+ def test_none_chained_to_methods_firing_queries_straight_to_db
+ assert_no_queries(ignore_none: false) do
+ assert_equal [], Developer.none.pluck(:id, :name)
+ assert_equal 0, Developer.none.delete_all
+ assert_equal 0, Developer.none.update_all(name: "David")
+ assert_equal 0, Developer.none.delete(1)
+ assert_equal false, Developer.none.exists?(1)
+ end
+ end
+
+ def test_null_relation_content_size_methods
+ assert_no_queries(ignore_none: false) do
+ assert_equal 0, Developer.none.size
+ assert_equal 0, Developer.none.count
+ assert_equal true, Developer.none.empty?
+ assert_equal true, Developer.none.none?
+ assert_equal false, Developer.none.any?
+ assert_equal false, Developer.none.one?
+ assert_equal false, Developer.none.many?
+ end
+ end
+
+ def test_null_relation_metadata_methods
+ assert_equal "", Developer.none.to_sql
+ assert_equal({}, Developer.none.where_values_hash)
+ end
+
+ def test_null_relation_where_values_hash
+ assert_equal({ "salary" => 100_000 }, Developer.none.where(salary: 100_000).where_values_hash)
+ end
+
+ [:count, :sum].each do |method|
+ define_method "test_null_relation_#{method}" do
+ assert_no_queries(ignore_none: false) do
+ assert_equal 0, Comment.none.public_send(method, :id)
+ assert_equal Hash.new, Comment.none.group(:post_id).public_send(method, :id)
+ end
+ end
+ end
+
+ [:average, :minimum, :maximum].each do |method|
+ define_method "test_null_relation_#{method}" do
+ assert_no_queries(ignore_none: false) do
+ assert_nil Comment.none.public_send(method, :id)
+ assert_equal Hash.new, Comment.none.group(:post_id).public_send(method, :id)
+ end
+ end
+ end
+
+ def test_null_relation_in_where_condition
+ assert_operator Comment.count, :>, 0 # precondition, make sure there are comments.
+ assert_equal 0, Comment.where(post_id: Post.none).count
+ end
+end
diff --git a/activerecord/test/cases/numeric_data_test.rb b/activerecord/test/cases/numeric_data_test.rb
new file mode 100644
index 0000000000..14db63890e
--- /dev/null
+++ b/activerecord/test/cases/numeric_data_test.rb
@@ -0,0 +1,67 @@
+# frozen_string_literal: true
+
+require "cases/helper"
+require "models/numeric_data"
+
+class NumericDataTest < ActiveRecord::TestCase
+ def test_big_decimal_conditions
+ m = NumericData.new(
+ bank_balance: 1586.43,
+ big_bank_balance: BigDecimal("1000234000567.95"),
+ world_population: 6000000000,
+ my_house_population: 3
+ )
+ assert m.save
+ assert_equal 0, NumericData.where("bank_balance > ?", 2000.0).count
+ end
+
+ def test_numeric_fields
+ m = NumericData.new(
+ bank_balance: 1586.43,
+ big_bank_balance: BigDecimal("1000234000567.95"),
+ world_population: 2**62,
+ my_house_population: 3
+ )
+ assert m.save
+
+ m1 = NumericData.find(m.id)
+ assert_not_nil m1
+
+ assert_kind_of Integer, m1.world_population
+ assert_equal 2**62, m1.world_population
+
+ assert_kind_of Integer, 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_numeric_fields_with_scale
+ m = NumericData.new(
+ bank_balance: 1586.43122334,
+ big_bank_balance: BigDecimal("234000567.952344"),
+ world_population: 2**62,
+ my_house_population: 3
+ )
+ assert m.save
+
+ m1 = NumericData.find(m.id)
+ assert_not_nil m1
+
+ assert_kind_of Integer, m1.world_population
+ assert_equal 2**62, m1.world_population
+
+ assert_kind_of Integer, m1.my_house_population
+ assert_equal 3, m1.my_house_population
+
+ assert_kind_of BigDecimal, m1.bank_balance
+ assert_equal BigDecimal("1586.43"), m1.bank_balance
+
+ assert_kind_of BigDecimal, m1.big_bank_balance
+ assert_equal BigDecimal("234000567.95"), m1.big_bank_balance
+ end
+end
diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb
index 5b7e2fd008..7348a22dd3 100644
--- a/activerecord/test/cases/persistence_test.rb
+++ b/activerecord/test/cases/persistence_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/aircraft"
require "models/post"
@@ -46,10 +48,10 @@ class PersistenceTest < ActiveRecord::TestCase
end
if test_update_with_order_succeeds.call("id DESC")
- assert !test_update_with_order_succeeds.call("id ASC") # test that this wasn't a fluke and using an incorrect order results in an exception
+ assert_not test_update_with_order_succeeds.call("id ASC") # test that this wasn't a fluke and using an incorrect order results in an exception
else
# test that we're failing because the current Arel's engine doesn't support UPDATE ORDER BY queries is using subselects instead
- assert_sql(/\AUPDATE .+ \(SELECT .* ORDER BY id DESC\)\Z/i) do
+ assert_sql(/\AUPDATE .+ \(SELECT .* ORDER BY id DESC\)\z/i) do
test_update_with_order_succeeds.call("id DESC")
end
end
@@ -57,13 +59,42 @@ class PersistenceTest < ActiveRecord::TestCase
def test_update_all_with_order_and_limit_updates_subset_only
author = authors(:david)
- assert_nothing_raised do
- assert_equal 1, author.posts_sorted_by_id_limited.size
- assert_equal 2, author.posts_sorted_by_id_limited.limit(2).to_a.size
- assert_equal 1, author.posts_sorted_by_id_limited.update_all([ "body = ?", "bulk update!" ])
- assert_equal "bulk update!", posts(:welcome).body
- assert_not_equal "bulk update!", posts(:thinking).body
- end
+ limited_posts = author.posts_sorted_by_id_limited
+ assert_equal 1, limited_posts.size
+ assert_equal 2, limited_posts.limit(2).size
+ assert_equal 1, limited_posts.update_all([ "body = ?", "bulk update!" ])
+ assert_equal "bulk update!", posts(:welcome).body
+ assert_not_equal "bulk update!", posts(:thinking).body
+ end
+
+ def test_update_all_with_order_and_limit_and_offset_updates_subset_only
+ author = authors(:david)
+ limited_posts = author.posts_sorted_by_id_limited.offset(1)
+ assert_equal 1, limited_posts.size
+ assert_equal 2, limited_posts.limit(2).size
+ assert_equal 1, limited_posts.update_all([ "body = ?", "bulk update!" ])
+ assert_equal "bulk update!", posts(:thinking).body
+ assert_not_equal "bulk update!", posts(:welcome).body
+ end
+
+ def test_delete_all_with_order_and_limit_deletes_subset_only
+ author = authors(:david)
+ limited_posts = Post.where(author: author).order(:id).limit(1)
+ assert_equal 1, limited_posts.size
+ assert_equal 2, limited_posts.limit(2).size
+ assert_equal 1, limited_posts.delete_all
+ assert_raise(ActiveRecord::RecordNotFound) { posts(:welcome) }
+ assert posts(:thinking)
+ end
+
+ def test_delete_all_with_order_and_limit_and_offset_deletes_subset_only
+ author = authors(:david)
+ limited_posts = Post.where(author: author).order(:id).limit(1).offset(1)
+ assert_equal 1, limited_posts.size
+ assert_equal 2, limited_posts.limit(2).size
+ assert_equal 1, limited_posts.delete_all
+ assert_raise(ActiveRecord::RecordNotFound) { posts(:thinking) }
+ assert posts(:welcome)
end
end
@@ -71,11 +102,43 @@ class PersistenceTest < ActiveRecord::TestCase
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, 2], updated.map(&:id)
assert_equal "1 updated", Topic.find(1).content
assert_equal "2 updated", Topic.find(2).content
end
+ def test_update_many_with_duplicated_ids
+ updated = Topic.update([1, 1, 2], [
+ { "content" => "1 duplicated" }, { "content" => "1 updated" }, { "content" => "2 updated" }
+ ])
+
+ assert_equal [1, 1, 2], updated.map(&:id)
+ assert_equal "1 updated", Topic.find(1).content
+ assert_equal "2 updated", Topic.find(2).content
+ end
+
+ def test_update_many_with_invalid_id
+ topic_data = { 1 => { "content" => "1 updated" }, 2 => { "content" => "2 updated" }, 99999 => {} }
+
+ assert_raise(ActiveRecord::RecordNotFound) do
+ Topic.update(topic_data.keys, topic_data.values)
+ end
+
+ assert_not_equal "1 updated", Topic.find(1).content
+ assert_not_equal "2 updated", Topic.find(2).content
+ end
+
+ def test_class_level_update_is_affected_by_scoping
+ topic_data = { 1 => { "content" => "1 updated" }, 2 => { "content" => "2 updated" } }
+
+ assert_raise(ActiveRecord::RecordNotFound) do
+ Topic.where("1=0").scoping { Topic.update(topic_data.keys, topic_data.values) }
+ end
+
+ assert_not_equal "1 updated", Topic.find(1).content
+ assert_not_equal "2 updated", Topic.find(2).content
+ end
+
def test_delete_all
assert Topic.count > 0
@@ -83,27 +146,31 @@ class PersistenceTest < ActiveRecord::TestCase
end
def test_delete_all_with_joins_and_where_part_is_hash
- where_args = { toys: { name: "Bone" } }
- count = Pet.joins(:toys).where(where_args).count
+ pets = Pet.joins(:toys).where(toys: { name: "Bone" })
+
+ assert_equal true, pets.exists?
+ assert_equal pets.count, pets.delete_all
+ end
+
+ def test_delete_all_with_joins_and_where_part_is_not_hash
+ pets = Pet.joins(:toys).where("toys.name = ?", "Bone")
- assert_equal count, 1
- assert_equal count, Pet.joins(:toys).where(where_args).delete_all
+ assert_equal true, pets.exists?
+ assert_equal pets.count, pets.delete_all
end
def test_delete_all_with_left_joins
- where_args = { toys: { name: "Bone" } }
- count = Pet.left_joins(:toys).where(where_args).count
+ pets = Pet.left_joins(:toys).where(toys: { name: "Bone" })
- assert_equal count, 1
- assert_equal count, Pet.left_joins(:toys).where(where_args).delete_all
+ assert_equal true, pets.exists?
+ assert_equal pets.count, pets.delete_all
end
- def test_delete_all_with_joins_and_where_part_is_not_hash
- where_args = ["toys.name = ?", "Bone"]
- count = Pet.joins(:toys).where(where_args).count
+ def test_delete_all_with_includes
+ pets = Pet.includes(:toys).where(toys: { name: "Bone" })
- assert_equal count, 1
- assert_equal count, Pet.joins(:toys).where(where_args).delete_all
+ assert_equal true, pets.exists?
+ assert_equal pets.count, pets.delete_all
end
def test_increment_attribute
@@ -139,18 +206,34 @@ class PersistenceTest < ActiveRecord::TestCase
assert_equal initial_credit + 2, a1.reload.credit_limit
end
- def test_increment_updates_timestamps
+ def test_increment_with_touch_updates_timestamps
+ topic = topics(:first)
+ assert_equal 1, topic.replies_count
+ previously_updated_at = topic.updated_at
+ travel(1.second) do
+ topic.increment!(:replies_count, touch: true)
+ end
+ assert_equal 2, topic.reload.replies_count
+ assert_operator previously_updated_at, :<, topic.updated_at
+ end
+
+ def test_increment_with_touch_an_attribute_updates_timestamps
topic = topics(:first)
- topic.update_columns(updated_at: 5.minutes.ago)
- previous_updated_at = topic.updated_at
- topic.increment!(:replies_count, touch: true)
- assert_operator previous_updated_at, :<, topic.reload.updated_at
+ assert_equal 1, topic.replies_count
+ previously_updated_at = topic.updated_at
+ previously_written_on = topic.written_on
+ travel(1.second) do
+ topic.increment!(:replies_count, touch: :written_on)
+ end
+ assert_equal 2, topic.reload.replies_count
+ assert_operator previously_updated_at, :<, topic.updated_at
+ assert_operator previously_written_on, :<, topic.written_on
end
def test_destroy_all
conditions = "author_name = 'Mary'"
topics_by_mary = Topic.all.merge!(where: conditions, order: "id").to_a
- assert ! topics_by_mary.empty?
+ assert_not_empty topics_by_mary
assert_difference("Topic.count", -topics_by_mary.size) do
destroyed = Topic.where(conditions).destroy_all.sort_by(&:id)
@@ -160,23 +243,40 @@ class PersistenceTest < ActiveRecord::TestCase
end
def test_destroy_many
- clients = Client.all.merge!(order: "id").find([2, 3])
+ clients = Client.find([2, 3])
assert_difference("Client.count", -2) do
- destroyed = Client.destroy([2, 3]).sort_by(&:id)
+ destroyed = Client.destroy([2, 3])
assert_equal clients, destroyed
assert destroyed.all?(&:frozen?), "destroyed clients should be frozen"
end
end
+ def test_destroy_many_with_invalid_id
+ clients = Client.find([2, 3])
+
+ assert_raise(ActiveRecord::RecordNotFound) do
+ Client.destroy([2, 3, 99999])
+ end
+
+ assert_equal clients, Client.find([2, 3])
+ 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_becomes_after_reload_schema_from_cache
+ Reply.define_attribute_methods
+ Reply.serialize(:content) # invoke reload_schema_from_cache
+ assert_kind_of Reply, topics(:first).becomes(Reply)
+ assert_equal "The First Topic", topics(:first).becomes(Reply).title
+ end
+
def test_becomes_includes_errors
company = Company.new(name: nil)
- assert !company.valid?
+ assert_not_predicate company, :valid?
original_errors = company.errors
client = company.becomes(Client)
assert_equal original_errors.keys, client.errors.keys
@@ -206,6 +306,17 @@ class PersistenceTest < ActiveRecord::TestCase
assert_equal "The First Topic", Topic.find(copy.id).title
end
+ def test_becomes_wont_break_mutation_tracking
+ topic = topics(:first)
+ reply = topic.becomes(Reply)
+
+ assert_equal 1, topic.id_in_database
+ assert_empty topic.attributes_in_database
+
+ assert_equal 1, reply.id_in_database
+ assert_empty reply.attributes_in_database
+ end
+
def test_becomes_includes_changed_attributes
company = Company.new(name: "37signals")
client = company.becomes(Client)
@@ -238,12 +349,28 @@ class PersistenceTest < ActiveRecord::TestCase
assert_equal 41, accounts(:signals37, :reload).credit_limit
end
- def test_decrement_updates_timestamps
+ def test_decrement_with_touch_updates_timestamps
topic = topics(:first)
- topic.update_columns(updated_at: 5.minutes.ago)
- previous_updated_at = topic.updated_at
- topic.decrement!(:replies_count, touch: true)
- assert_operator previous_updated_at, :<, topic.reload.updated_at
+ assert_equal 1, topic.replies_count
+ previously_updated_at = topic.updated_at
+ travel(1.second) do
+ topic.decrement!(:replies_count, touch: true)
+ end
+ assert_equal 0, topic.reload.replies_count
+ assert_operator previously_updated_at, :<, topic.updated_at
+ end
+
+ def test_decrement_with_touch_an_attribute_updates_timestamps
+ topic = topics(:first)
+ assert_equal 1, topic.replies_count
+ previously_updated_at = topic.updated_at
+ previously_written_on = topic.written_on
+ travel(1.second) do
+ topic.decrement!(:replies_count, touch: :written_on)
+ end
+ assert_equal 0, topic.reload.replies_count
+ assert_operator previously_updated_at, :<, topic.updated_at
+ assert_operator previously_written_on, :<, topic.written_on
end
def test_create
@@ -293,8 +420,8 @@ class PersistenceTest < ActiveRecord::TestCase
developer.destroy
new_developer = developer.dup
new_developer.save
- assert new_developer.persisted?
- assert_not new_developer.destroyed?
+ assert_predicate new_developer, :persisted?
+ assert_not_predicate new_developer, :destroyed?
end
def test_create_many
@@ -305,12 +432,13 @@ class PersistenceTest < ActiveRecord::TestCase
def test_create_columns_not_equal_attributes
topic = Topic.instantiate(
- "attributes" => {
- "title" => "Another New Topic",
- "does_not_exist" => "test"
- }
+ "title" => "Another New Topic",
+ "does_not_exist" => "test"
)
+ topic = topic.dup # reset @new_record
assert_nothing_raised { topic.save }
+ assert_predicate topic, :persisted?
+ assert_equal "Another New Topic", topic.reload.title
end
def test_create_through_factory_with_block
@@ -357,6 +485,8 @@ class PersistenceTest < ActiveRecord::TestCase
topic_reloaded = Topic.instantiate(topic.attributes.merge("does_not_exist" => "test"))
topic_reloaded.title = "A New Topic"
assert_nothing_raised { topic_reloaded.save }
+ assert_predicate topic_reloaded, :persisted?
+ assert_equal "A New Topic", topic_reloaded.reload.title
end
def test_update_for_record_with_only_primary_key
@@ -393,6 +523,22 @@ class PersistenceTest < ActiveRecord::TestCase
assert_instance_of Reply, Reply.find(reply.id)
end
+ def test_becomes_default_sti_subclass
+ original_type = Topic.columns_hash["type"].default
+ ActiveRecord::Base.connection.change_column_default :topics, :type, "Reply"
+ Topic.reset_column_information
+
+ reply = topics(:second)
+ assert_instance_of Reply, reply
+
+ topic = reply.becomes(Topic)
+ assert_instance_of Topic, topic
+
+ ensure
+ ActiveRecord::Base.connection.change_column_default :topics, :type, original_type
+ Topic.reset_column_information
+ end
+
def test_update_after_create
klass = Class.new(Topic) do
def self.name; "Topic"; end
@@ -422,7 +568,7 @@ class PersistenceTest < ActiveRecord::TestCase
def test_update_does_not_run_sql_if_record_has_not_changed
topic = Topic.create(title: "Another New Topic")
assert_queries(0) { assert topic.update(title: "Another New Topic") }
- assert_queries(0) { assert topic.update_attributes(title: "Another New Topic") }
+ assert_queries(0) { assert topic.update(title: "Another New Topic") }
end
def test_delete
@@ -437,6 +583,13 @@ class PersistenceTest < ActiveRecord::TestCase
assert_not_nil Topic.find(2)
end
+ def test_delete_isnt_affected_by_scoping
+ topic = Topic.find(1)
+ assert_difference("Topic.count", -1) do
+ Topic.where("1=0").scoping { topic.delete }
+ end
+ end
+
def test_destroy
topic = Topic.find(1)
assert_equal topic, topic.destroy, "topic.destroy did not return self"
@@ -451,10 +604,18 @@ class PersistenceTest < ActiveRecord::TestCase
assert_raise(ActiveRecord::RecordNotFound) { Topic.find(topic.id) }
end
- def test_record_not_found_exception
+ def test_find_raises_record_not_found_exception
assert_raise(ActiveRecord::RecordNotFound) { Topic.find(99999) }
end
+ def test_update_raises_record_not_found_exception
+ assert_raise(ActiveRecord::RecordNotFound) { Topic.update(99999, approved: true) }
+ end
+
+ def test_destroy_raises_record_not_found_exception
+ assert_raise(ActiveRecord::RecordNotFound) { Topic.destroy(99999) }
+ end
+
def test_update_all
assert_equal Topic.count, Topic.update_all("content = 'bulk updated!'")
assert_equal "bulk updated!", Topic.find(1).content
@@ -478,17 +639,24 @@ class PersistenceTest < ActiveRecord::TestCase
end
def test_update_all_with_joins
- where_args = { toys: { name: "Bone" } }
- count = Pet.left_joins(:toys).where(where_args).count
+ pets = Pet.joins(:toys).where(toys: { name: "Bone" })
- assert_equal count, Pet.joins(:toys).where(where_args).update_all(name: "Bob")
+ assert_equal true, pets.exists?
+ assert_equal pets.count, pets.update_all(name: "Bob")
end
def test_update_all_with_left_joins
- where_args = { toys: { name: "Bone" } }
- count = Pet.left_joins(:toys).where(where_args).count
+ pets = Pet.left_joins(:toys).where(toys: { name: "Bone" })
+
+ assert_equal true, pets.exists?
+ assert_equal pets.count, pets.update_all(name: "Bob")
+ end
+
+ def test_update_all_with_includes
+ pets = Pet.includes(:toys).where(toys: { name: "Bone" })
- assert_equal count, Pet.left_joins(:toys).where(where_args).update_all(name: "Bob")
+ assert_equal true, pets.exists?
+ assert_equal pets.count, pets.update_all(name: "Bob")
end
def test_update_all_with_non_standard_table_name
@@ -497,40 +665,65 @@ class PersistenceTest < ActiveRecord::TestCase
end
def test_delete_new_record
- client = Client.new
+ client = Client.new(name: "37signals")
client.delete
- assert client.frozen?
+ assert_predicate client, :frozen?
+
+ assert_not client.save
+ assert_raise(ActiveRecord::RecordNotSaved) { client.save! }
+
+ assert_predicate client, :frozen?
+ assert_raise(RuntimeError) { client.name = "something else" }
end
def test_delete_record_with_associations
client = Client.find(3)
client.delete
- assert client.frozen?
+ assert_predicate client, :frozen?
assert_kind_of Firm, client.firm
+
+ assert_not client.save
+ assert_raise(ActiveRecord::RecordNotSaved) { client.save! }
+
+ assert_predicate client, :frozen?
assert_raise(RuntimeError) { client.name = "something else" }
end
def test_destroy_new_record
- client = Client.new
+ client = Client.new(name: "37signals")
client.destroy
- assert client.frozen?
+ assert_predicate client, :frozen?
+
+ assert_not client.save
+ assert_raise(ActiveRecord::RecordNotSaved) { client.save! }
+
+ assert_predicate client, :frozen?
+ assert_raise(RuntimeError) { client.name = "something else" }
end
def test_destroy_record_with_associations
client = Client.find(3)
client.destroy
- assert client.frozen?
+ assert_predicate client, :frozen?
assert_kind_of Firm, client.firm
+
+ assert_not client.save
+ assert_raise(ActiveRecord::RecordNotSaved) { client.save! }
+
+ assert_predicate client, :frozen?
assert_raise(RuntimeError) { client.name = "something else" }
end
def test_update_attribute
- assert !Topic.find(1).approved?
+ assert_not_predicate Topic.find(1), :approved?
Topic.find(1).update_attribute("approved", true)
- assert Topic.find(1).approved?
+ assert_predicate Topic.find(1), :approved?
Topic.find(1).update_attribute(:approved, false)
- assert !Topic.find(1).approved?
+ assert_not_predicate Topic.find(1), :approved?
+
+ Topic.find(1).update_attribute(:change_approved_before_save, true)
+ assert_predicate Topic.find(1), :approved?
end
def test_update_attribute_for_readonly_attribute
@@ -542,8 +735,8 @@ class PersistenceTest < ActiveRecord::TestCase
t = Topic.first
t.update_attribute(:title, "super_title")
assert_equal "super_title", t.title
- assert !t.changed?, "topic should not have changed"
- assert !t.title_changed?, "title should not have changed"
+ assert_not t.changed?, "topic should not have changed"
+ assert_not t.title_changed?, "title should not have changed"
assert_nil t.title_change, "title change should be nil"
t.reload
@@ -567,14 +760,14 @@ class PersistenceTest < ActiveRecord::TestCase
def test_update_column
topic = Topic.find(1)
topic.update_column("approved", true)
- assert topic.approved?
+ assert_predicate topic, :approved?
topic.reload
- assert topic.approved?
+ assert_predicate topic, :approved?
topic.update_column(:approved, false)
- assert !topic.approved?
+ assert_not_predicate topic, :approved?
topic.reload
- assert !topic.approved?
+ assert_not_predicate topic, :approved?
end
def test_update_column_should_not_use_setter_method
@@ -661,10 +854,10 @@ class PersistenceTest < ActiveRecord::TestCase
def test_update_columns
topic = Topic.find(1)
topic.update_columns("approved" => true, title: "Sebastian Topic")
- assert topic.approved?
+ assert_predicate topic, :approved?
assert_equal "Sebastian Topic", topic.title
topic.reload
- assert topic.approved?
+ assert_predicate topic, :approved?
assert_equal "Sebastian Topic", topic.title
end
@@ -772,54 +965,45 @@ class PersistenceTest < ActiveRecord::TestCase
def test_update
topic = Topic.find(1)
- assert !topic.approved?
+ assert_not_predicate topic, :approved?
assert_equal "The First Topic", topic.title
topic.update("approved" => true, "title" => "The First Topic Updated")
topic.reload
- assert topic.approved?
+ assert_predicate topic, :approved?
assert_equal "The First Topic Updated", topic.title
topic.update(approved: false, title: "The First Topic")
topic.reload
- assert !topic.approved?
- assert_equal "The First Topic", topic.title
- 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_not_predicate topic, :approved?
assert_equal "The First Topic", topic.title
error = assert_raise(ActiveRecord::RecordNotUnique, ActiveRecord::StatementInvalid) do
- topic.update_attributes(id: 3, title: "Hm is it possible?")
+ topic.update(id: 3, title: "Hm is it possible?")
end
assert_not_nil error.cause
assert_not_equal "Hm is it possible?", Topic.find(3).title
- topic.update_attributes(id: 1234)
+ topic.update(id: 1234)
assert_nothing_raised { topic.reload }
assert_equal topic.title, Topic.find(1234).title
end
- def test_update_attributes_parameters
+ def test_update_attributes
+ topic = Topic.find(1)
+ assert_deprecated do
+ topic.update_attributes("title" => "The First Topic Updated")
+ end
+ end
+
+ def test_update_parameters
topic = Topic.find(1)
assert_nothing_raised do
- topic.update_attributes({})
+ topic.update({})
end
assert_raises(ArgumentError) do
- topic.update_attributes(nil)
+ topic.update(nil)
end
end
@@ -845,24 +1029,10 @@ class PersistenceTest < ActiveRecord::TestCase
end
def test_update_attributes!
- Reply.validates_presence_of(:title)
reply = Reply.find(2)
- assert_equal "The Second Topic of the day", reply.title
- assert_equal "Have a nice day", reply.content
-
- reply.update_attributes!("title" => "The Second Topic of the day updated", "content" => "Have a nice evening")
- reply.reload
- assert_equal "The Second Topic of the day updated", reply.title
- assert_equal "Have a nice evening", reply.content
-
- reply.update_attributes!(title: "The Second Topic of the day", content: "Have a nice day")
- reply.reload
- assert_equal "The Second Topic 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") }
- ensure
- Reply.clear_validators!
+ assert_deprecated do
+ reply.update_attributes!("title" => "The Second Topic of the day updated")
+ end
end
def test_destroyed_returns_boolean
@@ -898,18 +1068,41 @@ class PersistenceTest < ActiveRecord::TestCase
should_be_destroyed_reply = Reply.create("title" => "hello", "content" => "world")
Topic.find(1).replies << should_be_destroyed_reply
- Topic.destroy(1)
+ topic = Topic.destroy(1)
+ assert_predicate topic, :destroyed?
+
assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1) }
assert_raise(ActiveRecord::RecordNotFound) { Reply.find(should_be_destroyed_reply.id) }
end
+ def test_class_level_destroy_is_affected_by_scoping
+ should_not_be_destroyed_reply = Reply.create("title" => "hello", "content" => "world")
+ Topic.find(1).replies << should_not_be_destroyed_reply
+
+ assert_raise(ActiveRecord::RecordNotFound) do
+ Topic.where("1=0").scoping { Topic.destroy(1) }
+ end
+
+ assert_nothing_raised { Topic.find(1) }
+ assert_nothing_raised { Reply.find(should_not_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
+ should_not_be_destroyed_reply = Reply.create("title" => "hello", "content" => "world")
+ Topic.find(1).replies << should_not_be_destroyed_reply
Topic.delete(1)
assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1) }
- assert_nothing_raised { Reply.find(should_be_destroyed_reply.id) }
+ assert_nothing_raised { Reply.find(should_not_be_destroyed_reply.id) }
+ end
+
+ def test_class_level_delete_is_affected_by_scoping
+ should_not_be_destroyed_reply = Reply.create("title" => "hello", "content" => "world")
+ Topic.find(1).replies << should_not_be_destroyed_reply
+
+ Topic.where("1=0").scoping { Topic.delete(1) }
+ assert_nothing_raised { Topic.find(1) }
+ assert_nothing_raised { Reply.find(should_not_be_destroyed_reply.id) }
end
def test_create_with_custom_timestamps
@@ -956,13 +1149,13 @@ class PersistenceTest < ActiveRecord::TestCase
def test_find_via_reload
post = Post.new
- assert post.new_record?
+ assert_predicate post, :new_record?
post.id = 1
post.reload
assert_equal "Welcome to the weblog", post.title
- assert_not post.new_record?
+ assert_not_predicate post, :new_record?
end
def test_reload_via_querycache
@@ -993,44 +1186,35 @@ class PersistenceTest < ActiveRecord::TestCase
end
class SaveTest < ActiveRecord::TestCase
- self.use_transactional_tests = false
-
def test_save_touch_false
- widget = Class.new(ActiveRecord::Base) do
- connection.create_table :widgets, force: true do |t|
- t.string :name
- t.timestamps null: false
- end
-
- self.table_name = :widgets
- end
-
- instance = widget.create!(
+ pet = Pet.create!(
name: "Bob",
created_at: 1.day.ago,
updated_at: 1.day.ago)
- created_at = instance.created_at
- updated_at = instance.updated_at
+ created_at = pet.created_at
+ updated_at = pet.updated_at
- instance.name = "Barb"
- instance.save!(touch: false)
- assert_equal instance.created_at, created_at
- assert_equal instance.updated_at, updated_at
- ensure
- ActiveRecord::Base.connection.drop_table widget.table_name
- widget.reset_column_information
+ pet.name = "Barb"
+ pet.save!(touch: false)
+ assert_equal pet.created_at, created_at
+ assert_equal pet.updated_at, updated_at
end
end
def test_reset_column_information_resets_children
- child = Class.new(Topic)
- child.new # force schema to load
+ child_class = Class.new(Topic)
+ child_class.new # force schema to load
ActiveRecord::Base.connection.add_column(:topics, :foo, :string)
Topic.reset_column_information
- assert_equal "bar", child.new(foo: :bar).foo
+ # this should redefine attribute methods
+ child_class.new
+
+ assert child_class.instance_methods.include?(:foo)
+ assert child_class.instance_methods.include?(:foo_changed?)
+ assert_equal "bar", child_class.new(foo: :bar).foo
ensure
ActiveRecord::Base.connection.remove_column(:topics, :foo)
Topic.reset_column_information
diff --git a/activerecord/test/cases/pooled_connections_test.rb b/activerecord/test/cases/pooled_connections_test.rb
index f1b0d08765..fa7f759e51 100644
--- a/activerecord/test/cases/pooled_connections_test.rb
+++ b/activerecord/test/cases/pooled_connections_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/project"
require "timeout"
diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb
index 12386635f6..4ed7469039 100644
--- a/activerecord/test/cases/primary_keys_test.rb
+++ b/activerecord/test/cases/primary_keys_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "support/schema_dumping_helper"
require "models/topic"
@@ -46,7 +48,7 @@ class PrimaryKeysTest < ActiveRecord::TestCase
topic = Topic.new
topic.title = "New Topic"
assert_nil topic.id
- assert_nothing_raised { topic.save! }
+ topic.save!
id = topic.id
topicReloaded = Topic.find(id)
@@ -56,23 +58,36 @@ class PrimaryKeysTest < ActiveRecord::TestCase
def test_customized_primary_key_auto_assigns_on_save
Keyboard.delete_all
keyboard = Keyboard.new(name: "HHKB")
- assert_nothing_raised { keyboard.save! }
+ 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 }
+ assert_nil keyboard.key_number
end
def test_customized_string_primary_key_settable_before_save
subscriber = Subscriber.new
- assert_nothing_raised { subscriber.id = "webster123" }
+ subscriber.id = "webster123"
assert_equal "webster123", subscriber.id
assert_equal "webster123", subscriber.nick
end
+ def test_update_with_non_primary_key_id_column
+ subscriber = Subscriber.first
+ subscriber.update(update_count: 1)
+ subscriber.reload
+ assert_equal 1, subscriber.update_count
+ end
+
+ def test_update_columns_with_non_primary_key_id_column
+ subscriber = Subscriber.first
+ subscriber.update_columns(id: 1)
+ assert_not_equal 1, subscriber.nick
+ end
+
def test_string_key
subscriber = Subscriber.find(subscribers(:first).nick)
assert_equal(subscribers(:first).name, subscriber.name)
@@ -83,7 +98,7 @@ class PrimaryKeysTest < ActiveRecord::TestCase
subscriber.id = "jdoe"
assert_equal("jdoe", subscriber.id)
subscriber.name = "John Doe"
- assert_nothing_raised { subscriber.save! }
+ subscriber.save!
assert_equal("jdoe", subscriber.id)
subscriberReloaded = Subscriber.find("jdoe")
@@ -141,10 +156,6 @@ class PrimaryKeysTest < ActiveRecord::TestCase
assert_nothing_raised { MixedCaseMonkey.find(1).destroy }
end
- def test_deprecate_supports_primary_key
- assert_deprecated { ActiveRecord::Base.connection.supports_primary_key? }
- end
-
def test_primary_key_returns_value_if_it_exists
klass = Class.new(ActiveRecord::Base) do
self.table_name = "developers"
@@ -183,6 +194,8 @@ class PrimaryKeysTest < ActiveRecord::TestCase
end
def test_create_without_primary_key_no_extra_query
+ skip if current_adapter?(:OracleAdapter)
+
klass = Class.new(ActiveRecord::Base) do
self.table_name = "dashboards"
end
@@ -194,13 +207,13 @@ class PrimaryKeysTest < ActiveRecord::TestCase
def test_serial_with_quoted_sequence_name
column = MixedCaseMonkey.columns_hash[MixedCaseMonkey.primary_key]
assert_equal "nextval('\"mixed_case_monkeys_monkeyID_seq\"'::regclass)", column.default_function
- assert column.serial?
+ assert_predicate column, :serial?
end
def test_serial_with_unquoted_sequence_name
column = Topic.columns_hash[Topic.primary_key]
assert_equal "nextval('topics_id_seq'::regclass)", column.default_function
- assert column.serial?
+ assert_predicate column, :serial?
end
end
end
@@ -285,11 +298,14 @@ class PrimaryKeyAnyTypeTest < ActiveRecord::TestCase
assert_not column.null
assert_equal :string, column.type
assert_equal 42, column.limit
+ ensure
+ Barcode.reset_column_information
end
test "schema dump primary key includes type and options" do
schema = dump_table_schema "barcodes"
assert_match %r{create_table "barcodes", primary_key: "code", id: :string, limit: 42}, schema
+ assert_no_match %r{t\.index \["code"\]}, schema
end
if current_adapter?(:Mysql2Adapter) && subsecond_precision_supported?
@@ -309,7 +325,7 @@ class CompositePrimaryKeyTest < ActiveRecord::TestCase
def setup
@connection = ActiveRecord::Base.connection
@connection.schema_cache.clear!
- @connection.create_table(:barcodes, primary_key: ["region", "code"], force: true) do |t|
+ @connection.create_table(:uber_barcodes, primary_key: ["region", "code"], force: true) do |t|
t.string :region
t.integer :code
end
@@ -317,14 +333,24 @@ class CompositePrimaryKeyTest < ActiveRecord::TestCase
t.string :region
t.integer :code
end
+ @connection.create_table(:travels, primary_key: ["from", "to"], force: true) do |t|
+ t.string :from
+ t.string :to
+ end
end
def teardown
- @connection.drop_table(:barcodes, if_exists: true)
+ @connection.drop_table :uber_barcodes, if_exists: true
+ @connection.drop_table :barcodes_reverse, if_exists: true
+ @connection.drop_table :travels, if_exists: true
end
def test_composite_primary_key
- assert_equal ["region", "code"], @connection.primary_keys("barcodes")
+ assert_equal ["region", "code"], @connection.primary_keys("uber_barcodes")
+ end
+
+ def test_composite_primary_key_with_reserved_words
+ assert_equal ["from", "to"], @connection.primary_keys("travels")
end
def test_composite_primary_key_out_of_order
@@ -335,7 +361,7 @@ class CompositePrimaryKeyTest < ActiveRecord::TestCase
def test_primary_key_issues_warning
model = Class.new(ActiveRecord::Base) do
def self.table_name
- "barcodes"
+ "uber_barcodes"
end
end
warning = capture(:stderr) do
@@ -344,9 +370,9 @@ class CompositePrimaryKeyTest < ActiveRecord::TestCase
assert_match(/WARNING: Active Record does not support composite primary key\./, warning)
end
- def test_dumping_composite_primary_key
- schema = dump_table_schema "barcodes"
- assert_match %r{create_table "barcodes", primary_key: \["region", "code"\]}, schema
+ def test_collectly_dump_composite_primary_key
+ schema = dump_table_schema "uber_barcodes"
+ assert_match %r{create_table "uber_barcodes", primary_key: \["region", "code"\]}, schema
end
def test_dumping_composite_primary_key_out_of_order
@@ -405,7 +431,7 @@ if current_adapter?(:PostgreSQLAdapter, :Mysql2Adapter)
@connection.create_table(:widgets, id: @pk_type, force: true)
column = @connection.columns(:widgets).find { |c| c.name == "id" }
assert_equal :integer, column.type
- assert_not column.bigint?
+ assert_not_predicate column, :bigint?
end
test "primary key with serial/integer are automatically numbered" do
@@ -417,32 +443,32 @@ if current_adapter?(:PostgreSQLAdapter, :Mysql2Adapter)
test "schema dump primary key with serial/integer" do
@connection.create_table(:widgets, id: @pk_type, force: true)
schema = dump_table_schema "widgets"
- assert_match %r{create_table "widgets", id: :#{@pk_type}, force: :cascade}, schema
+ assert_match %r{create_table "widgets", id: :#{@pk_type}, }, schema
end
if current_adapter?(:Mysql2Adapter)
test "primary key column type with options" do
@connection.create_table(:widgets, id: :primary_key, limit: 4, unsigned: true, force: true)
column = @connection.columns(:widgets).find { |c| c.name == "id" }
- assert column.auto_increment?
+ assert_predicate column, :auto_increment?
assert_equal :integer, column.type
- assert_not column.bigint?
- assert column.unsigned?
+ assert_not_predicate column, :bigint?
+ assert_predicate column, :unsigned?
schema = dump_table_schema "widgets"
- assert_match %r{create_table "widgets", id: :integer, unsigned: true, force: :cascade}, schema
+ assert_match %r{create_table "widgets", id: :integer, unsigned: true, }, schema
end
test "bigint primary key with unsigned" do
@connection.create_table(:widgets, id: :bigint, unsigned: true, force: true)
column = @connection.columns(:widgets).find { |c| c.name == "id" }
- assert column.auto_increment?
+ assert_predicate column, :auto_increment?
assert_equal :integer, column.type
- assert column.bigint?
- assert column.unsigned?
+ assert_predicate column, :bigint?
+ assert_predicate column, :unsigned?
schema = dump_table_schema "widgets"
- assert_match %r{create_table "widgets", id: :bigint, unsigned: true, force: :cascade}, schema
+ assert_match %r{create_table "widgets", id: :bigint, unsigned: true, }, schema
end
end
end
diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb
index 494663eb04..69be091869 100644
--- a/activerecord/test/cases/query_cache_test.rb
+++ b/activerecord/test/cases/query_cache_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/topic"
require "models/task"
@@ -11,12 +13,13 @@ class QueryCacheTest < ActiveRecord::TestCase
fixtures :tasks, :topics, :categories, :posts, :categories_posts
class ShouldNotHaveExceptionsLogger < ActiveRecord::LogSubscriber
- attr_reader :logger
+ attr_reader :logger, :events
def initialize
super
@logger = ::Logger.new File::NULL
@exception = false
+ @events = []
end
def exception?
@@ -24,6 +27,7 @@ class QueryCacheTest < ActiveRecord::TestCase
end
def sql(event)
+ @events << event
super
rescue
@exception = true
@@ -42,7 +46,8 @@ class QueryCacheTest < ActiveRecord::TestCase
mw = middleware { |env|
Task.find 1
Task.find 1
- assert_equal 1, ActiveRecord::Base.connection.query_cache.length
+ query_cache = ActiveRecord::Base.connection.query_cache
+ assert_equal 1, query_cache.length, query_cache.keys
raise "lol borked"
}
assert_raises(RuntimeError) { mw.call({}) }
@@ -79,7 +84,7 @@ class QueryCacheTest < ActiveRecord::TestCase
assert_cache :off, conn
end
- assert !ActiveRecord::Base.connection.nil?
+ assert_not_predicate ActiveRecord::Base.connection, :nil?
assert_cache :off
middleware {
@@ -130,7 +135,7 @@ class QueryCacheTest < ActiveRecord::TestCase
assert_cache :off, conn
end
ensure
- ActiveRecord::Base.clear_all_connections!
+ ActiveRecord::Base.connection_pool.disconnect!
end
end
end
@@ -149,7 +154,8 @@ class QueryCacheTest < ActiveRecord::TestCase
mw = middleware { |env|
Task.find 1
Task.find 1
- assert_equal 1, ActiveRecord::Base.connection.query_cache.length
+ query_cache = ActiveRecord::Base.connection.query_cache
+ assert_equal 1, query_cache.length, query_cache.keys
[200, {}, nil]
}
mw.call({})
@@ -204,6 +210,52 @@ class QueryCacheTest < ActiveRecord::TestCase
end
end
+ def test_exists_queries_with_cache
+ Post.cache do
+ assert_queries(1) { Post.exists?; Post.exists? }
+ end
+ end
+
+ def test_select_all_with_cache
+ Post.cache do
+ assert_queries(1) do
+ 2.times { Post.connection.select_all(Post.all) }
+ end
+ end
+ end
+
+ def test_select_one_with_cache
+ Post.cache do
+ assert_queries(1) do
+ 2.times { Post.connection.select_one(Post.all) }
+ end
+ end
+ end
+
+ def test_select_value_with_cache
+ Post.cache do
+ assert_queries(1) do
+ 2.times { Post.connection.select_value(Post.all) }
+ end
+ end
+ end
+
+ def test_select_values_with_cache
+ Post.cache do
+ assert_queries(1) do
+ 2.times { Post.connection.select_values(Post.all) }
+ end
+ end
+ end
+
+ def test_select_rows_with_cache
+ Post.cache do
+ assert_queries(1) do
+ 2.times { Post.connection.select_rows(Post.all) }
+ end
+ end
+ end
+
def test_query_cache_dups_results_correctly
Task.cache do
now = Time.now.utc
@@ -215,6 +267,26 @@ class QueryCacheTest < ActiveRecord::TestCase
end
end
+ def test_cache_notifications_can_be_overridden
+ logger = ShouldNotHaveExceptionsLogger.new
+ subscriber = ActiveSupport::Notifications.subscribe "sql.active_record", logger
+
+ connection = ActiveRecord::Base.connection.dup
+
+ def connection.cache_notification_info(sql, name, binds)
+ super.merge(neat: true)
+ end
+
+ connection.cache do
+ connection.select_all "select 1"
+ connection.select_all "select 1"
+ end
+
+ assert_equal true, logger.events.last.payload[:neat]
+ ensure
+ ActiveSupport::Notifications.unsubscribe subscriber
+ end
+
def test_cache_does_not_raise_exceptions
logger = ShouldNotHaveExceptionsLogger.new
subscriber = ActiveSupport::Notifications.subscribe "sql.active_record", logger
@@ -233,7 +305,7 @@ class QueryCacheTest < ActiveRecord::TestCase
payload[:sql].downcase!
end
- assert_raises RuntimeError do
+ assert_raises frozen_error_class do
ActiveRecord::Base.cache do
assert_queries(1) { Task.find(1); Task.find(1) }
end
@@ -252,14 +324,10 @@ class QueryCacheTest < ActiveRecord::TestCase
end
end
- def test_cache_does_not_wrap_string_results_in_arrays
+ def test_cache_does_not_wrap_results_in_arrays
Task.cache do
- # Oracle adapter returns count() as Integer or Float
- if current_adapter?(:OracleAdapter)
- assert_kind_of Numeric, Task.connection.select_value("SELECT count(*) AS count_all FROM tasks")
- elsif current_adapter?(:SQLite3Adapter, :Mysql2Adapter, :PostgreSQLAdapter)
- # Future versions of the sqlite3 adapter will return numeric
- assert_instance_of 0.class, Task.connection.select_value("SELECT count(*) AS count_all FROM tasks")
+ if current_adapter?(:SQLite3Adapter, :Mysql2Adapter, :PostgreSQLAdapter, :OracleAdapter)
+ assert_equal 2, Task.connection.select_value("SELECT count(*) AS count_all FROM tasks")
else
assert_instance_of String, Task.connection.select_value("SELECT count(*) AS count_all FROM tasks")
end
@@ -285,25 +353,18 @@ class QueryCacheTest < ActiveRecord::TestCase
ActiveRecord::Base.configurations = conf
end
- def test_cache_is_not_available_when_using_a_not_connected_connection
+ def test_cache_is_available_when_using_a_not_connected_connection
+ skip "In-Memory DB can't test for using a not connected connection" if in_memory_db?
with_temporary_connection_pool do
spec_name = Task.connection_specification_name
conf = ActiveRecord::Base.configurations["arunit"].merge("name" => "test2")
ActiveRecord::Base.connection_handler.establish_connection(conf)
Task.connection_specification_name = "test2"
- refute Task.connected?
+ assert_not_predicate Task, :connected?
Task.cache do
begin
- if in_memory_db?
- Task.connection.create_table :tasks do |t|
- t.datetime :starting
- t.datetime :ending
- end
- ActiveRecord::FixtureSet.create_fixtures(self.class.fixture_path, ["tasks"], {}, ActiveRecord::Base)
- end
- Task.connection # warmup postgresql connection setup queries
- assert_queries(2) { Task.find(1); Task.find(1) }
+ assert_queries(1) { Task.find(1); Task.find(1) }
ensure
ActiveRecord::Base.connection_handler.remove_connection(Task.connection_specification_name)
Task.connection_specification_name = spec_name
@@ -338,7 +399,7 @@ class QueryCacheTest < ActiveRecord::TestCase
post = Post.first
Post.transaction do
- post.update_attributes(title: "rollback")
+ post.update(title: "rollback")
assert_equal 1, Post.where(title: "rollback").to_a.count
raise ActiveRecord::Rollback
end
@@ -351,7 +412,7 @@ class QueryCacheTest < ActiveRecord::TestCase
begin
Post.transaction do
- post.update_attributes(title: "rollback")
+ post.update(title: "rollback")
assert_equal 1, Post.where(title: "rollback").to_a.count
raise "broken"
end
@@ -370,10 +431,8 @@ class QueryCacheTest < ActiveRecord::TestCase
# Warm the cache
Task.find(1)
- Task.connection.type_map.clear
-
# Preload the type cache again (so we don't have those queries issued during our assertions)
- Task.connection.send(:initialize_type_map, Task.connection.type_map)
+ Task.connection.send(:reload_type_map)
# Clear places where type information is cached
Task.reset_column_information
@@ -388,24 +447,25 @@ class QueryCacheTest < ActiveRecord::TestCase
def test_query_cache_does_not_establish_connection_if_unconnected
with_temporary_connection_pool do
ActiveRecord::Base.clear_active_connections!
- refute ActiveRecord::Base.connection_handler.active_connections? # sanity check
+ assert_not ActiveRecord::Base.connection_handler.active_connections? # sanity check
middleware {
- refute ActiveRecord::Base.connection_handler.active_connections?, "QueryCache forced ActiveRecord::Base to establish a connection in setup"
+ assert_not ActiveRecord::Base.connection_handler.active_connections?, "QueryCache forced ActiveRecord::Base to establish a connection in setup"
}.call({})
- refute ActiveRecord::Base.connection_handler.active_connections?, "QueryCache forced ActiveRecord::Base to establish a connection in cleanup"
+ assert_not ActiveRecord::Base.connection_handler.active_connections?, "QueryCache forced ActiveRecord::Base to establish a connection in cleanup"
end
end
def test_query_cache_is_enabled_on_connections_established_after_middleware_runs
with_temporary_connection_pool do
ActiveRecord::Base.clear_active_connections!
- refute ActiveRecord::Base.connection_handler.active_connections? # sanity check
+ assert_not ActiveRecord::Base.connection_handler.active_connections? # sanity check
middleware {
- assert ActiveRecord::Base.connection.query_cache_enabled, "QueryCache did not get lazily enabled"
+ assert_predicate ActiveRecord::Base.connection, :query_cache_enabled
}.call({})
+ assert_not_predicate ActiveRecord::Base.connection, :query_cache_enabled
end
end
@@ -418,14 +478,22 @@ class QueryCacheTest < ActiveRecord::TestCase
assert ActiveRecord::Base.connection.query_cache_enabled
Thread.new {
- refute ActiveRecord::Base.connection_pool.query_cache_enabled
- refute ActiveRecord::Base.connection.query_cache_enabled
+ assert_not ActiveRecord::Base.connection_pool.query_cache_enabled
+ assert_not ActiveRecord::Base.connection.query_cache_enabled
}.join
}.call({})
-
end
end
+ def test_query_cache_is_enabled_on_all_connection_pools
+ middleware {
+ ActiveRecord::Base.connection_handler.connection_pool_list.each do |pool|
+ assert pool.query_cache_enabled
+ assert pool.connection.query_cache_enabled
+ end
+ }.call({})
+ end
+
private
def middleware(&app)
executor = Class.new(ActiveSupport::Executor)
@@ -436,14 +504,14 @@ class QueryCacheTest < ActiveRecord::TestCase
def assert_cache(state, connection = ActiveRecord::Base.connection)
case state
when :off
- assert !connection.query_cache_enabled, "cache should be off"
+ assert_not connection.query_cache_enabled, "cache should be off"
assert connection.query_cache.empty?, "cache should be empty"
when :clean
assert connection.query_cache_enabled, "cache should be on"
assert connection.query_cache.empty?, "cache should be empty"
when :dirty
assert connection.query_cache_enabled, "cache should be on"
- assert !connection.query_cache.empty?, "cache should be dirty"
+ assert_not connection.query_cache.empty?, "cache should be dirty"
else
raise "unknown state"
end
@@ -471,19 +539,19 @@ class QueryCacheExpiryTest < ActiveRecord::TestCase
def test_find
assert_called(Task.connection, :clear_query_cache) do
- assert !Task.connection.query_cache_enabled
+ assert_not 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
+ assert_not Task.connection.query_cache_enabled
Task.find(1)
end
assert Task.connection.query_cache_enabled
end
- assert !Task.connection.query_cache_enabled
+ assert_not Task.connection.query_cache_enabled
end
end
@@ -527,7 +595,7 @@ class QueryCacheExpiryTest < ActiveRecord::TestCase
assert_called(ActiveRecord::Base.connection, :clear_query_cache, times: 2) do
ActiveRecord::Base.cache do
p = Post.find(1)
- assert p.categories.any?
+ assert_predicate p.categories, :any?
p.categories.delete_all
end
end
diff --git a/activerecord/test/cases/quoting_test.rb b/activerecord/test/cases/quoting_test.rb
index f260d043e4..723fccc8d9 100644
--- a/activerecord/test/cases/quoting_test.rb
+++ b/activerecord/test/cases/quoting_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
module ActiveRecord
@@ -8,11 +10,11 @@ module ActiveRecord
end
def test_quoted_true
- assert_equal "'t'", @quoter.quoted_true
+ assert_equal "TRUE", @quoter.quoted_true
end
def test_quoted_false
- assert_equal "'f'", @quoter.quoted_false
+ assert_equal "FALSE", @quoter.quoted_false
end
def test_quote_column_name
@@ -44,27 +46,86 @@ module ActiveRecord
assert_equal t.to_s(:db), @quoter.quoted_date(t)
end
- def test_quoted_time_utc
+ def test_quoted_timestamp_utc
with_timezone_config default: :utc do
t = Time.now.change(usec: 0)
assert_equal t.getutc.to_s(:db), @quoter.quoted_date(t)
end
end
- def test_quoted_time_local
+ def test_quoted_timestamp_local
with_timezone_config default: :local do
t = Time.now.change(usec: 0)
assert_equal t.getlocal.to_s(:db), @quoter.quoted_date(t)
end
end
- def test_quoted_time_crazy
+ def test_quoted_timestamp_crazy
with_timezone_config default: :asdfasdf do
t = Time.now.change(usec: 0)
assert_equal t.getlocal.to_s(:db), @quoter.quoted_date(t)
end
end
+ def test_quoted_time_utc
+ with_timezone_config default: :utc do
+ t = Time.now.change(usec: 0)
+
+ expected = t.change(year: 2000, month: 1, day: 1)
+ expected = expected.getutc.to_s(:db).slice(11..-1)
+
+ assert_equal expected, @quoter.quoted_time(t)
+ end
+ end
+
+ def test_quoted_time_local
+ with_timezone_config default: :local do
+ t = Time.now.change(usec: 0)
+
+ expected = t.change(year: 2000, month: 1, day: 1)
+ expected = expected.getlocal.to_s(:db).sub("2000-01-01 ", "")
+
+ assert_equal expected, @quoter.quoted_time(t)
+ end
+ end
+
+ def test_quoted_time_dst_utc
+ with_env_tz "America/New_York" do
+ with_timezone_config default: :utc do
+ t = Time.new(2000, 7, 1, 0, 0, 0, "+04:30")
+
+ expected = t.change(year: 2000, month: 1, day: 1)
+ expected = expected.getutc.to_s(:db).slice(11..-1)
+
+ assert_equal expected, @quoter.quoted_time(t)
+ end
+ end
+ end
+
+ def test_quoted_time_dst_local
+ with_env_tz "America/New_York" do
+ with_timezone_config default: :local do
+ t = Time.new(2000, 7, 1, 0, 0, 0, "+04:30")
+
+ expected = t.change(year: 2000, month: 1, day: 1)
+ expected = expected.getlocal.to_s(:db).slice(11..-1)
+
+ assert_equal expected, @quoter.quoted_time(t)
+ end
+ end
+ end
+
+ def test_quoted_time_crazy
+ with_timezone_config default: :asdfasdf do
+ t = Time.now.change(usec: 0)
+
+ expected = t.change(year: 2000, month: 1, day: 1)
+ expected = expected.getlocal.to_s(:db).sub("2000-01-01 ", "")
+
+ assert_equal expected, @quoter.quoted_time(t)
+ end
+ end
+
def test_quoted_datetime_utc
with_timezone_config default: :utc do
t = Time.now.change(usec: 0).to_datetime
@@ -81,10 +142,6 @@ module ActiveRecord
end
end
- def test_quote_with_quoted_id
- assert_deprecated { assert_equal 1, @quoter.quote(Struct.new(:quoted_id).new(1)) }
- end
-
def test_quote_nil
assert_equal "NULL", @quoter.quote(nil)
end
@@ -113,7 +170,7 @@ module ActiveRecord
end
def test_quote_bigdecimal
- bigdec = BigDecimal.new((1 << 100).to_s)
+ bigdec = BigDecimal((1 << 100).to_s)
assert_equal bigdec.to_s("F"), @quoter.quote(bigdec)
end
@@ -161,13 +218,21 @@ module ActiveRecord
def test_type_cast_date
date = Date.today
- expected = @conn.quoted_date(date)
+ if current_adapter?(:Mysql2Adapter)
+ expected = date
+ else
+ expected = @conn.quoted_date(date)
+ end
assert_equal expected, @conn.type_cast(date)
end
def test_type_cast_time
time = Time.now
- expected = @conn.quoted_date(time)
+ if current_adapter?(:Mysql2Adapter)
+ expected = time
+ else
+ expected = @conn.quoted_date(time)
+ end
assert_equal expected, @conn.type_cast(time)
end
@@ -184,26 +249,6 @@ module ActiveRecord
obj = Class.new.new
assert_raise(TypeError) { @conn.type_cast(obj) }
end
-
- def test_type_cast_object_which_responds_to_quoted_id
- quoted_id_obj = Class.new {
- def quoted_id
- "'zomg'"
- end
-
- def id
- 10
- end
- }.new
- assert_equal 10, @conn.type_cast(quoted_id_obj)
-
- quoted_id_obj = Class.new {
- def quoted_id
- "'zomg'"
- end
- }.new
- assert_raise(TypeError) { @conn.type_cast(quoted_id_obj) }
- end
end
class QuoteBooleanTest < ActiveRecord::TestCase
@@ -244,7 +289,7 @@ module ActiveRecord
def test_type_cast_ar_object
value = DatetimePrimaryKey.new(id: @time)
- assert_equal "2017-02-14 12:34:56.789000", @connection.type_cast(value)
+ assert_equal @connection.type_cast(value.id), @connection.type_cast(value)
end
end
end
diff --git a/activerecord/test/cases/readonly_test.rb b/activerecord/test/cases/readonly_test.rb
index a93061b516..059fa76132 100644
--- a/activerecord/test/cases/readonly_test.rb
+++ b/activerecord/test/cases/readonly_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/author"
require "models/post"
@@ -10,18 +12,18 @@ require "models/person"
require "models/ship"
class ReadOnlyTest < ActiveRecord::TestCase
- fixtures :authors, :posts, :comments, :developers, :projects, :developers_projects, :people, :readers
+ fixtures :authors, :author_addresses, :posts, :comments, :developers, :projects, :developers_projects, :people, :readers
def test_cant_save_readonly_record
dev = Developer.find(1)
- assert !dev.readonly?
+ assert_not_predicate dev, :readonly?
dev.readonly!
- assert dev.readonly?
+ assert_predicate dev, :readonly?
assert_nothing_raised do
dev.name = "Luscious forbidden fruit."
- assert !dev.save
+ assert_not dev.save
dev.name = "Forbidden."
end
@@ -36,8 +38,8 @@ class ReadOnlyTest < ActiveRecord::TestCase
end
def test_find_with_readonly_option
- Developer.all.each { |d| assert !d.readonly? }
- Developer.readonly(false).each { |d| assert !d.readonly? }
+ Developer.all.each { |d| assert_not d.readonly? }
+ Developer.readonly(false).each { |d| assert_not d.readonly? }
Developer.readonly(true).each { |d| assert d.readonly? }
Developer.readonly.each { |d| assert d.readonly? }
end
@@ -52,56 +54,56 @@ class ReadOnlyTest < ActiveRecord::TestCase
def test_has_many_find_readonly
post = Post.find(1)
- assert !post.comments.empty?
- assert !post.comments.any?(&:readonly?)
- assert !post.comments.to_a.any?(&:readonly?)
+ assert_not_empty post.comments
+ assert_not post.comments.any?(&:readonly?)
+ assert_not post.comments.to_a.any?(&:readonly?)
assert post.comments.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?)
+ assert_not people.any?(&:readonly?)
end
def test_has_many_with_through_is_not_implicitly_marked_readonly_while_finding_by_id
- assert !posts(:welcome).people.find(1).readonly?
+ assert_not_predicate posts(:welcome).people.find(1), :readonly?
end
def test_has_many_with_through_is_not_implicitly_marked_readonly_while_finding_first
- assert !posts(:welcome).people.first.readonly?
+ assert_not_predicate posts(:welcome).people.first, :readonly?
end
def test_has_many_with_through_is_not_implicitly_marked_readonly_while_finding_last
- assert !posts(:welcome).people.last.readonly?
+ assert_not_predicate posts(:welcome).people.last, :readonly?
end
def test_readonly_scoping
Post.where("1=1").scoping do
- assert !Post.find(1).readonly?
- assert Post.readonly(true).find(1).readonly?
- assert !Post.readonly(false).find(1).readonly?
+ assert_not_predicate Post.find(1), :readonly?
+ assert_predicate Post.readonly(true).find(1), :readonly?
+ assert_not_predicate Post.readonly(false).find(1), :readonly?
end
Post.joins(" ").scoping do
- assert !Post.find(1).readonly?
- assert Post.readonly.find(1).readonly?
- assert !Post.readonly(false).find(1).readonly?
+ assert_not_predicate Post.find(1), :readonly?
+ assert_predicate Post.readonly.find(1), :readonly?
+ assert_not_predicate Post.readonly(false).find(1), :readonly?
end
# Oracle barfs on this because the join includes unqualified and
# conflicting column names
unless current_adapter?(:OracleAdapter)
Post.joins(", developers").scoping do
- assert_not Post.find(1).readonly?
- assert Post.readonly.find(1).readonly?
- assert !Post.readonly(false).find(1).readonly?
+ assert_not_predicate Post.find(1), :readonly?
+ assert_predicate Post.readonly.find(1), :readonly?
+ assert_not_predicate Post.readonly(false).find(1), :readonly?
end
end
Post.readonly(true).scoping do
- assert Post.find(1).readonly?
- assert Post.readonly.find(1).readonly?
- assert !Post.readonly(false).find(1).readonly?
+ assert_predicate Post.find(1), :readonly?
+ assert_predicate Post.readonly.find(1), :readonly?
+ assert_not_predicate Post.readonly(false).find(1), :readonly?
end
end
@@ -109,10 +111,10 @@ class ReadOnlyTest < ActiveRecord::TestCase
developer = Developer.find(1)
project = Post.find(1)
- assert !developer.projects.all_as_method.first.readonly?
- assert !developer.projects.all_as_scope.first.readonly?
+ assert_not_predicate developer.projects.all_as_method.first, :readonly?
+ assert_not_predicate developer.projects.all_as_scope.first, :readonly?
- assert !project.comments.all_as_method.first.readonly?
- assert !project.comments.all_as_scope.first.readonly?
+ assert_not_predicate project.comments.all_as_method.first, :readonly?
+ assert_not_predicate project.comments.all_as_scope.first, :readonly?
end
end
diff --git a/activerecord/test/cases/reaper_test.rb b/activerecord/test/cases/reaper_test.rb
index 249878b67d..b630f782bc 100644
--- a/activerecord/test/cases/reaper_test.rb
+++ b/activerecord/test/cases/reaper_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
module ActiveRecord
@@ -16,6 +18,7 @@ module ActiveRecord
class FakePool
attr_reader :reaped
+ attr_reader :flushed
def initialize
@reaped = false
@@ -24,20 +27,24 @@ module ActiveRecord
def reap
@reaped = true
end
+
+ def flush
+ @flushed = true
+ end
end
# A reaper with nil time should never reap connections
def test_nil_time
fp = FakePool.new
- assert !fp.reaped
+ assert_not fp.reaped
reaper = ConnectionPool::Reaper.new(fp, nil)
reaper.run
- assert !fp.reaped
+ assert_not fp.reaped
end
def test_some_time
fp = FakePool.new
- assert !fp.reaped
+ assert_not fp.reaped
reaper = ConnectionPool::Reaper.new(fp, 0.0001)
reaper.run
@@ -45,6 +52,7 @@ module ActiveRecord
Thread.pass
end
assert fp.reaped
+ assert fp.flushed
end
def test_pool_has_reaper
@@ -53,9 +61,9 @@ module ActiveRecord
def test_reaping_frequency_configuration
spec = ActiveRecord::Base.connection_pool.spec.dup
- spec.config[:reaping_frequency] = 100
+ spec.config[:reaping_frequency] = "10.01"
pool = ConnectionPool.new spec
- assert_equal 100, pool.reaper.frequency
+ assert_equal 10.01, pool.reaper.frequency
end
def test_connection_pool_starts_reaper
@@ -71,14 +79,14 @@ module ActiveRecord
end
Thread.pass while conn.nil?
- assert conn.in_use?
+ assert_predicate conn, :in_use?
child.terminate
while conn.in_use?
Thread.pass
end
- assert !conn.in_use?
+ assert_not_predicate conn, :in_use?
end
end
end
diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb
index c1c2efb9c8..abadafbad4 100644
--- a/activerecord/test/cases/reflection_test.rb
+++ b/activerecord/test/cases/reflection_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/topic"
require "models/customer"
@@ -23,7 +25,6 @@ require "models/chef"
require "models/department"
require "models/cake_designer"
require "models/drink_designer"
-require "models/mocktail_designer"
require "models/recipe"
class ReflectionTest < ActiveRecord::TestCase
@@ -65,13 +66,16 @@ class ReflectionTest < ActiveRecord::TestCase
def test_column_string_type_and_limit
assert_equal :string, @first.column_for_attribute("title").type
+ assert_equal :string, @first.column_for_attribute(:title).type
+ assert_equal :string, @first.type_for_attribute("title").type
+ assert_equal :string, @first.type_for_attribute(:title).type
assert_equal 250, @first.column_for_attribute("title").limit
end
def test_column_null_not_null
subscriber = Subscriber.first
assert subscriber.column_for_attribute("name").null
- assert !subscriber.column_for_attribute("nick").null
+ assert_not subscriber.column_for_attribute("nick").null
end
def test_human_name_for_column
@@ -80,6 +84,9 @@ class ReflectionTest < ActiveRecord::TestCase
def test_integer_columns
assert_equal :integer, @first.column_for_attribute("id").type
+ assert_equal :integer, @first.column_for_attribute(:id).type
+ assert_equal :integer, @first.type_for_attribute("id").type
+ assert_equal :integer, @first.type_for_attribute(:id).type
end
def test_non_existent_columns_return_null_object
@@ -88,6 +95,9 @@ class ReflectionTest < ActiveRecord::TestCase
assert_equal "attribute_that_doesnt_exist", column.name
assert_nil column.sql_type
assert_nil column.type
+
+ column = @first.column_for_attribute(:attribute_that_doesnt_exist)
+ assert_instance_of ActiveRecord::ConnectionAdapters::NullColumn, column
end
def test_non_existent_types_are_identity_types
@@ -97,6 +107,11 @@ class ReflectionTest < ActiveRecord::TestCase
assert_equal object, type.deserialize(object)
assert_equal object, type.cast(object)
assert_equal object, type.serialize(object)
+
+ type = @first.type_for_attribute(:attribute_that_doesnt_exist)
+ assert_equal object, type.deserialize(object)
+ assert_equal object, type.cast(object)
+ assert_equal object, type.serialize(object)
end
def test_reflection_klass_for_nested_class_name
@@ -148,7 +163,7 @@ class ReflectionTest < ActiveRecord::TestCase
expected = Pirate.reflect_on_all_associations.select { |r| r.options[:autosave] }
received = Pirate.reflect_on_all_autosave_associations
- assert !received.empty?
+ assert_not_empty received
assert_not_equal Pirate.reflect_on_all_associations.length, received.length
assert_equal expected, received
end
@@ -252,50 +267,35 @@ class ReflectionTest < ActiveRecord::TestCase
assert_equal expected, actual
end
- def test_scope_chain
- expected = [
- [Tagging.reflect_on_association(:tag).scope, Post.reflect_on_association(:first_blue_tags).scope],
- [Post.reflect_on_association(:first_taggings).scope],
- [Author.reflect_on_association(:misc_posts).scope]
- ]
- actual = assert_deprecated do
- Author.reflect_on_association(:misc_post_first_blue_tags).scope_chain
- end
- assert_equal expected, actual
-
- expected = [
- [
- Tagging.reflect_on_association(:blue_tag).scope,
- Post.reflect_on_association(:first_blue_tags_2).scope,
- Author.reflect_on_association(:misc_post_first_blue_tags_2).scope
- ],
- [],
- []
- ]
- actual = assert_deprecated do
- Author.reflect_on_association(:misc_post_first_blue_tags_2).scope_chain
- end
- assert_equal expected, actual
- end
-
def test_scope_chain_does_not_interfere_with_hmt_with_polymorphic_case
- @hotel = Hotel.create!
- @department = @hotel.departments.create!
- @department.chefs.create!(employable: CakeDesigner.create!)
- @department.chefs.create!(employable: DrinkDesigner.create!)
+ hotel = Hotel.create!
+ department = hotel.departments.create!
+ department.chefs.create!(employable: CakeDesigner.create!)
+ department.chefs.create!(employable: DrinkDesigner.create!)
- assert_equal 1, @hotel.cake_designers.size
- assert_equal 1, @hotel.drink_designers.size
- assert_equal 2, @hotel.chefs.size
+ assert_equal 1, hotel.cake_designers.size
+ assert_equal 1, hotel.cake_designers.count
+ assert_equal 1, hotel.drink_designers.size
+ assert_equal 1, hotel.drink_designers.count
+ assert_equal 2, hotel.chefs.size
+ assert_equal 2, hotel.chefs.count
end
def test_scope_chain_does_not_interfere_with_hmt_with_polymorphic_case_and_sti
- @hotel = Hotel.create!
- @hotel.mocktail_designers << MocktailDesigner.create!
+ hotel = Hotel.create!
+ hotel.mocktail_designers << MocktailDesigner.create!
+
+ assert_equal 1, hotel.mocktail_designers.size
+ assert_equal 1, hotel.mocktail_designers.count
+ assert_equal 1, hotel.chef_lists.size
+ assert_equal 1, hotel.chef_lists.count
- assert_equal 1, @hotel.mocktail_designers.size
- assert_equal 1, @hotel.mocktail_designers.count
- assert_equal 1, @hotel.chef_lists.size
+ hotel.mocktail_designers = []
+
+ assert_equal 0, hotel.mocktail_designers.size
+ assert_equal 0, hotel.mocktail_designers.count
+ assert_equal 0, hotel.chef_lists.size
+ assert_equal 0, hotel.chef_lists.count
end
def test_scope_chain_of_polymorphic_association_does_not_leak_into_other_hmt_associations
@@ -315,12 +315,12 @@ class ReflectionTest < ActiveRecord::TestCase
end
def test_nested?
- assert !Author.reflect_on_association(:comments).nested?
- assert Author.reflect_on_association(:tags).nested?
+ assert_not_predicate Author.reflect_on_association(:comments), :nested?
+ assert_predicate Author.reflect_on_association(:tags), :nested?
# Only goes :through once, but the through_reflection is a has_and_belongs_to_many, so this is
# a nested through association
- assert Category.reflect_on_association(:post_comments).nested?
+ assert_predicate Category.reflect_on_association(:post_comments), :nested?
end
def test_association_primary_key
@@ -335,15 +335,6 @@ class ReflectionTest < ActiveRecord::TestCase
assert_equal "custom_primary_key", Author.reflect_on_association(:tags_with_primary_key).association_primary_key.to_s # nested
end
- def test_association_primary_key_type
- # Normal Association
- assert_equal :integer, Author.reflect_on_association(:posts).association_primary_key_type.type
- assert_equal :string, Author.reflect_on_association(:essay).association_primary_key_type.type
-
- # Through Association
- assert_equal :string, Author.reflect_on_association(:essay_category).association_primary_key_type.type
- end
-
def test_association_primary_key_raises_when_missing_primary_key
reflection = ActiveRecord::Reflection.create(:has_many, :edge, nil, {}, Author)
assert_raises(ActiveRecord::UnknownPrimaryKey) { reflection.association_primary_key }
@@ -364,42 +355,49 @@ class ReflectionTest < ActiveRecord::TestCase
assert_raises(ActiveRecord::UnknownPrimaryKey) { reflection.active_record_primary_key }
end
+ def test_type
+ assert_equal "taggable_type", Post.reflect_on_association(:taggings).type.to_s
+ assert_equal "imageable_class", Post.reflect_on_association(:images).type.to_s
+ assert_nil Post.reflect_on_association(:readers).type
+ end
+
def test_foreign_type
assert_equal "sponsorable_type", Sponsor.reflect_on_association(:sponsorable).foreign_type.to_s
assert_equal "sponsorable_type", Sponsor.reflect_on_association(:thing).foreign_type.to_s
+ assert_nil Sponsor.reflect_on_association(:sponsor_club).foreign_type
end
def test_collection_association
- assert Pirate.reflect_on_association(:birds).collection?
- assert Pirate.reflect_on_association(:parrots).collection?
+ assert_predicate Pirate.reflect_on_association(:birds), :collection?
+ assert_predicate Pirate.reflect_on_association(:parrots), :collection?
- assert !Pirate.reflect_on_association(:ship).collection?
- assert !Ship.reflect_on_association(:pirate).collection?
+ assert_not_predicate Pirate.reflect_on_association(:ship), :collection?
+ assert_not_predicate Ship.reflect_on_association(:pirate), :collection?
end
def test_default_association_validation
- assert ActiveRecord::Reflection.create(:has_many, :clients, nil, {}, Firm).validate?
+ assert_predicate ActiveRecord::Reflection.create(:has_many, :clients, nil, {}, Firm), :validate?
- assert !ActiveRecord::Reflection.create(:has_one, :client, nil, {}, Firm).validate?
- assert !ActiveRecord::Reflection.create(:belongs_to, :client, nil, {}, Firm).validate?
+ assert_not_predicate ActiveRecord::Reflection.create(:has_one, :client, nil, {}, Firm), :validate?
+ assert_not_predicate ActiveRecord::Reflection.create(:belongs_to, :client, nil, {}, Firm), :validate?
end
def test_always_validate_association_if_explicit
- assert ActiveRecord::Reflection.create(:has_one, :client, nil, { validate: true }, Firm).validate?
- assert ActiveRecord::Reflection.create(:belongs_to, :client, nil, { validate: true }, Firm).validate?
- assert ActiveRecord::Reflection.create(:has_many, :clients, nil, { validate: true }, Firm).validate?
+ assert_predicate ActiveRecord::Reflection.create(:has_one, :client, nil, { validate: true }, Firm), :validate?
+ assert_predicate ActiveRecord::Reflection.create(:belongs_to, :client, nil, { validate: true }, Firm), :validate?
+ assert_predicate ActiveRecord::Reflection.create(:has_many, :clients, nil, { validate: true }, Firm), :validate?
end
def test_validate_association_if_autosave
- assert ActiveRecord::Reflection.create(:has_one, :client, nil, { autosave: true }, Firm).validate?
- assert ActiveRecord::Reflection.create(:belongs_to, :client, nil, { autosave: true }, Firm).validate?
- assert ActiveRecord::Reflection.create(:has_many, :clients, nil, { autosave: true }, Firm).validate?
+ assert_predicate ActiveRecord::Reflection.create(:has_one, :client, nil, { autosave: true }, Firm), :validate?
+ assert_predicate ActiveRecord::Reflection.create(:belongs_to, :client, nil, { autosave: true }, Firm), :validate?
+ assert_predicate ActiveRecord::Reflection.create(:has_many, :clients, nil, { autosave: true }, Firm), :validate?
end
def test_never_validate_association_if_explicit
- assert !ActiveRecord::Reflection.create(:has_one, :client, nil, { autosave: true, validate: false }, Firm).validate?
- assert !ActiveRecord::Reflection.create(:belongs_to, :client, nil, { autosave: true, validate: false }, Firm).validate?
- assert !ActiveRecord::Reflection.create(:has_many, :clients, nil, { autosave: true, validate: false }, Firm).validate?
+ assert_not_predicate ActiveRecord::Reflection.create(:has_one, :client, nil, { autosave: true, validate: false }, Firm), :validate?
+ assert_not_predicate ActiveRecord::Reflection.create(:belongs_to, :client, nil, { autosave: true, validate: false }, Firm), :validate?
+ assert_not_predicate ActiveRecord::Reflection.create(:has_many, :clients, nil, { autosave: true, validate: false }, Firm), :validate?
end
def test_foreign_key
@@ -407,26 +405,15 @@ class ReflectionTest < ActiveRecord::TestCase
assert_equal "category_id", Post.reflect_on_association(:categorizations).foreign_key.to_s
end
- def test_through_reflection_scope_chain_does_not_modify_other_reflections
- orig_conds = assert_deprecated do
- Post.reflect_on_association(:first_blue_tags_2).scope_chain
- end.inspect
- assert_deprecated do
- Author.reflect_on_association(:misc_post_first_blue_tags_2).scope_chain
- end
- assert_equal orig_conds, assert_deprecated {
- Post.reflect_on_association(:first_blue_tags_2).scope_chain
- }.inspect
- end
-
def test_symbol_for_class_name
assert_equal Client, Firm.reflect_on_association(:unsorted_clients_with_symbol).klass
end
def test_class_for_class_name
- assert_deprecated do
- assert_predicate ActiveRecord::Reflection.create(:has_many, :clients, nil, { class_name: Client }, Firm), :validate?
+ error = assert_raises(ArgumentError) do
+ ActiveRecord::Reflection.create(:has_many, :clients, nil, { class_name: Client }, Firm)
end
+ assert_equal "A class was passed to `:class_name` but we are expecting a string.", error.message
end
def test_join_table
diff --git a/activerecord/test/cases/relation/delegation_test.rb b/activerecord/test/cases/relation/delegation_test.rb
index 49d4aeafc9..a8030c2d64 100644
--- a/activerecord/test/cases/relation/delegation_test.rb
+++ b/activerecord/test/cases/relation/delegation_test.rb
@@ -1,39 +1,19 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/post"
require "models/comment"
module ActiveRecord
- class DelegationTest < ActiveRecord::TestCase
- fixtures :posts
-
- def call_method(target, method)
- method_arity = target.to_a.method(method).arity
-
- if method_arity.zero?
- target.public_send(method)
- elsif method_arity < 0
- if method == :shuffle!
- target.public_send(method)
- else
- target.public_send(method, 1)
- end
- elsif method_arity == 1
- target.public_send(method, 1)
- else
- raise NotImplementedError
- end
- end
- end
-
- module DelegationWhitelistBlacklistTests
+ module ArrayDelegationTests
ARRAY_DELEGATES = [
:+, :-, :|, :&, :[], :shuffle,
:all?, :collect, :compact, :detect, :each, :each_cons, :each_with_index,
:exclude?, :find_all, :flat_map, :group_by, :include?, :length,
- :map, :none?, :one?, :partition, :reject, :reverse,
- :sample, :second, :sort, :sort_by, :third,
+ :map, :none?, :one?, :partition, :reject, :reverse, :rotate,
+ :sample, :second, :sort, :sort_by, :slice, :third, :index, :rindex,
:to_ary, :to_set, :to_xml, :to_yaml, :join,
- :in_groups, :in_groups_of, :to_sentence, :to_formatted_s
+ :in_groups, :in_groups_of, :to_sentence, :to_formatted_s, :as_json
]
ARRAY_DELEGATES.each do |method|
@@ -43,21 +23,63 @@ module ActiveRecord
end
end
- class DelegationAssociationTest < DelegationTest
- include DelegationWhitelistBlacklistTests
+ module DeprecatedArelDelegationTests
+ AREL_METHODS = [
+ :with, :orders, :froms, :project, :projections, :taken, :constraints, :exists, :locked, :where_sql,
+ :ast, :source, :join_sources, :to_dot, :create_insert, :create_true, :create_false
+ ]
- def target
- Post.first.comments
+ def test_deprecate_arel_delegation
+ AREL_METHODS.each do |method|
+ assert_deprecated { target.public_send(method) }
+ assert_deprecated { target.public_send(method) }
+ end
end
end
- class DelegationRelationTest < DelegationTest
- include DelegationWhitelistBlacklistTests
+ class DelegationAssociationTest < ActiveRecord::TestCase
+ include ArrayDelegationTests
+ include DeprecatedArelDelegationTests
+
+ def target
+ Post.new.comments
+ end
+ end
- fixtures :comments
+ class DelegationRelationTest < ActiveRecord::TestCase
+ include ArrayDelegationTests
+ include DeprecatedArelDelegationTests
def target
Comment.all
end
end
+
+ class QueryingMethodsDelegationTest < ActiveRecord::TestCase
+ QUERYING_METHODS = [
+ :find, :take, :take!, :first, :first!, :last, :last!, :exists?, :any?, :many?, :none?, :one?,
+ :second, :second!, :third, :third!, :fourth, :fourth!, :fifth, :fifth!, :forty_two, :forty_two!, :third_to_last, :third_to_last!, :second_to_last, :second_to_last!,
+ :first_or_create, :first_or_create!, :first_or_initialize,
+ :find_or_create_by, :find_or_create_by!, :create_or_find_by, :create_or_find_by!, :find_or_initialize_by,
+ :find_by, :find_by!,
+ :destroy_all, :delete_all, :update_all,
+ :find_each, :find_in_batches, :in_batches,
+ :select, :group, :order, :except, :reorder, :limit, :offset, :joins, :left_joins, :left_outer_joins, :or,
+ :where, :rewhere, :preload, :eager_load, :includes, :from, :lock, :readonly, :extending,
+ :having, :create_with, :distinct, :references, :none, :unscope, :merge,
+ :count, :average, :minimum, :maximum, :sum, :calculate,
+ :pluck, :pick, :ids,
+ ]
+
+ def test_delegate_querying_methods
+ klass = Class.new(ActiveRecord::Base) do
+ self.table_name = "posts"
+ end
+
+ QUERYING_METHODS.each do |method|
+ assert_respond_to klass.all, method
+ assert_respond_to klass, method
+ end
+ end
+ end
end
diff --git a/activerecord/test/cases/relation/merging_test.rb b/activerecord/test/cases/relation/merging_test.rb
index 278dac8171..6e7998d15a 100644
--- a/activerecord/test/cases/relation/merging_test.rb
+++ b/activerecord/test/cases/relation/merging_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/author"
require "models/comment"
@@ -8,7 +10,7 @@ require "models/project"
require "models/rating"
class RelationMergingTest < ActiveRecord::TestCase
- fixtures :developers, :comments, :authors, :posts
+ fixtures :developers, :comments, :authors, :author_addresses, :posts
def test_relation_merging
devs = Developer.where("salary >= 80000").merge(Developer.limit(2)).merge(Developer.order("id ASC").where("id < 3"))
@@ -21,7 +23,7 @@ class RelationMergingTest < ActiveRecord::TestCase
def test_relation_to_sql
post = Post.first
sql = post.comments.to_sql
- assert_match(/.?post_id.? = #{post.id}\Z/i, sql)
+ assert_match(/.?post_id.? = #{post.id}\z/i, sql)
end
def test_relation_merging_with_arel_equalities_keeps_last_equality
@@ -56,7 +58,7 @@ class RelationMergingTest < ActiveRecord::TestCase
def test_relation_merging_with_locks
devs = Developer.lock.where("salary >= 80000").order("id DESC").merge(Developer.limit(2))
- assert devs.locked.present?
+ assert_predicate devs, :locked?
end
def test_relation_merging_with_preload
@@ -70,6 +72,16 @@ class RelationMergingTest < ActiveRecord::TestCase
assert_equal 1, comments.count
end
+ def test_relation_merging_with_left_outer_joins
+ comments = Comment.joins(:post).where(body: "Thank you for the welcome").merge(Post.left_outer_joins(:author).where(body: "Such a lovely day"))
+
+ assert_equal 1, comments.count
+ end
+
+ def test_relation_merging_with_skip_query_cache
+ assert_equal Post.all.merge(Post.all.skip_query_cache!).skip_query_cache_value, true
+ end
+
def test_relation_merging_with_association
assert_queries(2) do # one for loading post, and another one merged query
post = Post.where(body: "Such a lovely day").first
@@ -79,13 +91,11 @@ class RelationMergingTest < ActiveRecord::TestCase
end
test "merge collapses wheres from the LHS only" do
- left = Post.where(title: "omg").where(comments_count: 1)
+ left = Post.where(title: "omg").where(comments_count: 1)
right = Post.where(title: "wtf").where(title: "bbq")
- expected = [left.bound_attributes[1]] + right.bound_attributes
- merged = left.merge(right)
+ merged = left.merge(right)
- assert_equal expected, merged.bound_attributes
assert_not_includes merged.to_sql, "omg"
assert_includes merged.to_sql, "wtf"
assert_includes merged.to_sql, "bbq"
@@ -107,14 +117,24 @@ class RelationMergingTest < ActiveRecord::TestCase
def test_merging_with_from_clause
relation = Post.all
- assert relation.from_clause.empty?
+ assert_empty relation.from_clause
relation = relation.merge(Post.from("posts"))
- refute relation.from_clause.empty?
+ assert_not_empty relation.from_clause
+ end
+
+ def test_merging_with_order_with_binds
+ relation = Post.all.merge(Post.order([Arel.sql("title LIKE ?"), "%suffix"]))
+ assert_equal ["title LIKE '%suffix'"], relation.order_values
+ end
+
+ def test_merging_with_order_without_binds
+ relation = Post.all.merge(Post.order(Arel.sql("title LIKE '%?'")))
+ assert_equal ["title LIKE '%?'"], relation.order_values
end
end
class MergingDifferentRelationsTest < ActiveRecord::TestCase
- fixtures :posts, :authors, :developers
+ fixtures :posts, :authors, :author_addresses, :developers
test "merging where relations" do
hello_by_bob = Post.where(body: "hello").joins(:author).
@@ -143,7 +163,7 @@ class MergingDifferentRelationsTest < ActiveRecord::TestCase
assert_equal ["Mary", "Mary", "Mary", "David"], posts_by_author_name
end
- test "relation merging (using a proc argument)" do
+ test "relation merging (using a proc argument)" do
dev = Developer.where(name: "Jamis").first
comment_1 = dev.comments.create!(body: "I'm Jamis", post: Post.first)
diff --git a/activerecord/test/cases/relation/mutation_test.rb b/activerecord/test/cases/relation/mutation_test.rb
index 4f92f71a09..f82ecd4449 100644
--- a/activerecord/test/cases/relation/mutation_test.rb
+++ b/activerecord/test/cases/relation/mutation_test.rb
@@ -1,42 +1,11 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/post"
module ActiveRecord
class RelationMutationTest < ActiveRecord::TestCase
- FakeKlass = Struct.new(:table_name, :name) do
- extend ActiveRecord::Delegation::DelegateCache
- inherited self
-
- def connection
- Post.connection
- end
-
- def relation_delegate_class(klass)
- self.class.relation_delegate_class(klass)
- end
-
- def attribute_alias?(name)
- false
- end
-
- def sanitize_sql(sql)
- sql
- end
-
- def sanitize_sql_for_order(sql)
- sql
- end
-
- def arel_attribute(name, table)
- table[name]
- end
- end
-
- def relation
- @relation ||= Relation.new FakeKlass.new("posts"), Post.arel_table, Post.predicate_builder
- end
-
- (Relation::MULTI_VALUE_METHODS - [:references, :extending, :order, :unscope, :select, :left_joins]).each do |method|
+ (Relation::MULTI_VALUE_METHODS - [:references, :extending, :order, :unscope, :select]).each do |method|
test "##{method}!" do
assert relation.public_send("#{method}!", :foo).equal?(relation)
assert_equal [:foo], relation.public_send("#{method}_values")
@@ -56,7 +25,7 @@ module ActiveRecord
test "#order! with symbol prepends the table name" do
assert relation.order!(:name).equal?(relation)
node = relation.order_values.first
- assert node.ascending?
+ assert_predicate node, :ascending?
assert_equal :name, node.expr.name
assert_equal "posts", node.expr.relation.name
end
@@ -90,7 +59,7 @@ module ActiveRecord
assert_equal [], relation.extending_values
end
- (Relation::SINGLE_VALUE_METHODS - [:lock, :reordering, :reverse_order, :create_with]).each do |method|
+ (Relation::SINGLE_VALUE_METHODS - [:lock, :reordering, :reverse_order, :create_with, :skip_query_cache]).each do |method|
test "##{method}!" do
assert relation.public_send("#{method}!", :foo).equal?(relation)
assert_equal :foo, relation.public_send("#{method}_value")
@@ -119,7 +88,7 @@ module ActiveRecord
assert relation.reorder!(:name).equal?(relation)
node = relation.order_values.first
- assert node.ascending?
+ assert_predicate node, :ascending?
assert_equal :name, node.expr.name
assert_equal "posts", node.expr.relation.name
end
@@ -143,7 +112,7 @@ module ActiveRecord
assert_equal({ foo: "bar" }, relation.create_with_value)
end
- test "test_merge!" do
+ test "merge!" do
assert relation.merge!(select: :foo).equal?(relation)
assert_equal [:foo], relation.select_values
end
@@ -162,5 +131,20 @@ module ActiveRecord
relation.distinct! :foo
assert_equal :foo, relation.distinct_value
end
+
+ test "skip_query_cache!" do
+ relation.skip_query_cache!
+ assert relation.skip_query_cache_value
+ end
+
+ test "skip_preloading!" do
+ relation.skip_preloading!
+ assert relation.skip_preloading_value
+ end
+
+ private
+ def relation
+ @relation ||= Relation.new(FakeKlass)
+ end
end
end
diff --git a/activerecord/test/cases/relation/or_test.rb b/activerecord/test/cases/relation/or_test.rb
index abb7ca72dd..065819e0f1 100644
--- a/activerecord/test/cases/relation/or_test.rb
+++ b/activerecord/test/cases/relation/or_test.rb
@@ -1,9 +1,14 @@
+# frozen_string_literal: true
+
require "cases/helper"
+require "models/author"
+require "models/categorization"
require "models/post"
module ActiveRecord
class OrTest < ActiveRecord::TestCase
fixtures :posts
+ fixtures :authors, :author_addresses
def test_or_with_relation
expected = Post.where("id = 1 or id = 2").to_a
@@ -26,7 +31,7 @@ module ActiveRecord
end
def test_or_with_bind_params
- assert_equal Post.find([1, 2]), Post.where(id: 1).or(Post.where(id: 2)).to_a
+ assert_equal Post.find([1, 2]).sort_by(&:id), Post.where(id: 1).or(Post.where(id: 2)).sort_by(&:id)
end
def test_or_with_null_both
@@ -59,6 +64,31 @@ module ActiveRecord
assert_equal "Relation passed to #or must be structurally compatible. Incompatible values: [:order]", error.message
end
+ def test_or_with_unscope_where
+ expected = Post.where("id = 1 or id = 2")
+ partial = Post.where("id = 1 and id != 2")
+ assert_equal expected, partial.or(partial.unscope(:where).where("id = 2")).to_a
+ end
+
+ def test_or_with_unscope_where_column
+ expected = Post.where("id = 1 or id = 2")
+ partial = Post.where(id: 1).where.not(id: 2)
+ assert_equal expected, partial.or(partial.unscope(where: :id).where("id = 2")).to_a
+ end
+
+ def test_or_with_unscope_order
+ expected = Post.where("id = 1 or id = 2")
+ assert_equal expected, Post.order("body asc").where("id = 1").unscope(:order).or(Post.where("id = 2")).to_a
+ end
+
+ def test_or_with_incompatible_unscope
+ error = assert_raises ArgumentError do
+ Post.order("body asc").where("id = 1").or(Post.order("body asc").where("id = 2").unscope(:order)).to_a
+ end
+
+ assert_equal "Relation passed to #or must be structurally compatible. Incompatible values: [:order]", error.message
+ end
+
def test_or_when_grouping
groups = Post.where("id < 10").group("body").select("body, COUNT(*) AS c")
expected = groups.having("COUNT(*) > 1 OR body like 'Such%'").to_a.map { |o| [o.body, o.c] }
@@ -88,5 +118,20 @@ module ActiveRecord
Post.where(id: [1, 2, 3]).or(title: "Rails")
end
end
+
+ def test_or_with_references_inequality
+ joined = Post.includes(:author)
+ actual = joined.where(authors: { id: 1 })
+ .or(joined.where(title: "I don't have any comments"))
+ expected = Author.find(1).posts + Post.where(title: "I don't have any comments")
+ assert_equal expected.sort_by(&:id), actual.sort_by(&:id)
+ end
+
+ def test_or_with_scope_on_association
+ author = Author.first
+ assert_nothing_raised do
+ author.top_posts.or(author.other_top_posts)
+ end
+ end
end
end
diff --git a/activerecord/test/cases/relation/predicate_builder_test.rb b/activerecord/test/cases/relation/predicate_builder_test.rb
index 48758dc148..b432330deb 100644
--- a/activerecord/test/cases/relation/predicate_builder_test.rb
+++ b/activerecord/test/cases/relation/predicate_builder_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/topic"
diff --git a/activerecord/test/cases/relation/record_fetch_warning_test.rb b/activerecord/test/cases/relation/record_fetch_warning_test.rb
index 62ca051431..22d32d75bc 100644
--- a/activerecord/test/cases/relation/record_fetch_warning_test.rb
+++ b/activerecord/test/cases/relation/record_fetch_warning_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/post"
require "active_record/relation/record_fetch_warning"
diff --git a/activerecord/test/cases/relation/select_test.rb b/activerecord/test/cases/relation/select_test.rb
new file mode 100644
index 0000000000..dec8a6925d
--- /dev/null
+++ b/activerecord/test/cases/relation/select_test.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require "cases/helper"
+require "models/post"
+
+module ActiveRecord
+ class SelectTest < ActiveRecord::TestCase
+ fixtures :posts
+
+ def test_select_with_nil_argument
+ expected = Post.select(:title).to_sql
+ assert_equal expected, Post.select(nil).select(:title).to_sql
+ end
+ end
+end
diff --git a/activerecord/test/cases/relation/where_chain_test.rb b/activerecord/test/cases/relation/where_chain_test.rb
index a96d1ae5b5..a68eb2b446 100644
--- a/activerecord/test/cases/relation/where_chain_test.rb
+++ b/activerecord/test/cases/relation/where_chain_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/post"
require "models/comment"
@@ -25,7 +27,7 @@ module ActiveRecord
end
def test_association_not_eq
- expected = Arel::Nodes::Grouping.new(Comment.arel_table[@name].not_eq(Arel::Nodes::BindParam.new))
+ expected = Comment.arel_table[@name].not_eq(Arel::Nodes::BindParam.new(1))
relation = Post.joins(:comments).where.not(comments: { title: "hello" })
assert_equal(expected.to_sql, relation.where_clause.ast.to_sql)
end
diff --git a/activerecord/test/cases/relation/where_clause_test.rb b/activerecord/test/cases/relation/where_clause_test.rb
index d8e4c304f0..8703d238a0 100644
--- a/activerecord/test/cases/relation/where_clause_test.rb
+++ b/activerecord/test/cases/relation/where_clause_test.rb
@@ -1,78 +1,86 @@
+# frozen_string_literal: true
+
require "cases/helper"
class ActiveRecord::Relation
class WhereClauseTest < ActiveRecord::TestCase
test "+ combines two where clauses" do
- first_clause = WhereClause.new([table["id"].eq(bind_param)], [["id", 1]])
- second_clause = WhereClause.new([table["name"].eq(bind_param)], [["name", "Sean"]])
+ first_clause = WhereClause.new([table["id"].eq(bind_param(1))])
+ second_clause = WhereClause.new([table["name"].eq(bind_param("Sean"))])
combined = WhereClause.new(
- [table["id"].eq(bind_param), table["name"].eq(bind_param)],
- [["id", 1], ["name", "Sean"]],
+ [table["id"].eq(bind_param(1)), table["name"].eq(bind_param("Sean"))],
)
assert_equal combined, first_clause + second_clause
end
test "+ is associative, but not commutative" do
- a = WhereClause.new(["a"], ["bind a"])
- b = WhereClause.new(["b"], ["bind b"])
- c = WhereClause.new(["c"], ["bind c"])
+ a = WhereClause.new(["a"])
+ b = WhereClause.new(["b"])
+ c = WhereClause.new(["c"])
assert_equal a + (b + c), (a + b) + c
assert_not_equal a + b, b + a
end
test "an empty where clause is the identity value for +" do
- clause = WhereClause.new([table["id"].eq(bind_param)], [["id", 1]])
+ clause = WhereClause.new([table["id"].eq(bind_param(1))])
assert_equal clause, clause + WhereClause.empty
end
test "merge combines two where clauses" do
- a = WhereClause.new([table["id"].eq(1)], [])
- b = WhereClause.new([table["name"].eq("Sean")], [])
- expected = WhereClause.new([table["id"].eq(1), table["name"].eq("Sean")], [])
+ a = WhereClause.new([table["id"].eq(1)])
+ b = WhereClause.new([table["name"].eq("Sean")])
+ expected = WhereClause.new([table["id"].eq(1), table["name"].eq("Sean")])
assert_equal expected, a.merge(b)
end
test "merge keeps the right side, when two equality clauses reference the same column" do
- a = WhereClause.new([table["id"].eq(1), table["name"].eq("Sean")], [])
- b = WhereClause.new([table["name"].eq("Jim")], [])
- expected = WhereClause.new([table["id"].eq(1), table["name"].eq("Jim")], [])
+ a = WhereClause.new([table["id"].eq(1), table["name"].eq("Sean")])
+ b = WhereClause.new([table["name"].eq("Jim")])
+ expected = WhereClause.new([table["id"].eq(1), table["name"].eq("Jim")])
assert_equal expected, a.merge(b)
end
test "merge removes bind parameters matching overlapping equality clauses" do
a = WhereClause.new(
- [table["id"].eq(bind_param), table["name"].eq(bind_param)],
- [attribute("id", 1), attribute("name", "Sean")],
+ [table["id"].eq(bind_param(1)), table["name"].eq(bind_param("Sean"))],
)
b = WhereClause.new(
- [table["name"].eq(bind_param)],
- [attribute("name", "Jim")]
+ [table["name"].eq(bind_param("Jim"))],
)
expected = WhereClause.new(
- [table["id"].eq(bind_param), table["name"].eq(bind_param)],
- [attribute("id", 1), attribute("name", "Jim")],
+ [table["id"].eq(bind_param(1)), table["name"].eq(bind_param("Jim"))],
)
assert_equal expected, a.merge(b)
end
test "merge allows for columns with the same name from different tables" do
- skip "This is not possible as of 4.2, and the binds do not yet contain sufficient information for this to happen"
- # We might be able to change the implementation to remove conflicts by index, rather than column name
+ table2 = Arel::Table.new("table2")
+ a = WhereClause.new(
+ [table["id"].eq(bind_param(1)), table2["id"].eq(bind_param(2))],
+ )
+ b = WhereClause.new(
+ [table["id"].eq(bind_param(3))],
+ )
+ expected = WhereClause.new(
+ [table2["id"].eq(bind_param(2)), table["id"].eq(bind_param(3))],
+ )
+
+ assert_equal expected, a.merge(b)
end
test "a clause knows if it is empty" do
- assert WhereClause.empty.empty?
- assert_not WhereClause.new(["anything"], []).empty?
+ assert_empty WhereClause.empty
+ assert_not_empty WhereClause.new(["anything"])
end
test "invert cannot handle nil" do
- where_clause = WhereClause.new([nil], [])
+ where_clause = WhereClause.new([nil])
assert_raises ArgumentError do
where_clause.invert
@@ -86,37 +94,47 @@ class ActiveRecord::Relation
table["id"].eq(1),
"sql literal",
random_object
- ], [])
+ ])
expected = WhereClause.new([
table["id"].not_in([1, 2, 3]),
table["id"].not_eq(1),
Arel::Nodes::Not.new(Arel::Nodes::SqlLiteral.new("sql literal")),
Arel::Nodes::Not.new(random_object)
- ], [])
+ ])
assert_equal expected, original.invert
end
- test "accept removes binary predicates referencing a given column" do
+ test "except removes binary predicates referencing a given column" do
where_clause = WhereClause.new([
table["id"].in([1, 2, 3]),
- table["name"].eq(bind_param),
- table["age"].gteq(bind_param),
- ], [
- attribute("name", "Sean"),
- attribute("age", 30),
+ table["name"].eq(bind_param("Sean")),
+ table["age"].gteq(bind_param(30)),
])
- expected = WhereClause.new([table["age"].gteq(bind_param)], [attribute("age", 30)])
+ expected = WhereClause.new([table["age"].gteq(bind_param(30))])
assert_equal expected, where_clause.except("id", "name")
end
+ test "except jumps over unhandled binds (like with OR) correctly" do
+ wcs = (0..9).map do |i|
+ WhereClause.new([table["id#{i}"].eq(bind_param(i))])
+ end
+
+ wc = wcs[0] + wcs[1] + wcs[2].or(wcs[3]) + wcs[4] + wcs[5] + wcs[6].or(wcs[7]) + wcs[8] + wcs[9]
+
+ expected = wcs[0] + wcs[2].or(wcs[3]) + wcs[5] + wcs[6].or(wcs[7]) + wcs[9]
+ actual = wc.except("id1", "id2", "id4", "id7", "id8")
+
+ assert_equal expected, actual
+ end
+
test "ast groups its predicates with AND" do
predicates = [
table["id"].in([1, 2, 3]),
- table["name"].eq(bind_param),
+ table["name"].eq(bind_param(nil)),
]
- where_clause = WhereClause.new(predicates, [])
+ where_clause = WhereClause.new(predicates)
expected = Arel::Nodes::And.new(predicates)
assert_equal expected, where_clause.ast
@@ -128,55 +146,96 @@ class ActiveRecord::Relation
table["id"].in([1, 2, 3]),
"foo = bar",
random_object,
- ], [])
+ ])
expected = Arel::Nodes::And.new([
table["id"].in([1, 2, 3]),
Arel::Nodes::Grouping.new(Arel.sql("foo = bar")),
- Arel::Nodes::Grouping.new(random_object),
+ random_object,
])
assert_equal expected, where_clause.ast
end
test "ast removes any empty strings" do
- where_clause = WhereClause.new([table["id"].in([1, 2, 3])], [])
- where_clause_with_empty = WhereClause.new([table["id"].in([1, 2, 3]), ""], [])
+ where_clause = WhereClause.new([table["id"].in([1, 2, 3])])
+ where_clause_with_empty = WhereClause.new([table["id"].in([1, 2, 3]), ""])
assert_equal where_clause.ast, where_clause_with_empty.ast
end
test "or joins the two clauses using OR" do
- where_clause = WhereClause.new([table["id"].eq(bind_param)], [attribute("id", 1)])
- other_clause = WhereClause.new([table["name"].eq(bind_param)], [attribute("name", "Sean")])
+ where_clause = WhereClause.new([table["id"].eq(bind_param(1))])
+ other_clause = WhereClause.new([table["name"].eq(bind_param("Sean"))])
expected_ast =
Arel::Nodes::Grouping.new(
- Arel::Nodes::Or.new(table["id"].eq(bind_param), table["name"].eq(bind_param))
+ Arel::Nodes::Or.new(table["id"].eq(bind_param(1)), table["name"].eq(bind_param("Sean")))
)
- expected_binds = where_clause.binds + other_clause.binds
assert_equal expected_ast.to_sql, where_clause.or(other_clause).ast.to_sql
- assert_equal expected_binds, where_clause.or(other_clause).binds
end
test "or returns an empty where clause when either side is empty" do
- where_clause = WhereClause.new([table["id"].eq(bind_param)], [attribute("id", 1)])
+ where_clause = WhereClause.new([table["id"].eq(bind_param(1))])
assert_equal WhereClause.empty, where_clause.or(WhereClause.empty)
assert_equal WhereClause.empty, WhereClause.empty.or(where_clause)
end
+ test "or places common conditions before the OR" do
+ a = WhereClause.new(
+ [table["id"].eq(bind_param(1)), table["name"].eq(bind_param("Sean"))],
+ )
+ b = WhereClause.new(
+ [table["id"].eq(bind_param(1)), table["hair_color"].eq(bind_param("black"))],
+ )
+
+ common = WhereClause.new(
+ [table["id"].eq(bind_param(1))],
+ )
+
+ or_clause = WhereClause.new([table["name"].eq(bind_param("Sean"))])
+ .or(WhereClause.new([table["hair_color"].eq(bind_param("black"))]))
+
+ assert_equal common + or_clause, a.or(b)
+ end
+
+ test "or can detect identical or as being a common condition" do
+ common_or = WhereClause.new([table["name"].eq(bind_param("Sean"))])
+ .or(WhereClause.new([table["hair_color"].eq(bind_param("black"))]))
+
+ a = common_or + WhereClause.new([table["id"].eq(bind_param(1))])
+ b = common_or + WhereClause.new([table["foo"].eq(bind_param("bar"))])
+
+ new_or = WhereClause.new([table["id"].eq(bind_param(1))])
+ .or(WhereClause.new([table["foo"].eq(bind_param("bar"))]))
+
+ assert_equal common_or + new_or, a.or(b)
+ end
+
+ test "or will use only common conditions if one side only has common conditions" do
+ only_common = WhereClause.new([
+ table["id"].eq(bind_param(1)),
+ "foo = bar",
+ ])
+
+ common_with_extra = WhereClause.new([
+ table["id"].eq(bind_param(1)),
+ "foo = bar",
+ table["extra"].eq(bind_param("pluto")),
+ ])
+
+ assert_equal only_common, only_common.or(common_with_extra)
+ assert_equal only_common, common_with_extra.or(only_common)
+ end
+
private
def table
Arel::Table.new("table")
end
- def bind_param
- Arel::Nodes::BindParam.new
- end
-
- def attribute(name, value)
- ActiveRecord::Attribute.with_cast_value(name, value, ActiveRecord::Type::Value.new)
+ def bind_param(value)
+ Arel::Nodes::BindParam.new(value)
end
end
end
diff --git a/activerecord/test/cases/relation/where_test.rb b/activerecord/test/cases/relation/where_test.rb
index ed8ffddcf5..99797528b2 100644
--- a/activerecord/test/cases/relation/where_test.rb
+++ b/activerecord/test/cases/relation/where_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/author"
require "models/binary"
@@ -15,7 +17,7 @@ require "models/vertex"
module ActiveRecord
class WhereTest < ActiveRecord::TestCase
- fixtures :posts, :edges, :authors, :binaries, :essays, :cars, :treasures, :price_estimates
+ fixtures :posts, :edges, :authors, :author_addresses, :binaries, :essays, :cars, :treasures, :price_estimates, :topics
def test_where_copies_bind_params
author = authors(:david)
@@ -48,6 +50,10 @@ module ActiveRecord
assert_equal [chef], chefs.to_a
end
+ def test_where_with_casted_value_is_nil
+ assert_equal 4, Topic.where(last_read: "").count
+ end
+
def test_rewhere_on_root
assert_equal posts(:welcome), Post.rewhere(title: "Welcome to the weblog").first
end
@@ -103,6 +109,15 @@ module ActiveRecord
assert_equal expected.to_sql, actual.to_sql
end
+ def test_polymorphic_shallow_where_not
+ treasure = treasures(:sapphire)
+
+ expected = [price_estimates(:diamond), price_estimates(:honda)]
+ actual = PriceEstimate.where.not(estimate_of: treasure)
+
+ assert_equal expected.sort_by(&:id), actual.sort_by(&:id)
+ end
+
def test_polymorphic_nested_array_where
treasure = Treasure.new
treasure.id = 1
@@ -115,13 +130,23 @@ module ActiveRecord
assert_equal expected.to_sql, actual.to_sql
end
+ def test_polymorphic_nested_array_where_not
+ treasure = treasures(:diamond)
+ car = cars(:honda)
+
+ expected = [price_estimates(:sapphire_1), price_estimates(:sapphire_2)]
+ actual = PriceEstimate.where.not(estimate_of: [treasure, car])
+
+ assert_equal expected.sort_by(&:id), actual.sort_by(&:id)
+ end
+
def test_polymorphic_array_where_multiple_types
treasure_1 = treasures(:diamond)
treasure_2 = treasures(:sapphire)
car = cars(:honda)
expected = [price_estimates(:diamond), price_estimates(:sapphire_1), price_estimates(:sapphire_2), price_estimates(:honda)].sort
- actual = PriceEstimate.where(estimate_of: [treasure_1, treasure_2, car]).to_a.sort
+ actual = PriceEstimate.where(estimate_of: [treasure_1, treasure_2, car]).to_a.sort
assert_equal expected, actual
end
@@ -240,7 +265,7 @@ module ActiveRecord
end
def test_where_with_decimal_for_string_column
- count = Post.where(title: BigDecimal.new(0)).count
+ count = Post.where(title: BigDecimal(0)).count
assert_equal 0, count
end
@@ -289,6 +314,25 @@ module ActiveRecord
assert_equal essays(:david_modest_proposal), essay
end
+ def test_where_with_relation_on_has_many_association
+ essay = essays(:david_modest_proposal)
+ author = Author.where(essays: Essay.where(id: essay.id)).first
+
+ assert_equal authors(:david), author
+ end
+
+ def test_where_with_relation_on_has_one_association
+ author = authors(:david)
+ author_address = AuthorAddress.where(author: Author.where(id: author.id)).first
+ assert_equal author_addresses(:david_address), author_address
+ end
+
+
+ def test_where_on_association_with_select_relation
+ essay = Essay.where(author: Author.where(name: "David").select(:name)).take
+ assert_equal essays(:david_modest_proposal), essay
+ end
+
def test_where_with_strong_parameters
protected_params = Class.new do
attr_reader :permitted
diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb
index 1241bb54a4..fbeb617b29 100644
--- a/activerecord/test/cases/relation_test.rb
+++ b/activerecord/test/cases/relation_test.rb
@@ -1,56 +1,40 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/post"
require "models/comment"
require "models/author"
require "models/rating"
+require "models/categorization"
module ActiveRecord
class RelationTest < ActiveRecord::TestCase
- fixtures :posts, :comments, :authors
-
- FakeKlass = Struct.new(:table_name, :name) do
- extend ActiveRecord::Delegation::DelegateCache
-
- inherited self
-
- def self.connection
- Post.connection
- end
-
- def self.table_name
- "fake_table"
- end
-
- def self.sanitize_sql_for_order(sql)
- sql
- end
- end
+ fixtures :posts, :comments, :authors, :author_addresses, :ratings, :categorizations
def test_construction
- relation = Relation.new(FakeKlass, :b, nil)
+ relation = Relation.new(FakeKlass, table: :b)
assert_equal FakeKlass, relation.klass
assert_equal :b, relation.table
- assert !relation.loaded, "relation is not loaded"
+ assert_not relation.loaded, "relation is not loaded"
end
def test_responds_to_model_and_returns_klass
- relation = Relation.new(FakeKlass, :b, nil)
+ relation = Relation.new(FakeKlass)
assert_equal FakeKlass, relation.model
end
def test_initialize_single_values
- relation = Relation.new(FakeKlass, :b, nil)
- (Relation::SINGLE_VALUE_METHODS - [:create_with, :readonly]).each do |method|
+ relation = Relation.new(FakeKlass)
+ (Relation::SINGLE_VALUE_METHODS - [:create_with]).each do |method|
assert_nil relation.send("#{method}_value"), method.to_s
end
- assert_equal false, relation.readonly_value
value = relation.create_with_value
assert_equal({}, value)
assert_predicate value, :frozen?
end
def test_multi_value_initialize
- relation = Relation.new(FakeKlass, :b, nil)
+ relation = Relation.new(FakeKlass)
Relation::MULTI_VALUE_METHODS.each do |method|
values = relation.send("#{method}_values")
assert_equal [], values, method.to_s
@@ -59,70 +43,64 @@ module ActiveRecord
end
def test_extensions
- relation = Relation.new(FakeKlass, :b, nil)
+ relation = Relation.new(FakeKlass)
assert_equal [], relation.extensions
end
def test_empty_where_values_hash
- relation = Relation.new(FakeKlass, :b, nil)
+ relation = Relation.new(FakeKlass)
assert_equal({}, relation.where_values_hash)
end
def test_has_values
- relation = Relation.new(Post, Post.arel_table, Post.predicate_builder)
- relation.where! relation.table[:id].eq(10)
- assert_equal({ id: 10 }, relation.where_values_hash)
+ relation = Relation.new(Post)
+ relation.where!(id: 10)
+ assert_equal({ "id" => 10 }, relation.where_values_hash)
end
def test_values_wrong_table
- relation = Relation.new(Post, Post.arel_table, Post.predicate_builder)
+ relation = Relation.new(Post)
relation.where! Comment.arel_table[:id].eq(10)
assert_equal({}, relation.where_values_hash)
end
def test_tree_is_not_traversed
- relation = Relation.new(Post, Post.arel_table, Post.predicate_builder)
+ relation = Relation.new(Post)
left = relation.table[:id].eq(10)
right = relation.table[:id].eq(10)
- combine = left.and right
+ combine = left.or(right)
relation.where! combine
assert_equal({}, relation.where_values_hash)
end
- def test_table_name_delegates_to_klass
- relation = Relation.new(FakeKlass.new("posts"), :b, Post.predicate_builder)
- assert_equal "posts", relation.table_name
- end
-
def test_scope_for_create
- relation = Relation.new(FakeKlass, :b, nil)
+ relation = Relation.new(FakeKlass)
assert_equal({}, relation.scope_for_create)
end
def test_create_with_value
- relation = Relation.new(Post, Post.arel_table, Post.predicate_builder)
- hash = { hello: "world" }
- relation.create_with_value = hash
- assert_equal hash, relation.scope_for_create
- end
-
- def test_create_with_value_with_wheres
- relation = Relation.new(Post, Post.arel_table, Post.predicate_builder)
- relation.where! relation.table[:id].eq(10)
+ relation = Relation.new(Post)
relation.create_with_value = { hello: "world" }
- assert_equal({ hello: "world", id: 10 }, relation.scope_for_create)
+ assert_equal({ "hello" => "world" }, relation.scope_for_create)
end
- # FIXME: is this really wanted or expected behavior?
- def test_scope_for_create_is_cached
- relation = Relation.new(Post, Post.arel_table, Post.predicate_builder)
+ def test_create_with_value_with_wheres
+ relation = Relation.new(Post)
assert_equal({}, relation.scope_for_create)
- relation.where! relation.table[:id].eq(10)
- assert_equal({}, relation.scope_for_create)
+ relation.where!(id: 10)
+ assert_equal({ "id" => 10 }, relation.scope_for_create)
relation.create_with_value = { hello: "world" }
- assert_equal({}, relation.scope_for_create)
+ assert_equal({ "hello" => "world", "id" => 10 }, relation.scope_for_create)
+ end
+
+ def test_empty_scope
+ relation = Relation.new(Post)
+ assert_predicate relation, :empty_scope?
+
+ relation.merge!(relation)
+ assert_predicate relation, :empty_scope?
end
def test_bad_constants_raise_errors
@@ -132,31 +110,31 @@ module ActiveRecord
end
def test_empty_eager_loading?
- relation = Relation.new(FakeKlass, :b, nil)
- assert !relation.eager_loading?
+ relation = Relation.new(FakeKlass)
+ assert_not_predicate relation, :eager_loading?
end
def test_eager_load_values
- relation = Relation.new(FakeKlass, :b, nil)
+ relation = Relation.new(FakeKlass)
relation.eager_load! :b
- assert relation.eager_loading?
+ assert_predicate relation, :eager_loading?
end
def test_references_values
- relation = Relation.new(FakeKlass, :b, nil)
+ relation = Relation.new(FakeKlass)
assert_equal [], relation.references_values
relation = relation.references(:foo).references(:omg, :lol)
assert_equal ["foo", "omg", "lol"], relation.references_values
end
def test_references_values_dont_duplicate
- relation = Relation.new(FakeKlass, :b, nil)
+ relation = Relation.new(FakeKlass)
relation = relation.references(:foo).references(:foo)
assert_equal ["foo"], relation.references_values
end
test "merging a hash into a relation" do
- relation = Relation.new(Post, Post.arel_table, Post.predicate_builder)
+ relation = Relation.new(Post)
relation = relation.merge where: { name: :lol }, readonly: true
assert_equal({ "name" => :lol }, relation.where_clause.to_h)
@@ -164,7 +142,7 @@ module ActiveRecord
end
test "merging an empty hash into a relation" do
- assert_equal Relation::WhereClause.empty, Relation.new(FakeKlass, :b, nil).merge({}).where_clause
+ assert_equal Relation::WhereClause.empty, Relation.new(FakeKlass).merge({}).where_clause
end
test "merging a hash with unknown keys raises" do
@@ -172,7 +150,7 @@ module ActiveRecord
end
test "merging nil or false raises" do
- relation = Relation.new(FakeKlass, :b, nil)
+ relation = Relation.new(FakeKlass)
e = assert_raises(ArgumentError) do
relation = relation.merge nil
@@ -188,7 +166,7 @@ module ActiveRecord
end
test "#values returns a dup of the values" do
- relation = Relation.new(Post, Post.arel_table, Post.predicate_builder).where!(name: :foo)
+ relation = Relation.new(Post).where!(name: :foo)
values = relation.values
values[:where] = nil
@@ -196,7 +174,7 @@ module ActiveRecord
end
test "relations can be created with a values hash" do
- relation = Relation.new(FakeKlass, :b, nil, select: [:foo])
+ relation = Relation.new(FakeKlass, values: { select: [:foo] })
assert_equal [:foo], relation.select_values
end
@@ -208,13 +186,13 @@ module ActiveRecord
end
end
- relation = Relation.new(klass, :b, nil)
+ relation = Relation.new(klass)
relation.merge!(where: ["foo = ?", "bar"])
- assert_equal Relation::WhereClause.new(["foo = bar"], []), relation.where_clause
+ assert_equal Relation::WhereClause.new(["foo = bar"]), relation.where_clause
end
def test_merging_readonly_false
- relation = Relation.new(FakeKlass, :b, nil)
+ relation = Relation.new(FakeKlass)
readonly_false_relation = relation.readonly(false)
# test merging in both directions
assert_equal false, relation.merge(readonly_false_relation).readonly_value
@@ -224,7 +202,50 @@ module ActiveRecord
def test_relation_merging_with_merged_joins_as_symbols
special_comments_with_ratings = SpecialComment.joins(:ratings)
posts_with_special_comments_with_ratings = Post.group("posts.id").joins(:special_comments).merge(special_comments_with_ratings)
- assert_equal({ 2 => 1, 4 => 3, 5 => 1 }, authors(:david).posts.merge(posts_with_special_comments_with_ratings).count)
+ assert_equal({ 4 => 2 }, authors(:david).posts.merge(posts_with_special_comments_with_ratings).count)
+ end
+
+ def test_relation_merging_with_merged_symbol_joins_keeps_inner_joins
+ queries = capture_sql { Author.joins(:posts).merge(Post.joins(:comments)).to_a }
+
+ nb_inner_join = queries.sum { |sql| sql.scan(/INNER\s+JOIN/i).size }
+ assert_equal 2, nb_inner_join, "Wrong amount of INNER JOIN in query"
+ assert queries.none? { |sql| /LEFT\s+(OUTER)?\s+JOIN/i.match?(sql) }, "Shouldn't have any LEFT JOIN in query"
+ end
+
+ def test_relation_merging_with_merged_symbol_joins_has_correct_size_and_count
+ # Has one entry per comment
+ merged_authors_with_commented_posts_relation = Author.joins(:posts).merge(Post.joins(:comments))
+
+ post_ids_with_author = Post.joins(:author).pluck(:id)
+ manual_comments_on_post_that_have_author = Comment.where(post_id: post_ids_with_author).pluck(:id)
+
+ assert_equal manual_comments_on_post_that_have_author.size, merged_authors_with_commented_posts_relation.count
+ assert_equal manual_comments_on_post_that_have_author.size, merged_authors_with_commented_posts_relation.to_a.size
+ end
+
+ def test_relation_merging_with_merged_symbol_joins_is_aliased
+ categorizations_with_authors = Categorization.joins(:author)
+ queries = capture_sql { Post.joins(:author, :categorizations).merge(Author.select(:id)).merge(categorizations_with_authors).to_a }
+
+ nb_inner_join = queries.sum { |sql| sql.scan(/INNER\s+JOIN/i).size }
+ assert_equal 3, nb_inner_join, "Wrong amount of INNER JOIN in query"
+
+ # using `\W` as the column separator
+ assert queries.any? { |sql| %r[INNER\s+JOIN\s+#{Author.quoted_table_name}\s+\Wauthors_categorizations\W]i.match?(sql) }, "Should be aliasing the child INNER JOINs in query"
+ end
+
+ def test_relation_with_merged_joins_aliased_works
+ categorizations_with_authors = Categorization.joins(:author)
+ posts_with_joins_and_merges = Post.joins(:author, :categorizations)
+ .merge(Author.select(:id)).merge(categorizations_with_authors)
+
+ author_with_posts = Author.joins(:posts).ids
+ categorizations_with_author = Categorization.joins(:author).ids
+ posts_with_author_and_categorizations = Post.joins(:categorizations).where(author_id: author_with_posts, categorizations: { id: categorizations_with_author }).ids
+
+ assert_equal posts_with_author_and_categorizations.size, posts_with_joins_and_merges.count
+ assert_equal posts_with_author_and_categorizations.size, posts_with_joins_and_merges.to_a.size
end
def test_relation_merging_with_joins_as_join_dependency_pick_proper_parent
@@ -239,17 +260,17 @@ module ActiveRecord
def test_merge_raises_with_invalid_argument
assert_raises ArgumentError do
- relation = Relation.new(FakeKlass, :b, nil)
+ relation = Relation.new(FakeKlass)
relation.merge(true)
end
end
def test_respond_to_for_non_selected_element
post = Post.select(:title).first
- assert_equal false, post.respond_to?(:body), "post should not respond_to?(:body) since invoking it raises exception"
+ assert_not_respond_to post, :body, "post should not respond_to?(:body) since invoking it raises exception"
silence_warnings { post = Post.select("'title' as post_title").first }
- assert_equal false, post.respond_to?(:title), "post should not respond_to?(:body) since invoking it raises exception"
+ assert_not_respond_to post, :title, "post should not respond_to?(:body) since invoking it raises exception"
end
def test_select_quotes_when_using_from_clause
@@ -277,18 +298,32 @@ module ActiveRecord
assert_equal({ 2 => 1, 4 => 3, 5 => 1 }, authors(:david).posts.merge(posts_with_special_comments_with_ratings).count)
end
+ def test_relation_merging_keeps_joining_order
+ authors = Author.where(id: 1)
+ posts = Post.joins(:author).merge(authors)
+ comments = Comment.joins(:post).merge(posts)
+ ratings = Rating.joins(:comment).merge(comments)
+
+ assert_equal 3, ratings.count
+ end
+
class EnsureRoundTripTypeCasting < ActiveRecord::Type::Value
def type
:string
end
+ def cast(value)
+ raise value unless value == "value from user"
+ "cast value"
+ end
+
def deserialize(value)
raise value unless value == "type cast for database"
"type cast from database"
end
def serialize(value)
- raise value unless value == "value from user"
+ raise value unless value == "cast value"
"type cast for database"
end
end
@@ -305,6 +340,14 @@ module ActiveRecord
assert_equal "type cast from database", UpdateAllTestModel.first.body
end
+ def test_skip_preloading_after_arel_has_been_generated
+ assert_nothing_raised do
+ relation = Comment.all
+ relation.arel
+ relation.skip_preloading!
+ end
+ end
+
private
def skip_if_sqlite3_version_includes_quoting_bug
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 0c94e891eb..c14dc23cf3 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/tag"
require "models/tagging"
@@ -7,6 +9,8 @@ require "models/comment"
require "models/author"
require "models/entrant"
require "models/developer"
+require "models/project"
+require "models/person"
require "models/computer"
require "models/reply"
require "models/company"
@@ -15,19 +19,21 @@ require "models/car"
require "models/engine"
require "models/tyre"
require "models/minivan"
-require "models/aircraft"
require "models/possession"
require "models/reader"
+require "models/category"
require "models/categorization"
require "models/edge"
+require "models/subscriber"
class RelationTest < ActiveRecord::TestCase
- fixtures :authors, :topics, :entrants, :developers, :companies, :developers_projects, :accounts, :categories, :categorizations, :posts, :comments,
- :tags, :taggings, :cars, :minivans
+ fixtures :authors, :author_addresses, :topics, :entrants, :developers, :people, :companies, :developers_projects, :accounts, :categories, :categorizations, :categories_posts, :posts, :comments, :tags, :taggings, :cars, :minivans
class TopicWithCallbacks < ActiveRecord::Base
self.table_name = :topics
+ cattr_accessor :topic_count
before_update { |topic| topic.author_name = "David" if topic.author_name.blank? }
+ after_update { |topic| topic.class.topic_count = topic.class.count }
end
def test_do_not_double_quote_string_id
@@ -55,7 +61,7 @@ class RelationTest < ActiveRecord::TestCase
def test_dynamic_finder
x = Post.where("author_id = ?", 1)
- assert x.klass.respond_to?(:find_by_id), "@klass should handle dynamic finders"
+ assert_respond_to x.klass, :find_by_id
end
def test_multivalue_where
@@ -97,7 +103,7 @@ class RelationTest < ActiveRecord::TestCase
2.times { assert_equal 5, topics.to_a.size }
end
- assert topics.loaded?
+ assert_predicate topics, :loaded?
end
def test_scoped_first
@@ -107,36 +113,36 @@ class RelationTest < ActiveRecord::TestCase
2.times { assert_equal "The First Topic", topics.first.title }
end
- assert ! topics.loaded?
+ assert_not_predicate topics, :loaded?
end
def test_loaded_first
topics = Topic.all.order("id ASC")
- topics.to_a # force load
+ topics.load # force load
assert_no_queries do
assert_equal "The First Topic", topics.first.title
end
- assert topics.loaded?
+ assert_predicate topics, :loaded?
end
def test_loaded_first_with_limit
topics = Topic.all.order("id ASC")
- topics.to_a # force load
+ topics.load # force load
assert_no_queries do
assert_equal ["The First Topic",
"The Second Topic of the day"], topics.first(2).map(&:title)
end
- assert topics.loaded?
+ assert_predicate topics, :loaded?
end
def test_first_get_more_than_available
topics = Topic.all.order("id ASC")
unloaded_first = topics.first(10)
- topics.to_a # force load
+ topics.load # force load
assert_no_queries do
loaded_first = topics.first(10)
@@ -151,14 +157,14 @@ class RelationTest < ActiveRecord::TestCase
2.times { topics.to_a }
end
- assert topics.loaded?
+ assert_predicate topics, :loaded?
original_size = topics.to_a.size
Topic.create! title: "fake"
assert_queries(1) { topics.reload }
assert_equal original_size + 1, topics.size
- assert topics.loaded?
+ assert_predicate topics, :loaded?
end
def test_finding_with_subquery
@@ -194,6 +200,18 @@ class RelationTest < ActiveRecord::TestCase
assert_equal(relation.map(&:post_count).sort, subquery.values.sort)
end
+ def test_finding_with_subquery_with_eager_loading_in_from
+ relation = Comment.includes(:post).where("posts.type": "Post")
+ assert_equal relation.to_a, Comment.select("*").from(relation).to_a
+ assert_equal relation.to_a, Comment.select("subquery.*").from(relation).to_a
+ assert_equal relation.to_a, Comment.select("a.*").from(relation, :a).to_a
+ end
+
+ def test_finding_with_subquery_with_eager_loading_in_where
+ relation = Comment.includes(:post).where("posts.type": "Post")
+ assert_equal relation.sort_by(&:id), Comment.where(id: relation).sort_by(&:id)
+ end
+
def test_finding_with_conditions
assert_equal ["David"], Author.where(name: "David").map(&:name)
assert_equal ["Mary"], Author.where(["name = ?", "Mary"]).map(&:name)
@@ -218,45 +236,68 @@ class RelationTest < ActiveRecord::TestCase
assert_equal topics(:fifth).title, topics.first.title
end
- def test_finding_with_reverted_assoc_order
+ def test_finding_with_arel_assoc_order
+ topics = Topic.order(Arel.sql("id") => :desc)
+ assert_equal 5, topics.to_a.size
+ assert_equal topics(:fifth).title, topics.first.title
+ end
+
+ def test_finding_with_reversed_assoc_order
topics = Topic.order(id: :asc).reverse_order
assert_equal 5, topics.to_a.size
assert_equal topics(:fifth).title, topics.first.title
end
+ def test_finding_with_reversed_arel_assoc_order
+ topics = Topic.order(Arel.sql("id") => :asc).reverse_order
+ assert_equal 5, topics.to_a.size
+ assert_equal topics(:fifth).title, topics.first.title
+ end
+
def test_reverse_order_with_function
- topics = Topic.order("length(title)").reverse_order
+ topics = Topic.order(Arel.sql("length(title)")).reverse_order
+ assert_equal topics(:second).title, topics.first.title
+ end
+
+ def test_reverse_arel_assoc_order_with_function
+ topics = Topic.order(Arel.sql("length(title)") => :asc).reverse_order
assert_equal topics(:second).title, topics.first.title
end
def test_reverse_order_with_function_other_predicates
- topics = Topic.order("author_name, length(title), id").reverse_order
+ topics = Topic.order(Arel.sql("author_name, length(title), id")).reverse_order
assert_equal topics(:second).title, topics.first.title
- topics = Topic.order("length(author_name), id, length(title)").reverse_order
+ topics = Topic.order(Arel.sql("length(author_name), id, length(title)")).reverse_order
assert_equal topics(:fifth).title, topics.first.title
end
def test_reverse_order_with_multiargument_function
assert_raises(ActiveRecord::IrreversibleOrderError) do
- Topic.order("concat(author_name, title)").reverse_order
+ Topic.order(Arel.sql("concat(author_name, title)")).reverse_order
end
assert_raises(ActiveRecord::IrreversibleOrderError) do
- Topic.order("concat(lower(author_name), title)").reverse_order
+ Topic.order(Arel.sql("concat(lower(author_name), title)")).reverse_order
end
assert_raises(ActiveRecord::IrreversibleOrderError) do
- Topic.order("concat(author_name, lower(title))").reverse_order
+ Topic.order(Arel.sql("concat(author_name, lower(title))")).reverse_order
end
assert_raises(ActiveRecord::IrreversibleOrderError) do
- Topic.order("concat(lower(author_name), title, length(title)").reverse_order
+ Topic.order(Arel.sql("concat(lower(author_name), title, length(title)")).reverse_order
+ end
+ end
+
+ def test_reverse_arel_assoc_order_with_multiargument_function
+ assert_nothing_raised do
+ Topic.order(Arel.sql("REPLACE(title, '', '')") => :asc).reverse_order
end
end
def test_reverse_order_with_nulls_first_or_last
assert_raises(ActiveRecord::IrreversibleOrderError) do
- Topic.order("title NULLS FIRST").reverse_order
+ Topic.order(Arel.sql("title NULLS FIRST")).reverse_order
end
assert_raises(ActiveRecord::IrreversibleOrderError) do
- Topic.order("title nulls last").reverse_order
+ Topic.order(Arel.sql("title nulls last")).reverse_order
end
end
@@ -349,29 +390,29 @@ class RelationTest < ActiveRecord::TestCase
def test_finding_with_cross_table_order_and_limit
tags = Tag.includes(:taggings).
- order("tags.name asc", "taggings.taggable_id asc", "REPLACE('abc', taggings.taggable_type, taggings.taggable_type)").
+ order("tags.name asc", "taggings.taggable_id asc", Arel.sql("REPLACE('abc', taggings.taggable_type, taggings.taggable_type)")).
limit(1).to_a
assert_equal 1, tags.length
end
def test_finding_with_complex_order_and_limit
- tags = Tag.includes(:taggings).references(:taggings).order("REPLACE('abc', taggings.taggable_type, taggings.taggable_type)").limit(1).to_a
+ tags = Tag.includes(:taggings).references(:taggings).order(Arel.sql("REPLACE('abc', taggings.taggable_type, taggings.taggable_type)")).limit(1).to_a
assert_equal 1, tags.length
end
def test_finding_with_complex_order
- tags = Tag.includes(:taggings).references(:taggings).order("REPLACE('abc', taggings.taggable_type, taggings.taggable_type)").to_a
+ tags = Tag.includes(:taggings).references(:taggings).order(Arel.sql("REPLACE('abc', taggings.taggable_type, taggings.taggable_type)")).to_a
assert_equal 3, tags.length
end
def test_finding_with_sanitized_order
- query = Tag.order(["field(id, ?)", [1, 3, 2]]).to_sql
+ query = Tag.order([Arel.sql("field(id, ?)"), [1, 3, 2]]).to_sql
assert_match(/field\(id, 1,3,2\)/, query)
- query = Tag.order(["field(id, ?)", []]).to_sql
+ query = Tag.order([Arel.sql("field(id, ?)"), []]).to_sql
assert_match(/field\(id, NULL\)/, query)
- query = Tag.order(["field(id, ?)", nil]).to_sql
+ query = Tag.order([Arel.sql("field(id, ?)"), nil]).to_sql
assert_match(/field\(id, NULL\)/, query)
end
@@ -397,123 +438,6 @@ class RelationTest < ActiveRecord::TestCase
assert_equal [2, 4, 6, 8, 10], even_ids.sort
end
- def test_none
- assert_no_queries(ignore_none: false) do
- assert_equal [], Developer.none
- assert_equal [], Developer.all.none
- end
- end
-
- def test_none_chainable
- assert_no_queries(ignore_none: false) do
- assert_equal [], Developer.none.where(name: "David")
- end
- end
-
- def test_none_chainable_to_existing_scope_extension_method
- assert_no_queries(ignore_none: false) do
- assert_equal 1, Topic.anonymous_extension.none.one
- end
- end
-
- def test_none_chained_to_methods_firing_queries_straight_to_db
- assert_no_queries(ignore_none: false) do
- assert_equal [], Developer.none.pluck(:id, :name)
- assert_equal 0, Developer.none.delete_all
- assert_equal 0, Developer.none.update_all(name: "David")
- assert_equal 0, Developer.none.delete(1)
- assert_equal false, Developer.none.exists?(1)
- end
- end
-
- def test_null_relation_content_size_methods
- assert_no_queries(ignore_none: false) do
- assert_equal 0, Developer.none.size
- assert_equal 0, Developer.none.count
- assert_equal true, Developer.none.empty?
- assert_equal true, Developer.none.none?
- assert_equal false, Developer.none.any?
- assert_equal false, Developer.none.one?
- assert_equal false, Developer.none.many?
- end
- end
-
- def test_null_relation_calculations_methods
- assert_no_queries(ignore_none: false) do
- assert_equal 0, Developer.none.count
- assert_equal 0, Developer.none.calculate(:count, nil)
- assert_nil Developer.none.calculate(:average, "salary")
- end
- end
-
- def test_null_relation_metadata_methods
- assert_equal "", Developer.none.to_sql
- assert_equal({}, Developer.none.where_values_hash)
- end
-
- def test_null_relation_where_values_hash
- assert_equal({ "salary" => 100_000 }, Developer.none.where(salary: 100_000).where_values_hash)
- end
-
- def test_null_relation_sum
- ac = Aircraft.new
- assert_equal Hash.new, ac.engines.group(:id).sum(:id)
- assert_equal 0, ac.engines.count
- ac.save
- assert_equal Hash.new, ac.engines.group(:id).sum(:id)
- assert_equal 0, ac.engines.count
- end
-
- def test_null_relation_count
- ac = Aircraft.new
- assert_equal Hash.new, ac.engines.group(:id).count
- assert_equal 0, ac.engines.count
- ac.save
- assert_equal Hash.new, ac.engines.group(:id).count
- assert_equal 0, ac.engines.count
- end
-
- def test_null_relation_size
- ac = Aircraft.new
- assert_equal Hash.new, ac.engines.group(:id).size
- assert_equal 0, ac.engines.size
- ac.save
- assert_equal Hash.new, ac.engines.group(:id).size
- assert_equal 0, ac.engines.size
- end
-
- def test_null_relation_average
- ac = Aircraft.new
- assert_equal Hash.new, ac.engines.group(:car_id).average(:id)
- assert_nil ac.engines.average(:id)
- ac.save
- assert_equal Hash.new, ac.engines.group(:car_id).average(:id)
- assert_nil ac.engines.average(:id)
- end
-
- def test_null_relation_minimum
- ac = Aircraft.new
- assert_equal Hash.new, ac.engines.group(:car_id).minimum(:id)
- assert_nil ac.engines.minimum(:id)
- ac.save
- assert_equal Hash.new, ac.engines.group(:car_id).minimum(:id)
- assert_nil ac.engines.minimum(:id)
- end
-
- def test_null_relation_maximum
- ac = Aircraft.new
- assert_equal Hash.new, ac.engines.group(:car_id).maximum(:id)
- assert_nil ac.engines.maximum(:id)
- ac.save
- assert_equal Hash.new, ac.engines.group(:car_id).maximum(:id)
- assert_nil ac.engines.maximum(:id)
- end
-
- def test_null_relation_in_where_condition
- assert_operator Comment.count, :>, 0 # precondition, make sure there are comments.
- assert_equal 0, Comment.where(post_id: Post.none).to_a.size
- end
-
def test_joins_with_nil_argument
assert_nothing_raised { DependentFirm.joins(nil).first }
end
@@ -563,15 +487,7 @@ class RelationTest < ActiveRecord::TestCase
assert_nothing_raised { Topic.reorder([]) }
end
- def test_scoped_responds_to_delegated_methods
- relation = Topic.all
-
- ["map", "uniq", "sort", "insert", "delete", "update"].each do |method|
- assert_respond_to relation, method, "Topic.all should respond to #{method.inspect}"
- end
- end
-
- def test_respond_to_delegates_to_relation
+ def test_respond_to_delegates_to_arel
relation = Topic.all
fake_arel = Struct.new(:responds) {
def respond_to?(method, access = false)
@@ -584,26 +500,22 @@ class RelationTest < ActiveRecord::TestCase
relation.respond_to?(:matching_attributes)
assert_equal [:matching_attributes, false], fake_arel.responds.first
-
- fake_arel.responds = []
- relation.respond_to?(:matching_attributes, true)
- assert_equal [:matching_attributes, true], fake_arel.responds.first
end
def test_respond_to_dynamic_finders
relation = Topic.all
["find_by_title", "find_by_title_and_author_name"].each do |method|
- assert_respond_to relation, method, "Topic.all should respond to #{method.inspect}"
+ assert_respond_to relation, method
end
end
def test_respond_to_class_methods_and_scopes
- assert Topic.all.respond_to?(:by_lifo)
+ assert_respond_to Topic.all, :by_lifo
end
def test_find_with_readonly_option
- Developer.all.each { |d| assert !d.readonly? }
+ Developer.all.each { |d| assert_not d.readonly? }
Developer.all.readonly.each { |d| assert d.readonly? }
end
@@ -676,16 +588,6 @@ class RelationTest < ActiveRecord::TestCase
end
end
- def test_default_scope_with_conditions_string
- assert_equal Developer.where(name: "David").map(&:id).sort, DeveloperCalledDavid.all.map(&:id).sort
- assert_nil DeveloperCalledDavid.create!.name
- end
-
- def test_default_scope_with_conditions_hash
- assert_equal Developer.where(name: "Jamis").map(&:id).sort, DeveloperCalledJamis.all.map(&:id).sort
- assert_equal "Jamis", DeveloperCalledJamis.create!.name
- end
-
def test_default_scoping_finder_methods
developers = DeveloperCalledDavid.order("id").map(&:id).sort
assert_equal Developer.where(name: "David").map(&:id).sort, developers
@@ -704,7 +606,7 @@ class RelationTest < ActiveRecord::TestCase
reader = Reader.create! post_id: post.id, person_id: 1
comment = Comment.create! post_id: post.id, body: "body"
- assert !comment.respond_to?(:readers)
+ assert_not_respond_to comment, :readers
post_rel = Post.preload(:readers).joins(:readers).where(title: "Uhuu")
result_comment = Comment.joins(:post).merge(post_rel).to_a.first
@@ -825,7 +727,7 @@ class RelationTest < ActiveRecord::TestCase
def test_find_in_empty_array
authors = Author.all.where(id: [])
- assert authors.to_a.blank?
+ assert_predicate authors.to_a, :blank?
end
def test_where_with_ar_object
@@ -883,8 +785,6 @@ class RelationTest < ActiveRecord::TestCase
def test_find_all_using_where_with_relation
david = authors(:david)
- # switching the lines below would succeed in current rails
- # assert_queries(2) {
assert_queries(1) {
relation = Author.where(id: Author.where(id: david.id))
assert_equal [david], relation.to_a
@@ -923,8 +823,6 @@ class RelationTest < ActiveRecord::TestCase
def test_find_all_using_where_with_relation_and_alternate_primary_key
cool_first = minivans(:cool_first)
- # switching the lines below would succeed in current rails
- # assert_queries(2) {
assert_queries(1) {
relation = Minivan.where(minivan_id: Minivan.where(name: cool_first.name))
assert_equal [cool_first], relation.to_a
@@ -960,32 +858,6 @@ class RelationTest < ActiveRecord::TestCase
}
end
- def test_exists
- davids = Author.where(name: "David")
- assert davids.exists?
- assert davids.exists?(authors(:david).id)
- assert ! davids.exists?(authors(:mary).id)
- assert ! davids.exists?("42")
- assert ! davids.exists?(42)
- assert ! davids.exists?(davids.new.id)
-
- fake = Author.where(name: "fake author")
- assert ! fake.exists?
- assert ! fake.exists?(authors(:david).id)
- end
-
- def test_exists_uses_existing_scope
- post = authors(:david).posts.first
- authors = Author.includes(:posts).where(name: "David", posts: { id: post.id })
- assert authors.exists?(authors(:david).id)
- end
-
- def test_any_with_scope_on_hash_includes
- post = authors(:david).posts.first
- categories = Categorization.includes(author: :posts).where(posts: { id: post.id })
- assert categories.exists?
- end
-
def test_last
authors = Author.all
assert_equal authors(:bob), authors.last
@@ -996,19 +868,19 @@ class RelationTest < ActiveRecord::TestCase
# Force load
assert_equal [authors(:david)], davids.to_a
- assert davids.loaded?
+ assert_predicate davids, :loaded?
assert_difference("Author.count", -1) { davids.destroy_all }
assert_equal [], davids.to_a
- assert davids.loaded?
+ assert_predicate davids, :loaded?
end
def test_delete_all
davids = Author.where(name: "David")
assert_difference("Author.count", -1) { davids.delete_all }
- assert ! davids.loaded?
+ assert_not_predicate davids, :loaded?
end
def test_delete_all_loaded
@@ -1016,20 +888,18 @@ class RelationTest < ActiveRecord::TestCase
# Force load
assert_equal [authors(:david)], davids.to_a
- assert davids.loaded?
+ assert_predicate davids, :loaded?
assert_difference("Author.count", -1) { davids.delete_all }
assert_equal [], davids.to_a
- assert davids.loaded?
+ assert_predicate davids, :loaded?
end
def test_delete_all_with_unpermitted_relation_raises_error
- assert_raises(ActiveRecord::ActiveRecordError) { Author.limit(10).delete_all }
assert_raises(ActiveRecord::ActiveRecordError) { Author.distinct.delete_all }
assert_raises(ActiveRecord::ActiveRecordError) { Author.group(:name).delete_all }
assert_raises(ActiveRecord::ActiveRecordError) { Author.having("SUM(id) < 3").delete_all }
- assert_raises(ActiveRecord::ActiveRecordError) { Author.offset(10).delete_all }
end
def test_select_with_aggregates
@@ -1037,9 +907,9 @@ class RelationTest < ActiveRecord::TestCase
assert_equal 11, posts.count(:all)
assert_equal 11, posts.size
- assert posts.any?
- assert posts.many?
- assert_not posts.empty?
+ assert_predicate posts, :any?
+ assert_predicate posts, :many?
+ assert_not_empty posts
end
def test_select_takes_a_variable_list_of_args
@@ -1068,13 +938,13 @@ class RelationTest < ActiveRecord::TestCase
assert_equal 11, posts.count(:all)
assert_equal 11, posts.count(:id)
- assert_equal 1, posts.where("comments_count > 1").count
- assert_equal 9, posts.where(comments_count: 0).count
+ assert_equal 3, posts.where("comments_count > 1").count
+ assert_equal 6, posts.where(comments_count: 0).count
end
def test_count_with_block
posts = Post.all
- assert_equal 10, posts.count { |p| p.comments_count.even? }
+ assert_equal 8, posts.count { |p| p.comments_count.even? }
end
def test_count_on_association_relation
@@ -1085,19 +955,37 @@ class RelationTest < ActiveRecord::TestCase
assert_equal author.posts.where(author_id: author.id).size, posts.count
assert_equal 0, author.posts.where(author_id: another_author.id).size
- assert author.posts.where(author_id: another_author.id).empty?
+ assert_empty author.posts.where(author_id: another_author.id)
end
def test_count_with_distinct
posts = Post.all
- assert_equal 3, posts.distinct(true).count(:comments_count)
+ assert_equal 4, posts.distinct(true).count(:comments_count)
assert_equal 11, posts.distinct(false).count(:comments_count)
- assert_equal 3, posts.distinct(true).select(:comments_count).count
+ assert_equal 4, posts.distinct(true).select(:comments_count).count
assert_equal 11, posts.distinct(false).select(:comments_count).count
end
+ def test_size_with_distinct
+ posts = Post.distinct.select(:author_id, :comments_count)
+ assert_queries(1) { assert_equal 8, posts.size }
+ assert_queries(1) { assert_equal 8, posts.load.size }
+ end
+
+ def test_size_with_eager_loading_and_custom_order
+ posts = Post.includes(:comments).order("comments.id")
+ assert_queries(1) { assert_equal 11, posts.size }
+ assert_queries(1) { assert_equal 11, posts.load.size }
+ end
+
+ def test_size_with_eager_loading_and_custom_order_and_distinct
+ posts = Post.includes(:comments).order("comments.id").distinct
+ assert_queries(1) { assert_equal 11, posts.size }
+ assert_queries(1) { assert_equal 11, posts.load.size }
+ end
+
def test_update_all_with_scope
tag = Tag.first
Post.tagged_with(tag.id).update_all title: "rofl"
@@ -1129,31 +1017,31 @@ class RelationTest < ActiveRecord::TestCase
posts = Post.all
assert_queries(1) { assert_equal 11, posts.size }
- assert ! posts.loaded?
+ assert_not_predicate posts, :loaded?
best_posts = posts.where(comments_count: 0)
- best_posts.to_a # force load
- assert_no_queries { assert_equal 9, best_posts.size }
+ best_posts.load # force load
+ assert_no_queries { assert_equal 6, best_posts.size }
end
def test_size_with_limit
posts = Post.limit(10)
assert_queries(1) { assert_equal 10, posts.size }
- assert ! posts.loaded?
+ assert_not_predicate posts, :loaded?
best_posts = posts.where(comments_count: 0)
- best_posts.to_a # force load
- assert_no_queries { assert_equal 9, best_posts.size }
+ best_posts.load # force load
+ assert_no_queries { assert_equal 6, best_posts.size }
end
def test_size_with_zero_limit
posts = Post.limit(0)
assert_no_queries { assert_equal 0, posts.size }
- assert ! posts.loaded?
+ assert_not_predicate posts, :loaded?
- posts.to_a # force load
+ posts.load # force load
assert_no_queries { assert_equal 0, posts.size }
end
@@ -1161,13 +1049,13 @@ class RelationTest < ActiveRecord::TestCase
posts = Post.limit(0)
assert_no_queries { assert_equal true, posts.empty? }
- assert ! posts.loaded?
+ assert_not_predicate posts, :loaded?
end
def test_count_complex_chained_relations
posts = Post.select("comments_count").where("id is not null").group("author_id").where("comments_count > 0")
- expected = { 1 => 2 }
+ expected = { 1 => 4, 2 => 1 }
assert_equal expected, posts.count
end
@@ -1175,14 +1063,14 @@ class RelationTest < ActiveRecord::TestCase
posts = Post.all
assert_queries(1) { assert_equal false, posts.empty? }
- assert ! posts.loaded?
+ assert_not_predicate posts, :loaded?
no_posts = posts.where(title: "")
assert_queries(1) { assert_equal true, no_posts.empty? }
- assert ! no_posts.loaded?
+ assert_not_predicate no_posts, :loaded?
best_posts = posts.where(comments_count: 0)
- best_posts.to_a # force load
+ best_posts.load # force load
assert_no_queries { assert_equal false, best_posts.empty? }
end
@@ -1190,11 +1078,11 @@ class RelationTest < ActiveRecord::TestCase
posts = Post.select("comments_count").where("id is not null").group("author_id").where("comments_count > 0")
assert_queries(1) { assert_equal false, posts.empty? }
- assert ! posts.loaded?
+ assert_not_predicate posts, :loaded?
no_posts = posts.where(title: "")
assert_queries(1) { assert_equal true, no_posts.empty? }
- assert ! no_posts.loaded?
+ assert_not_predicate no_posts, :loaded?
end
def test_any
@@ -1210,13 +1098,13 @@ class RelationTest < ActiveRecord::TestCase
assert_queries(3) do
assert posts.any? # Uses COUNT()
- assert ! posts.where(id: nil).any?
+ assert_not_predicate posts.where(id: nil), :any?
assert posts.any? { |p| p.id > 0 }
- assert ! posts.any? { |p| p.id <= 0 }
+ assert_not posts.any? { |p| p.id <= 0 }
end
- assert posts.loaded?
+ assert_predicate posts, :loaded?
end
def test_many
@@ -1225,49 +1113,49 @@ class RelationTest < ActiveRecord::TestCase
assert_queries(2) do
assert posts.many? # Uses COUNT()
assert posts.many? { |p| p.id > 0 }
- assert ! posts.many? { |p| p.id < 2 }
+ assert_not posts.many? { |p| p.id < 2 }
end
- assert posts.loaded?
+ assert_predicate posts, :loaded?
end
def test_many_with_limits
posts = Post.all
- assert posts.many?
- assert ! posts.limit(1).many?
+ assert_predicate posts, :many?
+ assert_not_predicate posts.limit(1), :many?
end
def test_none?
posts = Post.all
assert_queries(1) do
- assert ! posts.none? # Uses COUNT()
+ assert_not posts.none? # Uses COUNT()
end
- assert ! posts.loaded?
+ assert_not_predicate posts, :loaded?
assert_queries(1) do
assert posts.none? { |p| p.id < 0 }
- assert ! posts.none? { |p| p.id == 1 }
+ assert_not posts.none? { |p| p.id == 1 }
end
- assert posts.loaded?
+ assert_predicate posts, :loaded?
end
def test_one
posts = Post.all
assert_queries(1) do
- assert ! posts.one? # Uses COUNT()
+ assert_not posts.one? # Uses COUNT()
end
- assert ! posts.loaded?
+ assert_not_predicate posts, :loaded?
assert_queries(1) do
- assert ! posts.one? { |p| p.id < 3 }
+ assert_not posts.one? { |p| p.id < 3 }
assert posts.one? { |p| p.id == 1 }
end
- assert posts.loaded?
+ assert_predicate posts, :loaded?
end
def test_to_a_should_dup_target
@@ -1300,10 +1188,10 @@ class RelationTest < ActiveRecord::TestCase
sparrow = birds.create
assert_kind_of Bird, sparrow
- assert !sparrow.persisted?
+ assert_not_predicate sparrow, :persisted?
hen = birds.where(name: "hen").create
- assert hen.persisted?
+ assert_predicate hen, :persisted?
assert_equal "hen", hen.name
end
@@ -1314,34 +1202,43 @@ class RelationTest < ActiveRecord::TestCase
hen = birds.where(name: "hen").create!
assert_kind_of Bird, hen
- assert hen.persisted?
+ assert_predicate hen, :persisted?
assert_equal "hen", hen.name
end
+ def test_create_with_polymorphic_association
+ author = authors(:david)
+ post = posts(:welcome)
+ comment = Comment.where(post: post, author: author).create!(body: "hello")
+
+ assert_equal author, comment.author
+ assert_equal post, comment.post
+ end
+
def test_first_or_create
parrot = Bird.where(color: "green").first_or_create(name: "parrot")
assert_kind_of Bird, parrot
- assert parrot.persisted?
+ assert_predicate parrot, :persisted?
assert_equal "parrot", parrot.name
assert_equal "green", parrot.color
same_parrot = Bird.where(color: "green").first_or_create(name: "parakeet")
assert_kind_of Bird, same_parrot
- assert same_parrot.persisted?
+ assert_predicate same_parrot, :persisted?
assert_equal parrot, same_parrot
end
def test_first_or_create_with_no_parameters
parrot = Bird.where(color: "green").first_or_create
assert_kind_of Bird, parrot
- assert !parrot.persisted?
+ assert_not_predicate parrot, :persisted?
assert_equal "green", parrot.color
end
def test_first_or_create_with_block
parrot = Bird.where(color: "green").first_or_create { |bird| bird.name = "parrot" }
assert_kind_of Bird, parrot
- assert parrot.persisted?
+ assert_predicate parrot, :persisted?
assert_equal "green", parrot.color
assert_equal "parrot", parrot.name
@@ -1362,13 +1259,13 @@ class RelationTest < ActiveRecord::TestCase
def test_first_or_create_bang_with_valid_options
parrot = Bird.where(color: "green").first_or_create!(name: "parrot")
assert_kind_of Bird, parrot
- assert parrot.persisted?
+ assert_predicate parrot, :persisted?
assert_equal "parrot", parrot.name
assert_equal "green", parrot.color
same_parrot = Bird.where(color: "green").first_or_create!(name: "parakeet")
assert_kind_of Bird, same_parrot
- assert same_parrot.persisted?
+ assert_predicate same_parrot, :persisted?
assert_equal parrot, same_parrot
end
@@ -1383,7 +1280,7 @@ class RelationTest < ActiveRecord::TestCase
def test_first_or_create_bang_with_valid_block
parrot = Bird.where(color: "green").first_or_create! { |bird| bird.name = "parrot" }
assert_kind_of Bird, parrot
- assert parrot.persisted?
+ assert_predicate parrot, :persisted?
assert_equal "green", parrot.color
assert_equal "parrot", parrot.name
@@ -1414,9 +1311,9 @@ class RelationTest < ActiveRecord::TestCase
def test_first_or_initialize
parrot = Bird.where(color: "green").first_or_initialize(name: "parrot")
assert_kind_of Bird, parrot
- assert !parrot.persisted?
- assert parrot.valid?
- assert parrot.new_record?
+ assert_not_predicate parrot, :persisted?
+ assert_predicate parrot, :valid?
+ assert_predicate parrot, :new_record?
assert_equal "parrot", parrot.name
assert_equal "green", parrot.color
end
@@ -1424,18 +1321,18 @@ class RelationTest < ActiveRecord::TestCase
def test_first_or_initialize_with_no_parameters
parrot = Bird.where(color: "green").first_or_initialize
assert_kind_of Bird, parrot
- assert !parrot.persisted?
- assert !parrot.valid?
- assert parrot.new_record?
+ assert_not_predicate parrot, :persisted?
+ assert_not_predicate parrot, :valid?
+ assert_predicate parrot, :new_record?
assert_equal "green", parrot.color
end
def test_first_or_initialize_with_block
parrot = Bird.where(color: "green").first_or_initialize { |bird| bird.name = "parrot" }
assert_kind_of Bird, parrot
- assert !parrot.persisted?
- assert parrot.valid?
- assert parrot.new_record?
+ assert_not_predicate parrot, :persisted?
+ assert_predicate parrot, :valid?
+ assert_predicate parrot, :new_record?
assert_equal "green", parrot.color
assert_equal "parrot", parrot.name
end
@@ -1444,7 +1341,7 @@ class RelationTest < ActiveRecord::TestCase
assert_nil Bird.find_by(name: "bob")
bird = Bird.find_or_create_by(name: "bob")
- assert bird.persisted?
+ assert_predicate bird, :persisted?
assert_equal bird, Bird.find_or_create_by(name: "bob")
end
@@ -1453,7 +1350,7 @@ class RelationTest < ActiveRecord::TestCase
assert_nil Bird.find_by(name: "bob")
bird = Bird.create_with(color: "green").find_or_create_by(name: "bob")
- assert bird.persisted?
+ assert_predicate bird, :persisted?
assert_equal "green", bird.color
assert_equal bird, Bird.create_with(color: "blue").find_or_create_by(name: "bob")
@@ -1463,17 +1360,45 @@ class RelationTest < ActiveRecord::TestCase
assert_raises(ActiveRecord::RecordInvalid) { Bird.find_or_create_by!(color: "green") }
end
+ def test_create_or_find_by
+ assert_nil Subscriber.find_by(nick: "bob")
+
+ subscriber = Subscriber.create!(nick: "bob")
+
+ assert_equal subscriber, Subscriber.create_or_find_by(nick: "bob")
+ assert_not_equal subscriber, Subscriber.create_or_find_by(nick: "cat")
+ end
+
+ def test_create_or_find_by_with_non_unique_attributes
+ Subscriber.create!(nick: "bob", name: "the builder")
+
+ assert_raises(ActiveRecord::RecordNotFound) do
+ Subscriber.create_or_find_by(nick: "bob", name: "the cat")
+ end
+ end
+
+ def test_create_or_find_by_within_transaction
+ assert_nil Subscriber.find_by(nick: "bob")
+
+ subscriber = Subscriber.create!(nick: "bob")
+
+ Subscriber.transaction do
+ assert_equal subscriber, Subscriber.create_or_find_by(nick: "bob")
+ assert_not_equal subscriber, Subscriber.create_or_find_by(nick: "cat")
+ end
+ end
+
def test_find_or_initialize_by
assert_nil Bird.find_by(name: "bob")
bird = Bird.find_or_initialize_by(name: "bob")
- assert bird.new_record?
+ assert_predicate bird, :new_record?
bird.save!
assert_equal bird, Bird.find_or_initialize_by(name: "bob")
end
- def test_explicit_create_scope
+ def test_explicit_create_with
hens = Bird.where(name: "hen")
assert_equal "hen", hens.new.name
@@ -1481,6 +1406,16 @@ class RelationTest < ActiveRecord::TestCase
assert_equal "cock", hens.new.name
end
+ def test_create_with_nested_attributes
+ assert_difference("Project.count", 1) do
+ developers = Developer.where(name: "Aaron")
+ developers = developers.create_with(
+ projects_attributes: [{ name: "p1" }]
+ )
+ developers.create!
+ end
+ end
+
def test_except
relation = Post.where(author_id: 1).order("id ASC").limit(1)
assert_equal [posts(:welcome)], relation.to_a
@@ -1604,12 +1539,76 @@ class RelationTest < ActiveRecord::TestCase
assert_equal posts(:welcome), comments(:greetings).post
end
+ def test_touch_all_updates_records_timestamps
+ david = developers(:david)
+ david_previously_updated_at = david.updated_at
+ jamis = developers(:jamis)
+ jamis_previously_updated_at = jamis.updated_at
+ Developer.where(name: "David").touch_all
+
+ assert_not_equal david_previously_updated_at, david.reload.updated_at
+ assert_equal jamis_previously_updated_at, jamis.reload.updated_at
+ end
+
+ def test_touch_all_with_custom_timestamp
+ developer = developers(:david)
+ previously_created_at = developer.created_at
+ previously_updated_at = developer.updated_at
+ Developer.where(name: "David").touch_all(:created_at)
+ developer = developer.reload
+
+ assert_not_equal previously_created_at, developer.created_at
+ assert_not_equal previously_updated_at, developer.updated_at
+ end
+
+ def test_touch_all_with_given_time
+ developer = developers(:david)
+ previously_created_at = developer.created_at
+ previously_updated_at = developer.updated_at
+ new_time = Time.utc(2015, 2, 16, 4, 54, 0)
+ Developer.where(name: "David").touch_all(:created_at, time: new_time)
+ developer = developer.reload
+
+ assert_not_equal previously_created_at, developer.created_at
+ assert_not_equal previously_updated_at, developer.updated_at
+ assert_equal new_time, developer.created_at
+ assert_equal new_time, developer.updated_at
+ end
+
+ def test_touch_all_updates_locking_column
+ person = people(:david)
+
+ assert_difference -> { person.reload.lock_version }, +1 do
+ Person.where(first_name: "David").touch_all
+ end
+ end
+
def test_update_on_relation
topic1 = TopicWithCallbacks.create! title: "arel", author_name: nil
topic2 = TopicWithCallbacks.create! title: "activerecord", author_name: nil
topics = TopicWithCallbacks.where(id: [topic1.id, topic2.id])
topics.update(title: "adequaterecord")
+ assert_equal TopicWithCallbacks.count, TopicWithCallbacks.topic_count
+
+ assert_equal "adequaterecord", topic1.reload.title
+ assert_equal "adequaterecord", topic2.reload.title
+ # Testing that the before_update callbacks have run
+ assert_equal "David", topic1.reload.author_name
+ assert_equal "David", topic2.reload.author_name
+ end
+
+ def test_update_with_ids_on_relation
+ topic1 = TopicWithCallbacks.create!(title: "arel", author_name: nil)
+ topic2 = TopicWithCallbacks.create!(title: "activerecord", author_name: nil)
+ topics = TopicWithCallbacks.none
+ topics.update(
+ [topic1.id, topic2.id],
+ [{ title: "adequaterecord" }, { title: "adequaterecord" }]
+ )
+
+ assert_equal TopicWithCallbacks.count, TopicWithCallbacks.topic_count
+
assert_equal "adequaterecord", topic1.reload.title
assert_equal "adequaterecord", topic2.reload.title
# Testing that the before_update callbacks have run
@@ -1642,10 +1641,10 @@ class RelationTest < ActiveRecord::TestCase
def test_doesnt_add_having_values_if_options_are_blank
scope = Post.having("")
- assert scope.having_clause.empty?
+ assert_empty scope.having_clause
scope = Post.having([])
- assert scope.having_clause.empty?
+ assert_empty scope.having_clause
end
def test_having_with_binds_for_both_where_and_having
@@ -1671,13 +1670,13 @@ class RelationTest < ActiveRecord::TestCase
def test_references_triggers_eager_loading
scope = Post.includes(:comments)
- assert !scope.eager_loading?
- assert scope.references(:comments).eager_loading?
+ assert_not_predicate scope, :eager_loading?
+ assert_predicate scope.references(:comments), :eager_loading?
end
def test_references_doesnt_trigger_eager_loading_if_reference_not_included
scope = Post.references(:comments)
- assert !scope.eager_loading?
+ assert_not_predicate scope, :eager_loading?
end
def test_automatically_added_where_references
@@ -1708,6 +1707,13 @@ class RelationTest < ActiveRecord::TestCase
scope = Post.order("comments.body")
assert_equal ["comments"], scope.references_values
+ scope = Post.order(Arel.sql("#{Comment.quoted_table_name}.#{Comment.quoted_primary_key}"))
+ if current_adapter?(:OracleAdapter)
+ assert_equal ["COMMENTS"], scope.references_values
+ else
+ assert_equal ["comments"], scope.references_values
+ end
+
scope = Post.order("comments.body", "yaks.body")
assert_equal ["comments", "yaks"], scope.references_values
@@ -1718,7 +1724,7 @@ class RelationTest < ActiveRecord::TestCase
scope = Post.order("comments.body asc")
assert_equal ["comments"], scope.references_values
- scope = Post.order("foo(comments.body)")
+ scope = Post.order(Arel.sql("foo(comments.body)"))
assert_equal [], scope.references_values
end
@@ -1726,6 +1732,13 @@ class RelationTest < ActiveRecord::TestCase
scope = Post.reorder("comments.body")
assert_equal %w(comments), scope.references_values
+ scope = Post.reorder(Arel.sql("#{Comment.quoted_table_name}.#{Comment.quoted_primary_key}"))
+ if current_adapter?(:OracleAdapter)
+ assert_equal ["COMMENTS"], scope.references_values
+ else
+ assert_equal ["comments"], scope.references_values
+ end
+
scope = Post.reorder("comments.body", "yaks.body")
assert_equal %w(comments yaks), scope.references_values
@@ -1736,7 +1749,7 @@ class RelationTest < ActiveRecord::TestCase
scope = Post.reorder("comments.body asc")
assert_equal %w(comments), scope.references_values
- scope = Post.reorder("foo(comments.body)")
+ scope = Post.reorder(Arel.sql("foo(comments.body)"))
assert_equal [], scope.references_values
end
@@ -1761,7 +1774,7 @@ class RelationTest < ActiveRecord::TestCase
# checking if there are topics is used before you actually display them,
# thus it shouldn't invoke an extra count query.
assert_no_queries { assert topics.present? }
- assert_no_queries { assert !topics.blank? }
+ assert_no_queries { assert_not topics.blank? }
# shows count of topics and loops after loading the query should not trigger extra queries either.
assert_no_queries { topics.size }
@@ -1771,7 +1784,7 @@ class RelationTest < ActiveRecord::TestCase
# count always trigger the COUNT query.
assert_queries(1) { topics.count }
- assert topics.loaded?
+ assert_predicate topics, :loaded?
end
test "find_by with hash conditions returns the first matching record" do
@@ -1862,7 +1875,7 @@ class RelationTest < ActiveRecord::TestCase
test "relations with cached arel can't be mutated [internal API]" do
relation = Post.all
- relation.count
+ relation.arel
assert_raises(ActiveRecord::ImmutableRelation) { relation.limit!(5) }
assert_raises(ActiveRecord::ImmutableRelation) { relation.where!("1 = 2") }
@@ -1878,6 +1891,12 @@ class RelationTest < ActiveRecord::TestCase
assert_equal "#<ActiveRecord::Relation [#{Post.limit(10).map(&:inspect).join(', ')}, ...]>", relation.inspect
end
+ test "relations don't load all records in #inspect" do
+ assert_sql(/LIMIT|ROWNUM <=|FETCH FIRST/) do
+ Post.all.inspect
+ end
+ end
+
test "already-loaded relations don't perform a new query in #inspect" do
relation = Post.limit(2)
relation.to_a
@@ -1890,15 +1909,23 @@ class RelationTest < ActiveRecord::TestCase
end
test "using a custom table affects the wheres" do
- table_alias = Post.arel_table.alias("omg_posts")
+ post = posts(:welcome)
+
+ assert_equal post, custom_post_relation.where!(title: post.title).take
+ end
+
+ test "using a custom table with joins affects the joins" do
+ post = posts(:welcome)
- table_metadata = ActiveRecord::TableMetadata.new(Post, table_alias)
- predicate_builder = ActiveRecord::PredicateBuilder.new(table_metadata)
- relation = ActiveRecord::Relation.new(Post, table_alias, predicate_builder)
- relation.where!(foo: "bar")
+ assert_equal post, custom_post_relation.joins(:author).where!(title: post.title).take
+ end
+
+ test "arel_attribute respects a custom table" do
+ assert_equal [posts(:sti_comments)], custom_post_relation.ranked_by_comments.limit_by(1).to_a
+ end
- node = relation.arel.constraints.first.grep(Arel::Attributes::Attribute).first
- assert_equal table_alias, node.relation
+ test "alias_tracker respects a custom table" do
+ assert_equal posts(:welcome), custom_post_relation("categories_posts").joins(:categories).first
end
test "#load" do
@@ -1932,7 +1959,7 @@ class RelationTest < ActiveRecord::TestCase
test "delegations do not leak to other classes" do
Topic.all.by_lifo
assert Topic.all.class.method_defined?(:by_lifo)
- assert !Post.all.respond_to?(:by_lifo)
+ assert_not_respond_to Post.all, :by_lifo
end
def test_unscope_with_subquery
@@ -1947,61 +1974,89 @@ class RelationTest < ActiveRecord::TestCase
assert_equal p2.first.comments, comments
end
- def test_unscope_removes_binds
- left = Post.where(id: Arel::Nodes::BindParam.new)
- column = Post.columns_hash["id"]
- left.bind_values += [[column, 20]]
+ def test_unscope_specific_where_value
+ posts = Post.where(title: "Welcome to the weblog", body: "Such a lovely day")
- relation = left.unscope(where: :id)
- assert_equal [], relation.bind_values
+ assert_equal 1, posts.count
+ assert_equal 1, posts.unscope(where: :title).count
+ assert_equal 1, posts.unscope(where: :body).count
end
- def test_merging_removes_rhs_bind_parameters
- left = Post.where(id: 20)
- right = Post.where(id: [1, 2, 3, 4])
+ def test_locked_should_not_build_arel
+ posts = Post.locked
+ assert_predicate posts, :locked?
+ assert_nothing_raised { posts.lock!(false) }
+ end
- merged = left.merge(right)
- assert_equal [], merged.bind_values
+ def test_relation_join_method
+ assert_equal "Thank you for the welcome,Thank you again for the welcome", Post.first.comments.join(",")
end
- def test_merging_keeps_lhs_bind_parameters
- binds = [ActiveRecord::Relation::QueryAttribute.new("id", 20, Post.type_for_attribute("id"))]
+ test "#skip_query_cache!" do
+ Post.cache do
+ assert_queries(1) do
+ Post.all.load
+ Post.all.load
+ end
+
+ assert_queries(2) do
+ Post.all.skip_query_cache!.load
+ Post.all.skip_query_cache!.load
+ end
+ end
+ end
- right = Post.where(id: 20)
- left = Post.where(id: 10)
+ test "#skip_query_cache! with an eager load" do
+ Post.cache do
+ assert_queries(1) do
+ Post.eager_load(:comments).load
+ Post.eager_load(:comments).load
+ end
- merged = left.merge(right)
- assert_equal binds, merged.bound_attributes
+ assert_queries(2) do
+ Post.eager_load(:comments).skip_query_cache!.load
+ Post.eager_load(:comments).skip_query_cache!.load
+ end
+ end
end
- def test_merging_reorders_bind_params
- post = Post.first
- right = Post.where(id: post.id)
- left = Post.where(title: post.title)
+ test "#skip_query_cache! with a preload" do
+ Post.cache do
+ assert_queries(2) do
+ Post.preload(:comments).load
+ Post.preload(:comments).load
+ end
- merged = left.merge(right)
- assert_equal post, merged.first
+ assert_queries(4) do
+ Post.preload(:comments).skip_query_cache!.load
+ Post.preload(:comments).skip_query_cache!.load
+ end
+ end
end
- def test_relation_join_method
- assert_equal "Thank you for the welcome,Thank you again for the welcome", Post.first.comments.join(",")
+ test "#where with set" do
+ david = authors(:david)
+ mary = authors(:mary)
+
+ authors = Author.where(name: ["David", "Mary"].to_set)
+ assert_equal [david, mary], authors
end
- def test_connection_adapters_can_reorder_binds
- posts = Post.limit(1).offset(2)
+ test "#where with empty set" do
+ authors = Author.where(name: Set.new)
+ assert_empty authors
+ end
- stubbed_connection = Post.connection.dup
- def stubbed_connection.combine_bind_parameters(**kwargs)
- offset = kwargs[:offset]
- kwargs[:offset] = kwargs[:limit]
- kwargs[:limit] = offset
- super(**kwargs)
- end
+ private
+ def custom_post_relation(alias_name = "omg_posts")
+ table_alias = Post.arel_table.alias(alias_name)
+ table_metadata = ActiveRecord::TableMetadata.new(Post, table_alias)
+ predicate_builder = ActiveRecord::PredicateBuilder.new(table_metadata)
- posts.define_singleton_method(:connection) do
- stubbed_connection
+ ActiveRecord::Relation.create(
+ Post,
+ table: table_alias,
+ predicate_builder: predicate_builder
+ )
end
-
- assert_equal 2, posts.to_a.length
- end
end
diff --git a/activerecord/test/cases/reload_models_test.rb b/activerecord/test/cases/reload_models_test.rb
index 5dc9d6d8b7..72f4bfaf6d 100644
--- a/activerecord/test/cases/reload_models_test.rb
+++ b/activerecord/test/cases/reload_models_test.rb
@@ -1,8 +1,12 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/owner"
require "models/pet"
class ReloadModelsTest < ActiveRecord::TestCase
+ include ActiveSupport::Testing::Isolation
+
fixtures :pets, :owners
def test_has_one_with_reload
@@ -13,10 +17,10 @@ class ReloadModelsTest < ActiveRecord::TestCase
# development environment. Note that meanwhile the class Pet is not
# reloaded, simulating a class that is present in a plugin.
Object.class_eval { remove_const :Owner }
- Kernel.load(File.expand_path(File.join(File.dirname(__FILE__), "../models/owner.rb")))
+ Kernel.load(File.expand_path("../models/owner.rb", __dir__))
pet = Pet.find_by_name("parrot")
pet.owner = Owner.find_by_name("ashley")
assert_equal pet.owner, Owner.find_by_name("ashley")
end
-end
+end unless in_memory_db?
diff --git a/activerecord/test/cases/reserved_word_test.rb b/activerecord/test/cases/reserved_word_test.rb
new file mode 100644
index 0000000000..e32605fd11
--- /dev/null
+++ b/activerecord/test/cases/reserved_word_test.rb
@@ -0,0 +1,141 @@
+# frozen_string_literal: true
+
+require "cases/helper"
+
+class ReservedWordTest < ActiveRecord::TestCase
+ self.use_instantiated_fixtures = true
+ self.use_transactional_tests = false
+
+ class Group < ActiveRecord::Base
+ Group.table_name = "group"
+ belongs_to :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
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+ @connection.create_table :select, force: true
+ @connection.create_table :distinct, force: true
+ @connection.create_table :distinct_select, id: false, force: true do |t|
+ t.belongs_to :distinct
+ t.belongs_to :select
+ end
+ @connection.create_table :group, force: true do |t|
+ t.string :order
+ t.belongs_to :select
+ end
+ @connection.create_table :values, primary_key: :as, force: true do |t|
+ t.belongs_to :group
+ end
+ end
+
+ def teardown
+ @connection.drop_table :select, if_exists: true
+ @connection.drop_table :distinct, if_exists: true
+ @connection.drop_table :distinct_select, if_exists: true
+ @connection.drop_table :group, if_exists: true
+ @connection.drop_table :values, if_exists: true
+ @connection.drop_table :order, if_exists: true
+ end
+
+ def test_create_tables
+ assert_not @connection.table_exists?(:order)
+
+ @connection.create_table :order do |t|
+ t.string :group
+ end
+
+ assert @connection.table_exists?(:order)
+ end
+
+ def test_rename_tables
+ assert_nothing_raised { @connection.rename_table(:group, :order) }
+ end
+
+ def test_change_columns
+ assert_nothing_raised { @connection.change_column_default(:group, :order, "whatever") }
+ assert_nothing_raised { @connection.change_column("group", "order", :text, default: nil) }
+ assert_nothing_raised { @connection.rename_column(:group, :order, :values) }
+ end
+
+ def test_introspect
+ assert_equal ["id", "order", "select_id"], @connection.columns(:group).map(&:name).sort
+ assert_equal ["index_group_on_select_id"], @connection.indexes(:group).map(&:name).sort
+ end
+
+ def test_activerecord_model
+ x = Group.new
+ x.order = "x"
+ x.save!
+ x.order = "y"
+ x.save!
+ assert_equal x, Group.find_by_order("y")
+ assert_equal x, Group.find(x.id)
+ end
+
+ def test_delete_all_with_subselect
+ create_test_fixtures :values
+ assert_equal 1, Values.order(:as).limit(1).offset(1).delete_all
+ assert_raise(ActiveRecord::RecordNotFound) { Values.find(2) }
+ assert Values.find(1)
+ end
+
+ def test_has_one_associations
+ create_test_fixtures :group, :values
+ v = Group.find(1).values
+ assert_equal 2, v.id
+ end
+
+ def test_belongs_to_associations
+ create_test_fixtures :select, :group
+ gs = Select.find(2).groups
+ assert_equal 2, gs.length
+ assert_equal [2, 3], gs.collect(&:id).sort
+ end
+
+ def test_has_and_belongs_to_many
+ create_test_fixtures :select, :distinct, :distinct_select
+ s = Distinct.find(1).selects
+ assert_equal 2, s.length
+ assert_equal [1, 2], s.collect(&:id).sort
+ end
+
+ def test_activerecord_introspection
+ assert_predicate Group, :table_exists?
+ assert_equal ["id", "order", "select_id"], Group.columns.map(&:name).sort
+ end
+
+ def test_calculations_work_with_reserved_words
+ create_test_fixtures :group
+ assert_equal 3, Group.count
+ end
+
+ def test_associations_work_with_reserved_words
+ create_test_fixtures :select, :group
+ selects = Select.all.merge!(includes: [:groups]).to_a
+ assert_no_queries do
+ selects.each { |select| select.groups }
+ end
+ end
+
+ private
+ # custom fixture loader, uses FixtureSet#create_fixtures and appends base_path to the current file's path
+ def create_test_fixtures(*fixture_names)
+ ActiveRecord::FixtureSet.create_fixtures(FIXTURES_ROOT + "/reserved_words", fixture_names)
+ end
+end
diff --git a/activerecord/test/cases/result_test.rb b/activerecord/test/cases/result_test.rb
index 949086fda0..68fcafb682 100644
--- a/activerecord/test/cases/result_test.rb
+++ b/activerecord/test/cases/result_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
module ActiveRecord
@@ -10,6 +12,11 @@ module ActiveRecord
])
end
+ test "includes_column?" do
+ assert result.includes_column?("col_1")
+ assert_not result.includes_column?("foo")
+ end
+
test "length" do
assert_equal 3, result.length
end
@@ -45,10 +52,8 @@ module ActiveRecord
end
end
- if Enumerator.method_defined? :size
- test "each without block returns a sized enumerator" do
- assert_equal 3, result.each.size
- end
+ test "each without block returns a sized enumerator" do
+ assert_equal 3, result.each.size
end
test "cast_values returns rows after type casting" do
diff --git a/activerecord/test/cases/sanitize_test.rb b/activerecord/test/cases/sanitize_test.rb
index 72f09186e2..778cf86ac3 100644
--- a/activerecord/test/cases/sanitize_test.rb
+++ b/activerecord/test/cases/sanitize_test.rb
@@ -1,7 +1,10 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/binary"
require "models/author"
require "models/post"
+require "models/customer"
class SanitizeTest < ActiveRecord::TestCase
def setup
@@ -9,30 +12,30 @@ class SanitizeTest < ActiveRecord::TestCase
def test_sanitize_sql_array_handles_string_interpolation
quoted_bambi = ActiveRecord::Base.connection.quote_string("Bambi")
- assert_equal "name='#{quoted_bambi}'", Binary.send(:sanitize_sql_array, ["name='%s'", "Bambi"])
- assert_equal "name='#{quoted_bambi}'", Binary.send(:sanitize_sql_array, ["name='%s'", "Bambi".mb_chars])
+ assert_equal "name='#{quoted_bambi}'", Binary.sanitize_sql_array(["name='%s'", "Bambi"])
+ assert_equal "name='#{quoted_bambi}'", Binary.sanitize_sql_array(["name='%s'", "Bambi".mb_chars])
quoted_bambi_and_thumper = ActiveRecord::Base.connection.quote_string("Bambi\nand\nThumper")
- assert_equal "name='#{quoted_bambi_and_thumper}'", Binary.send(:sanitize_sql_array, ["name='%s'", "Bambi\nand\nThumper"])
- assert_equal "name='#{quoted_bambi_and_thumper}'", Binary.send(:sanitize_sql_array, ["name='%s'", "Bambi\nand\nThumper".mb_chars])
+ assert_equal "name='#{quoted_bambi_and_thumper}'", Binary.sanitize_sql_array(["name='%s'", "Bambi\nand\nThumper"])
+ assert_equal "name='#{quoted_bambi_and_thumper}'", Binary.sanitize_sql_array(["name='%s'", "Bambi\nand\nThumper".mb_chars])
end
def test_sanitize_sql_array_handles_bind_variables
quoted_bambi = ActiveRecord::Base.connection.quote("Bambi")
- assert_equal "name=#{quoted_bambi}", Binary.send(:sanitize_sql_array, ["name=?", "Bambi"])
- assert_equal "name=#{quoted_bambi}", Binary.send(:sanitize_sql_array, ["name=?", "Bambi".mb_chars])
+ assert_equal "name=#{quoted_bambi}", Binary.sanitize_sql_array(["name=?", "Bambi"])
+ assert_equal "name=#{quoted_bambi}", Binary.sanitize_sql_array(["name=?", "Bambi".mb_chars])
quoted_bambi_and_thumper = ActiveRecord::Base.connection.quote("Bambi\nand\nThumper")
- assert_equal "name=#{quoted_bambi_and_thumper}", Binary.send(:sanitize_sql_array, ["name=?", "Bambi\nand\nThumper"])
- assert_equal "name=#{quoted_bambi_and_thumper}", Binary.send(:sanitize_sql_array, ["name=?", "Bambi\nand\nThumper".mb_chars])
+ assert_equal "name=#{quoted_bambi_and_thumper}", Binary.sanitize_sql_array(["name=?", "Bambi\nand\nThumper"])
+ assert_equal "name=#{quoted_bambi_and_thumper}", Binary.sanitize_sql_array(["name=?", "Bambi\nand\nThumper".mb_chars])
end
def test_sanitize_sql_array_handles_named_bind_variables
quoted_bambi = ActiveRecord::Base.connection.quote("Bambi")
- assert_equal "name=#{quoted_bambi}", Binary.send(:sanitize_sql_array, ["name=:name", name: "Bambi"])
- assert_equal "name=#{quoted_bambi} AND id=1", Binary.send(:sanitize_sql_array, ["name=:name AND id=:id", name: "Bambi", id: 1])
+ assert_equal "name=#{quoted_bambi}", Binary.sanitize_sql_array(["name=:name", name: "Bambi"])
+ assert_equal "name=#{quoted_bambi} AND id=1", Binary.sanitize_sql_array(["name=:name AND id=:id", name: "Bambi", id: 1])
quoted_bambi_and_thumper = ActiveRecord::Base.connection.quote("Bambi\nand\nThumper")
- assert_equal "name=#{quoted_bambi_and_thumper}", Binary.send(:sanitize_sql_array, ["name=:name", name: "Bambi\nand\nThumper"])
- assert_equal "name=#{quoted_bambi_and_thumper} AND name2=#{quoted_bambi_and_thumper}", Binary.send(:sanitize_sql_array, ["name=:name AND name2=:name", name: "Bambi\nand\nThumper"])
+ assert_equal "name=#{quoted_bambi_and_thumper}", Binary.sanitize_sql_array(["name=:name", name: "Bambi\nand\nThumper"])
+ assert_equal "name=#{quoted_bambi_and_thumper} AND name2=#{quoted_bambi_and_thumper}", Binary.sanitize_sql_array(["name=:name AND name2=:name", name: "Bambi\nand\nThumper"])
end
def test_sanitize_sql_array_handles_relations
@@ -41,42 +44,50 @@ class SanitizeTest < ActiveRecord::TestCase
sub_query_pattern = /\(\bselect\b.*?\bwhere\b.*?\)/i
- select_author_sql = Post.send(:sanitize_sql_array, ["id in (?)", david_posts])
+ select_author_sql = Post.sanitize_sql_array(["id in (?)", david_posts])
assert_match(sub_query_pattern, select_author_sql, "should sanitize `Relation` as subquery for bind variables")
- select_author_sql = Post.send(:sanitize_sql_array, ["id in (:post_ids)", post_ids: david_posts])
+ select_author_sql = Post.sanitize_sql_array(["id in (:post_ids)", post_ids: david_posts])
assert_match(sub_query_pattern, select_author_sql, "should sanitize `Relation` as subquery for named bind variables")
end
def test_sanitize_sql_array_handles_empty_statement
- select_author_sql = Post.send(:sanitize_sql_array, [""])
+ select_author_sql = Post.sanitize_sql_array([""])
assert_equal("", select_author_sql)
end
def test_sanitize_sql_like
- assert_equal '100\%', Binary.send(:sanitize_sql_like, "100%")
- assert_equal 'snake\_cased\_string', Binary.send(:sanitize_sql_like, "snake_cased_string")
- assert_equal 'C:\\\\Programs\\\\MsPaint', Binary.send(:sanitize_sql_like, 'C:\\Programs\\MsPaint')
- assert_equal "normal string 42", Binary.send(:sanitize_sql_like, "normal string 42")
+ assert_equal '100\%', Binary.sanitize_sql_like("100%")
+ assert_equal 'snake\_cased\_string', Binary.sanitize_sql_like("snake_cased_string")
+ assert_equal 'C:\\\\Programs\\\\MsPaint', Binary.sanitize_sql_like('C:\\Programs\\MsPaint')
+ assert_equal "normal string 42", Binary.sanitize_sql_like("normal string 42")
end
def test_sanitize_sql_like_with_custom_escape_character
- assert_equal "100!%", Binary.send(:sanitize_sql_like, "100%", "!")
- assert_equal "snake!_cased!_string", Binary.send(:sanitize_sql_like, "snake_cased_string", "!")
- assert_equal "great!!", Binary.send(:sanitize_sql_like, "great!", "!")
- assert_equal 'C:\\Programs\\MsPaint', Binary.send(:sanitize_sql_like, 'C:\\Programs\\MsPaint', "!")
- assert_equal "normal string 42", Binary.send(:sanitize_sql_like, "normal string 42", "!")
+ assert_equal "100!%", Binary.sanitize_sql_like("100%", "!")
+ assert_equal "snake!_cased!_string", Binary.sanitize_sql_like("snake_cased_string", "!")
+ assert_equal "great!!", Binary.sanitize_sql_like("great!", "!")
+ assert_equal 'C:\\Programs\\MsPaint', Binary.sanitize_sql_like('C:\\Programs\\MsPaint', "!")
+ assert_equal "normal string 42", Binary.sanitize_sql_like("normal string 42", "!")
end
def test_sanitize_sql_like_example_use_case
searchable_post = Class.new(Post) do
- def self.search(term)
+ def self.search_as_method(term)
where("title LIKE ?", sanitize_sql_like(term, "!"))
end
+
+ scope :search_as_scope, -> (term) {
+ where("title LIKE ?", sanitize_sql_like(term, "!"))
+ }
end
assert_sql(/LIKE '20!% !_reduction!_!!'/) do
- searchable_post.search("20% _reduction_!").to_a
+ searchable_post.search_as_method("20% _reduction_!").to_a
+ end
+
+ assert_sql(/LIKE '20!% !_reduction!_!!'/) do
+ searchable_post.search_as_scope("20% _reduction_!").to_a
end
end
@@ -151,24 +162,18 @@ class SanitizeTest < ActiveRecord::TestCase
assert_equal "name=#{quoted_bambi_and_thumper}", bind("name=?", "Bambi\nand\nThumper".mb_chars)
end
- def test_bind_record
- o = Class.new {
- def quoted_id
- 1
- end
- }.new
- assert_deprecated { assert_equal "1", bind("?", o) }
-
- os = [o] * 3
- assert_deprecated { assert_equal "1,1,1", bind("?", os) }
- end
-
def test_named_bind_with_postgresql_type_casts
l = Proc.new { bind(":a::integer '2009-01-01'::date", a: "10") }
assert_nothing_raised(&l)
assert_equal "#{ActiveRecord::Base.connection.quote('10')}::integer '2009-01-01'::date", l.call
end
+ def test_deprecated_expand_hash_conditions_for_aggregates
+ assert_deprecated do
+ assert_equal({ "balance" => 50 }, Customer.send(:expand_hash_conditions_for_aggregates, balance: Money.new(50)))
+ end
+ end
+
private
def bind(statement, *vars)
if vars.first.is_a?(Hash)
diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb
index 9584318e86..75b68b521c 100644
--- a/activerecord/test/cases/schema_dumper_test.rb
+++ b/activerecord/test/cases/schema_dumper_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "support/schema_dumping_helper"
@@ -17,6 +19,12 @@ class SchemaDumperTest < ActiveRecord::TestCase
dump_all_table_schema []
end
+ def test_dump_schema_information_with_empty_versions
+ ActiveRecord::SchemaMigration.delete_all
+ schema_info = ActiveRecord::Base.connection.dump_schema_information
+ assert_no_match(/INSERT INTO/, schema_info)
+ end
+
def test_dump_schema_information_outputs_lexically_ordered_versions
versions = %w{ 20100101010101 20100201010101 20100301010101 }
versions.reverse_each do |v|
@@ -29,24 +37,6 @@ class SchemaDumperTest < ActiveRecord::TestCase
ActiveRecord::SchemaMigration.delete_all
end
- if current_adapter?(:SQLite3Adapter)
- %w{3.7.8 3.7.11 3.7.12}.each do |version_string|
- test "dumps schema version for sqlite version #{version_string}" do
- version = ActiveRecord::ConnectionAdapters::SQLite3Adapter::Version.new(version_string)
- ActiveRecord::Base.connection.stubs(:sqlite_version).returns(version)
-
- versions = %w{ 20100101010101 20100201010101 20100301010101 }
- versions.reverse_each do |v|
- ActiveRecord::SchemaMigration.create!(version: v)
- end
-
- schema_info = ActiveRecord::Base.connection.dump_schema_information
- assert_match(/20100201010101.*20100301010101/m, schema_info)
- ActiveRecord::SchemaMigration.delete_all
- end
- end
- end
-
def test_schema_dump
output = standard_dump
assert_match %r{create_table "accounts"}, output
@@ -58,7 +48,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
def test_schema_dump_uses_force_cascade_on_create_table
output = dump_table_schema "authors"
- assert_match %r{create_table "authors", force: :cascade}, output
+ assert_match %r{create_table "authors",.* force: :cascade}, output
end
def test_schema_dump_excludes_sqlite_sequence
@@ -116,32 +106,22 @@ class SchemaDumperTest < ActiveRecord::TestCase
def test_schema_dump_includes_limit_constraint_for_integer_columns
output = dump_all_table_schema([/^(?!integer_limits)/])
- assert_match %r{c_int_without_limit}, output
+ assert_match %r{"c_int_without_limit"(?!.*limit)}, output
if current_adapter?(:PostgreSQLAdapter)
- assert_no_match %r{c_int_without_limit.*limit:}, output
-
assert_match %r{c_int_1.*limit: 2}, output
assert_match %r{c_int_2.*limit: 2}, output
# int 3 is 4 bytes in postgresql
- assert_match %r{c_int_3.*}, output
- assert_no_match %r{c_int_3.*limit:}, output
-
- assert_match %r{c_int_4.*}, output
- assert_no_match %r{c_int_4.*limit:}, output
+ assert_match %r{"c_int_3"(?!.*limit)}, output
+ assert_match %r{"c_int_4"(?!.*limit)}, output
elsif current_adapter?(:Mysql2Adapter)
- assert_match %r{c_int_without_limit"$}, output
-
assert_match %r{c_int_1.*limit: 1}, output
assert_match %r{c_int_2.*limit: 2}, output
assert_match %r{c_int_3.*limit: 3}, output
- assert_match %r{c_int_4.*}, output
- assert_no_match %r{c_int_4.*:limit}, output
+ assert_match %r{"c_int_4"(?!.*limit)}, output
elsif current_adapter?(:SQLite3Adapter)
- assert_no_match %r{c_int_without_limit.*limit:}, output
-
assert_match %r{c_int_1.*limit: 1}, output
assert_match %r{c_int_2.*limit: 2}, output
assert_match %r{c_int_3.*limit: 3}, output
@@ -179,10 +159,14 @@ class SchemaDumperTest < ActiveRecord::TestCase
def test_schema_dumps_index_columns_in_right_order
index_definition = dump_table_schema("companies").split(/\n/).grep(/t\.index.*company_index/).first.strip
- if current_adapter?(:PostgreSQLAdapter)
+ if current_adapter?(:Mysql2Adapter)
+ if ActiveRecord::Base.connection.supports_index_sort_order?
+ assert_equal 't.index ["firm_id", "type", "rating"], name: "company_index", length: { type: 10 }, order: { rating: :desc }', index_definition
+ else
+ assert_equal 't.index ["firm_id", "type", "rating"], name: "company_index", length: { type: 10 }', index_definition
+ end
+ elsif ActiveRecord::Base.connection.supports_index_sort_order?
assert_equal 't.index ["firm_id", "type", "rating"], name: "company_index", order: { rating: :desc }', index_definition
- elsif current_adapter?(:Mysql2Adapter)
- assert_equal 't.index ["firm_id", "type", "rating"], name: "company_index", length: { type: 10 }', index_definition
else
assert_equal 't.index ["firm_id", "type", "rating"], name: "company_index"', index_definition
end
@@ -190,13 +174,31 @@ class SchemaDumperTest < ActiveRecord::TestCase
def test_schema_dumps_partial_indices
index_definition = dump_table_schema("companies").split(/\n/).grep(/t\.index.*company_partial_index/).first.strip
- if current_adapter?(:PostgreSQLAdapter, :SQLite3Adapter) && ActiveRecord::Base.connection.supports_partial_index?
+ if ActiveRecord::Base.connection.supports_partial_index?
assert_equal 't.index ["firm_id", "type"], name: "company_partial_index", where: "(rating > 10)"', index_definition
else
assert_equal 't.index ["firm_id", "type"], name: "company_partial_index"', index_definition
end
end
+ def test_schema_dumps_index_sort_order
+ index_definition = dump_table_schema("companies").split(/\n/).grep(/t\.index.*_name_and_rating/).first.strip
+ if ActiveRecord::Base.connection.supports_index_sort_order?
+ assert_equal 't.index ["name", "rating"], name: "index_companies_on_name_and_rating", order: :desc', index_definition
+ else
+ assert_equal 't.index ["name", "rating"], name: "index_companies_on_name_and_rating"', index_definition
+ end
+ end
+
+ def test_schema_dumps_index_length
+ index_definition = dump_table_schema("companies").split(/\n/).grep(/t\.index.*_name_and_description/).first.strip
+ if current_adapter?(:Mysql2Adapter)
+ assert_equal 't.index ["name", "description"], name: "index_companies_on_name_and_description", length: 10', index_definition
+ else
+ assert_equal 't.index ["name", "description"], name: "index_companies_on_name_and_description"', index_definition
+ end
+ end
+
def test_schema_dump_should_honor_nonstandard_primary_keys
output = standard_dump
match = output.match(%r{create_table "movies"(.*)do})
@@ -205,20 +207,25 @@ class SchemaDumperTest < ActiveRecord::TestCase
end
def test_schema_dump_should_use_false_as_default
- output = standard_dump
+ output = dump_table_schema "booleans"
assert_match %r{t\.boolean\s+"has_fun",.+default: false}, output
end
def test_schema_dump_does_not_include_limit_for_text_field
- output = standard_dump
+ output = dump_table_schema "admin_users"
assert_match %r{t\.text\s+"params"$}, output
end
def test_schema_dump_does_not_include_limit_for_binary_field
- output = standard_dump
+ output = dump_table_schema "binaries"
assert_match %r{t\.binary\s+"data"$}, output
end
+ def test_schema_dump_does_not_include_limit_for_float_field
+ output = dump_table_schema "numeric_data"
+ assert_match %r{t\.float\s+"temperature"$}, output
+ end
+
if current_adapter?(:Mysql2Adapter)
def test_schema_dump_includes_length_for_mysql_binary_fields
output = standard_dump
@@ -273,7 +280,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
def test_schema_dump_expression_indices
index_definition = dump_table_schema("companies").split(/\n/).grep(/t\.index.*company_expression_index/).first.strip
- assert_equal 't.index "lower((name)::text)", name: "company_expression_index"', index_definition
+ assert_match %r{CASE.+lower\(\(name\)::text\)}i, index_definition
end
def test_schema_dump_interval_type
@@ -287,21 +294,37 @@ class SchemaDumperTest < ActiveRecord::TestCase
assert_match %r{t\.oid\s+"obj_id"$}, output
end
- if ActiveRecord::Base.connection.supports_extensions?
- def test_schema_dump_includes_extensions
- connection = ActiveRecord::Base.connection
+ def test_schema_dump_includes_extensions
+ connection = ActiveRecord::Base.connection
- connection.stubs(:extensions).returns(["hstore"])
+ connection.stub(:extensions, ["hstore"]) do
output = perform_schema_dump
assert_match "# These are extensions that must be enabled", output
assert_match %r{enable_extension "hstore"}, output
+ end
- connection.stubs(:extensions).returns([])
+ connection.stub(:extensions, []) do
output = perform_schema_dump
assert_no_match "# These are extensions that must be enabled", output
assert_no_match %r{enable_extension}, output
end
end
+
+ def test_schema_dump_includes_extensions_in_alphabetic_order
+ connection = ActiveRecord::Base.connection
+
+ connection.stub(:extensions, ["hstore", "uuid-ossp", "xml2"]) do
+ output = perform_schema_dump
+ enabled_extensions = output.scan(%r{enable_extension "(.+)"}).flatten
+ assert_equal ["hstore", "uuid-ossp", "xml2"], enabled_extensions
+ end
+
+ connection.stub(:extensions, ["uuid-ossp", "xml2", "hstore"]) do
+ output = perform_schema_dump
+ enabled_extensions = output.scan(%r{enable_extension "(.+)"}).flatten
+ assert_equal ["hstore", "uuid-ossp", "xml2"], enabled_extensions
+ end
+ end
end
def test_schema_dump_keeps_large_precision_integer_columns_as_decimal
@@ -326,7 +349,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
def test_schema_dump_keeps_id_false_when_id_is_false_and_unique_not_null_column_added
output = standard_dump
- assert_match %r{create_table "subscribers", id: false}, output
+ assert_match %r{create_table "string_key_objects", id: false}, output
end
if ActiveRecord::Base.connection.supports_foreign_keys?
@@ -384,6 +407,31 @@ class SchemaDumperTest < ActiveRecord::TestCase
$stdout = original
end
+ def test_schema_dump_with_table_name_prefix_and_suffix_regexp_escape
+ original, $stdout = $stdout, StringIO.new
+ ActiveRecord::Base.table_name_prefix = "foo$"
+ ActiveRecord::Base.table_name_suffix = "$bar"
+
+ migration = CreateDogMigration.new
+ migration.migrate(:up)
+
+ output = perform_schema_dump
+ assert_no_match %r{create_table "foo\$.+\$bar"}, output
+ assert_no_match %r{add_index "foo\$.+\$bar"}, output
+ assert_no_match %r{create_table "schema_migrations"}, output
+ assert_no_match %r{create_table "ar_internal_metadata"}, output
+
+ if ActiveRecord::Base.connection.supports_foreign_keys?
+ assert_no_match %r{add_foreign_key "foo\$.+\$bar"}, output
+ assert_no_match %r{add_foreign_key "[^"]+", "foo\$.+\$bar"}, output
+ end
+ ensure
+ migration.migrate(:down)
+
+ ActiveRecord::Base.table_name_suffix = ActiveRecord::Base.table_name_prefix = ""
+ $stdout = original
+ end
+
def test_schema_dump_with_table_name_prefix_and_ignoring_tables
original, $stdout = $stdout, StringIO.new
@@ -407,7 +455,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
output = ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream).string
assert_match %r{create_table "omg_cats"}, output
- refute_match %r{create_table "cats"}, output
+ assert_no_match %r{create_table "cats"}, output
ensure
migration.migrate(:down)
ActiveRecord::Base.table_name_prefix = original_table_name_prefix
@@ -422,11 +470,12 @@ class SchemaDumperDefaultsTest < ActiveRecord::TestCase
setup do
@connection = ActiveRecord::Base.connection
- @connection.create_table :defaults, force: true do |t|
+ @connection.create_table :dump_defaults, force: true do |t|
t.string :string_with_default, default: "Hello!"
t.date :date_with_default, default: "2014-06-05"
t.datetime :datetime_with_default, default: "2014-06-05 07:17:04"
t.time :time_with_default, default: "07:17:04"
+ t.decimal :decimal_with_default, default: "1234567890.0123456789", precision: 20, scale: 10
end
if current_adapter?(:PostgreSQLAdapter)
@@ -438,17 +487,17 @@ class SchemaDumperDefaultsTest < ActiveRecord::TestCase
end
teardown do
- return unless @connection
- @connection.drop_table "defaults", if_exists: true
+ @connection.drop_table "dump_defaults", if_exists: true
end
def test_schema_dump_defaults_with_universally_supported_types
- output = dump_table_schema("defaults")
+ output = dump_table_schema("dump_defaults")
assert_match %r{t\.string\s+"string_with_default",.*?default: "Hello!"}, output
- assert_match %r{t\.date\s+"date_with_default",\s+default: '2014-06-05'}, output
- assert_match %r{t\.datetime\s+"datetime_with_default",\s+default: '2014-06-05 07:17:04'}, output
- assert_match %r{t\.time\s+"time_with_default",\s+default: '2000-01-01 07:17:04'}, output
+ assert_match %r{t\.date\s+"date_with_default",\s+default: "2014-06-05"}, output
+ assert_match %r{t\.datetime\s+"datetime_with_default",\s+default: "2014-06-05 07:17:04"}, output
+ assert_match %r{t\.time\s+"time_with_default",\s+default: "2000-01-01 07:17:04"}, output
+ assert_match %r{t\.decimal\s+"decimal_with_default",\s+precision: 20,\s+scale: 10,\s+default: "1234567890.0123456789"}, output
end
def test_schema_dump_with_float_column_infinity_default
diff --git a/activerecord/test/cases/schema_loading_test.rb b/activerecord/test/cases/schema_loading_test.rb
index 362370ac61..f539156466 100644
--- a/activerecord/test/cases/schema_loading_test.rb
+++ b/activerecord/test/cases/schema_loading_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
module SchemaLoadCounter
diff --git a/activerecord/test/cases/scoping/default_scoping_test.rb b/activerecord/test/cases/scoping/default_scoping_test.rb
index 14fb2fbbfa..6281712df6 100644
--- a/activerecord/test/cases/scoping/default_scoping_test.rb
+++ b/activerecord/test/cases/scoping/default_scoping_test.rb
@@ -1,7 +1,10 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/post"
require "models/comment"
require "models/developer"
+require "models/project"
require "models/computer"
require "models/vehicle"
require "models/cat"
@@ -10,8 +13,6 @@ require "concurrent/atomic/cyclic_barrier"
class DefaultScopingTest < ActiveRecord::TestCase
fixtures :developers, :posts, :comments
- self.use_transactional_tests = false
-
def test_default_scope
expected = Developer.all.merge!(order: "salary DESC").to_a.collect(&:salary)
received = DeveloperOrderedBySalary.all.collect(&:salary)
@@ -61,17 +62,6 @@ class DefaultScopingTest < ActiveRecord::TestCase
assert_equal "Jamis", DeveloperCalledJamis.create!.name
end
- unless in_memory_db?
- def test_default_scoping_with_threads
- 2.times do
- Thread.new {
- assert_includes DeveloperOrderedBySalary.all.to_sql, "salary DESC"
- DeveloperOrderedBySalary.connection.close
- }.join
- end
- end
- end
-
def test_default_scope_with_inheritance
wheres = InheritedPoorDeveloperCalledJamis.all.where_values_hash
assert_equal "Jamis", wheres["name"]
@@ -131,49 +121,49 @@ class DefaultScopingTest < ActiveRecord::TestCase
def test_unscope_with_where_attributes
expected = Developer.order("salary DESC").collect(&:name)
received = DeveloperOrderedBySalary.where(name: "David").unscope(where: :name).collect(&:name)
- assert_equal expected, received
+ assert_equal expected.sort, received.sort
expected_2 = Developer.order("salary DESC").collect(&:name)
received_2 = DeveloperOrderedBySalary.select("id").where("name" => "Jamis").unscope({ where: :name }, :select).collect(&:name)
- assert_equal expected_2, received_2
+ assert_equal expected_2.sort, received_2.sort
expected_3 = Developer.order("salary DESC").collect(&:name)
received_3 = DeveloperOrderedBySalary.select("id").where("name" => "Jamis").unscope(:select, :where).collect(&:name)
- assert_equal expected_3, received_3
+ assert_equal expected_3.sort, received_3.sort
expected_4 = Developer.order("salary DESC").collect(&:name)
received_4 = DeveloperOrderedBySalary.where.not("name" => "Jamis").unscope(where: :name).collect(&:name)
- assert_equal expected_4, received_4
+ assert_equal expected_4.sort, received_4.sort
expected_5 = Developer.order("salary DESC").collect(&:name)
received_5 = DeveloperOrderedBySalary.where.not("name" => ["Jamis", "David"]).unscope(where: :name).collect(&:name)
- assert_equal expected_5, received_5
+ assert_equal expected_5.sort, received_5.sort
expected_6 = Developer.order("salary DESC").collect(&:name)
received_6 = DeveloperOrderedBySalary.where(Developer.arel_table["name"].eq("David")).unscope(where: :name).collect(&:name)
- assert_equal expected_6, received_6
+ assert_equal expected_6.sort, received_6.sort
expected_7 = Developer.order("salary DESC").collect(&:name)
received_7 = DeveloperOrderedBySalary.where(Developer.arel_table[:name].eq("David")).unscope(where: :name).collect(&:name)
- assert_equal expected_7, received_7
+ assert_equal expected_7.sort, received_7.sort
end
def test_unscope_comparison_where_clauses
# unscoped for WHERE (`developers`.`id` <= 2)
expected = Developer.order("salary DESC").collect(&:name)
received = DeveloperOrderedBySalary.where(id: -Float::INFINITY..2).unscope(where: :id).collect { |dev| dev.name }
- assert_equal expected, received
+ assert_equal expected.sort, received.sort
# unscoped for WHERE (`developers`.`id` < 2)
expected = Developer.order("salary DESC").collect(&:name)
received = DeveloperOrderedBySalary.where(id: -Float::INFINITY...2).unscope(where: :id).collect { |dev| dev.name }
- assert_equal expected, received
+ assert_equal expected.sort, received.sort
end
def test_unscope_multiple_where_clauses
expected = Developer.order("salary DESC").collect(&:name)
received = DeveloperOrderedBySalary.where(name: "Jamis").where(id: 1).unscope(where: [:name, :id]).collect(&:name)
- assert_equal expected, received
+ assert_equal expected.sort, received.sort
end
def test_unscope_string_where_clauses_involved
@@ -183,28 +173,28 @@ class DefaultScopingTest < ActiveRecord::TestCase
dev_ordered_relation = DeveloperOrderedBySalary.where(name: "Jamis").where("created_at > ?", 1.year.ago)
received = dev_ordered_relation.unscope(where: [:name]).collect(&:name)
- assert_equal expected, received
+ assert_equal expected.sort, received.sort
end
def test_unscope_with_grouping_attributes
expected = Developer.order("salary DESC").collect(&:name)
received = DeveloperOrderedBySalary.group(:name).unscope(:group).collect(&:name)
- assert_equal expected, received
+ assert_equal expected.sort, received.sort
expected_2 = Developer.order("salary DESC").collect(&:name)
received_2 = DeveloperOrderedBySalary.group("name").unscope(:group).collect(&:name)
- assert_equal expected_2, received_2
+ assert_equal expected_2.sort, received_2.sort
end
def test_unscope_with_limit_in_query
expected = Developer.order("salary DESC").collect(&:name)
received = DeveloperOrderedBySalary.limit(1).unscope(:limit).collect(&:name)
- assert_equal expected, received
+ assert_equal expected.sort, received.sort
end
def test_order_to_unscope_reordering
scope = DeveloperOrderedBySalary.order("salary DESC, name ASC").reverse_order.unscope(:order)
- assert !/order/i.match?(scope.to_sql)
+ assert_no_match(/order/i, scope.to_sql)
end
def test_unscope_reverse_order
@@ -235,6 +225,18 @@ class DefaultScopingTest < ActiveRecord::TestCase
assert_equal expected, received
end
+ def test_unscope_left_outer_joins
+ expected = Developer.all.collect(&:name)
+ received = Developer.left_outer_joins(:projects).select(:id).unscope(:left_outer_joins, :select).collect(&:name)
+ assert_equal expected, received
+ end
+
+ def test_unscope_left_joins
+ expected = Developer.all.collect(&:name)
+ received = Developer.left_joins(:projects).select(:id).unscope(:left_joins, :select).collect(&:name)
+ assert_equal expected, received
+ end
+
def test_unscope_includes
expected = Developer.all.collect(&:name)
received = Developer.includes(:projects).select(:id).unscope(:includes, :select).collect(&:name)
@@ -301,8 +303,8 @@ class DefaultScopingTest < ActiveRecord::TestCase
def test_unscope_merging
merged = Developer.where(name: "Jamis").merge(Developer.unscope(:where))
- assert merged.where_clause.empty?
- assert !merged.where(name: "Jon").where_clause.empty?
+ assert_empty merged.where_clause
+ assert_not_empty merged.where(name: "Jon").where_clause
end
def test_order_in_default_scope_should_not_prevail
@@ -345,7 +347,7 @@ class DefaultScopingTest < ActiveRecord::TestCase
def test_create_with_merge
aaron = PoorDeveloperCalledJamis.create_with(name: "foo", salary: 20).merge(
- PoorDeveloperCalledJamis.create_with(name: "Aaron")).new
+ PoorDeveloperCalledJamis.create_with(name: "Aaron")).new
assert_equal 20, aaron.salary
assert_equal "Aaron", aaron.name
@@ -355,11 +357,31 @@ class DefaultScopingTest < ActiveRecord::TestCase
assert_equal "Aaron", aaron.name
end
+ def test_create_with_using_both_string_and_symbol
+ jamis = PoorDeveloperCalledJamis.create_with(name: "foo").create_with("name" => "Aaron").new
+ assert_equal "Aaron", jamis.name
+ end
+
def test_create_with_reset
jamis = PoorDeveloperCalledJamis.create_with(name: "Aaron").create_with(nil).new
assert_equal "Jamis", jamis.name
end
+ def test_create_with_takes_precedence_over_where
+ developer = Developer.where(name: nil).create_with(name: "Aaron").new
+ assert_equal "Aaron", developer.name
+ end
+
+ def test_create_with_nested_attributes
+ assert_difference("Project.count", 1) do
+ Developer.create_with(
+ projects_attributes: [{ name: "p1" }]
+ ).scoping do
+ Developer.create!(name: "Aaron")
+ end
+ end
+ end
+
# FIXME: I don't know if this is *desired* behavior, but it is *today's*
# behavior.
def test_create_with_empty_hash_will_not_reset
@@ -385,11 +407,39 @@ class DefaultScopingTest < ActiveRecord::TestCase
Comment.joins(:post).count
end
+ def test_joins_not_affected_by_scope_other_than_default_or_unscoped
+ without_scope_on_post = Comment.joins(:post).to_a
+ with_scope_on_post = nil
+ Post.where(id: [1, 5, 6]).scoping do
+ with_scope_on_post = Comment.joins(:post).to_a
+ end
+
+ assert_equal with_scope_on_post, without_scope_on_post
+ end
+
def test_unscoped_with_joins_should_not_have_default_scope
assert_equal SpecialPostWithDefaultScope.unscoped { Comment.joins(:special_post_with_default_scope).to_a },
Comment.joins(:post).to_a
end
+ def test_sti_association_with_unscoped_not_affected_by_default_scope
+ post = posts(:thinking)
+ comments = [comments(:does_it_hurt)]
+
+ post.special_comments.update_all(deleted_at: Time.now)
+
+ assert_raises(ActiveRecord::RecordNotFound) { Post.joins(:special_comments).find(post.id) }
+ assert_equal [], post.special_comments
+
+ SpecialComment.unscoped do
+ assert_equal post, Post.joins(:special_comments).find(post.id)
+ assert_equal comments, Post.joins(:special_comments).find(post.id).special_comments
+ assert_equal comments, Post.eager_load(:special_comments).find(post.id).special_comments
+ assert_equal comments, Post.includes(:special_comments).find(post.id).special_comments
+ assert_equal comments, Post.preload(:special_comments).find(post.id).special_comments
+ end
+ end
+
def test_default_scope_select_ignored_by_aggregations
assert_equal DeveloperWithSelect.all.to_a.count, DeveloperWithSelect.count
end
@@ -435,29 +485,6 @@ class DefaultScopingTest < ActiveRecord::TestCase
assert_equal comment, CommentWithDefaultScopeReferencesAssociation.find_by(id: comment.id)
end
- unless in_memory_db?
- def test_default_scope_is_threadsafe
- threads = []
- assert_not_equal 1, ThreadsafeDeveloper.unscoped.count
-
- barrier_1 = Concurrent::CyclicBarrier.new(2)
- barrier_2 = Concurrent::CyclicBarrier.new(2)
-
- threads << Thread.new do
- Thread.current[:default_scope_delay] = -> { barrier_1.wait; barrier_2.wait }
- assert_equal 1, ThreadsafeDeveloper.all.to_a.count
- ThreadsafeDeveloper.connection.close
- end
- threads << Thread.new do
- Thread.current[:default_scope_delay] = -> { barrier_2.wait }
- barrier_1.wait
- assert_equal 1, ThreadsafeDeveloper.all.to_a.count
- ThreadsafeDeveloper.connection.close
- end
- threads.each(&:join)
- end
- end
-
test "additional conditions are ANDed with the default scope" do
scope = DeveloperCalledJamis.where(name: "David")
assert_equal 2, scope.where_clause.ast.children.length
@@ -473,7 +500,7 @@ class DefaultScopingTest < ActiveRecord::TestCase
test "a scope can remove the condition from the default scope" do
scope = DeveloperCalledJamis.david2
assert_equal 1, scope.where_clause.ast.children.length
- assert_equal Developer.where(name: "David"), scope
+ assert_equal Developer.where(name: "David").map(&:id), scope.map(&:id)
end
def test_with_abstract_class_where_clause_should_not_be_duplicated
@@ -498,6 +525,8 @@ class DefaultScopingTest < ActiveRecord::TestCase
def test_with_abstract_class_scope_should_be_executed_in_correct_context
vegetarian_pattern, gender_pattern = if current_adapter?(:Mysql2Adapter)
[/`lions`.`is_vegetarian`/, /`lions`.`gender`/]
+ elsif current_adapter?(:OracleAdapter)
+ [/"LIONS"."IS_VEGETARIAN"/, /"LIONS"."GENDER"/]
else
[/"lions"."is_vegetarian"/, /"lions"."gender"/]
end
@@ -506,3 +535,41 @@ class DefaultScopingTest < ActiveRecord::TestCase
assert_match gender_pattern, Lion.female.to_sql
end
end
+
+class DefaultScopingWithThreadTest < ActiveRecord::TestCase
+ self.use_transactional_tests = false
+
+ def test_default_scoping_with_threads
+ 2.times do
+ Thread.new {
+ assert_includes DeveloperOrderedBySalary.all.to_sql, "salary DESC"
+ DeveloperOrderedBySalary.connection.close
+ }.join
+ end
+ end
+
+ def test_default_scope_is_threadsafe
+ 2.times { ThreadsafeDeveloper.unscoped.create! }
+
+ threads = []
+ assert_not_equal 1, ThreadsafeDeveloper.unscoped.count
+
+ barrier_1 = Concurrent::CyclicBarrier.new(2)
+ barrier_2 = Concurrent::CyclicBarrier.new(2)
+
+ threads << Thread.new do
+ Thread.current[:default_scope_delay] = -> { barrier_1.wait; barrier_2.wait }
+ assert_equal 1, ThreadsafeDeveloper.all.to_a.count
+ ThreadsafeDeveloper.connection.close
+ end
+ threads << Thread.new do
+ Thread.current[:default_scope_delay] = -> { barrier_2.wait }
+ barrier_1.wait
+ assert_equal 1, ThreadsafeDeveloper.all.to_a.count
+ ThreadsafeDeveloper.connection.close
+ end
+ threads.each(&:join)
+ ensure
+ ThreadsafeDeveloper.unscoped.destroy_all
+ end
+end unless in_memory_db?
diff --git a/activerecord/test/cases/scoping/named_scoping_test.rb b/activerecord/test/cases/scoping/named_scoping_test.rb
index d261fd5321..4214f347fb 100644
--- a/activerecord/test/cases/scoping/named_scoping_test.rb
+++ b/activerecord/test/cases/scoping/named_scoping_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/post"
require "models/topic"
@@ -11,7 +13,7 @@ class NamedScopingTest < ActiveRecord::TestCase
fixtures :posts, :authors, :topics, :comments, :author_addresses
def test_implements_enumerable
- assert !Topic.all.empty?
+ assert_not_empty Topic.all
assert_equal Topic.all.to_a, Topic.base
assert_equal Topic.all.to_a, Topic.base.to_a
@@ -23,8 +25,8 @@ class NamedScopingTest < ActiveRecord::TestCase
all_posts = Topic.base
assert_queries(1) do
- all_posts.collect
- all_posts.collect
+ all_posts.collect { true }
+ all_posts.collect { true }
end
end
@@ -38,7 +40,7 @@ class NamedScopingTest < ActiveRecord::TestCase
end
def test_delegates_finds_and_calculations_to_the_base_class
- assert !Topic.all.empty?
+ assert_not_empty Topic.all
assert_equal Topic.all.to_a, Topic.base.to_a
assert_equal Topic.first, Topic.base.first
@@ -63,13 +65,13 @@ class NamedScopingTest < ActiveRecord::TestCase
end
def test_scope_should_respond_to_own_methods_and_methods_of_the_proxy
- assert Topic.approved.respond_to?(:limit)
- assert Topic.approved.respond_to?(:count)
- assert Topic.approved.respond_to?(:length)
+ assert_respond_to Topic.approved, :limit
+ assert_respond_to Topic.approved, :count
+ assert_respond_to Topic.approved, :length
end
def test_scopes_with_options_limit_finds_to_those_matching_the_criteria_specified
- assert !Topic.all.merge!(where: { approved: true }).to_a.empty?
+ assert_not_empty Topic.all.merge!(where: { approved: true }).to_a
assert_equal Topic.all.merge!(where: { approved: true }).to_a, Topic.approved
assert_equal Topic.where(approved: true).count, Topic.approved.count
@@ -84,8 +86,8 @@ class NamedScopingTest < ActiveRecord::TestCase
def test_scopes_are_composable
assert_equal((approved = Topic.all.merge!(where: { approved: true }).to_a), Topic.approved)
assert_equal((replied = Topic.all.merge!(where: "replies_count > 0").to_a), Topic.replied)
- assert !(approved == replied)
- assert !(approved & replied).empty?
+ assert_not (approved == replied)
+ assert_not_empty (approved & replied)
assert_equal approved & replied, Topic.approved.replied
end
@@ -113,9 +115,10 @@ class NamedScopingTest < ActiveRecord::TestCase
def test_has_many_associations_have_access_to_scopes
assert_not_equal Post.containing_the_letter_a, authors(:david).posts
- assert !Post.containing_the_letter_a.empty?
+ assert_not_empty Post.containing_the_letter_a
- assert_equal authors(:david).posts & Post.containing_the_letter_a, authors(:david).posts.containing_the_letter_a
+ expected = authors(:david).posts & Post.containing_the_letter_a
+ assert_equal expected.sort_by(&:id), authors(:david).posts.containing_the_letter_a.sort_by(&:id)
end
def test_scope_with_STI
@@ -125,14 +128,15 @@ class NamedScopingTest < ActiveRecord::TestCase
def test_has_many_through_associations_have_access_to_scopes
assert_not_equal Comment.containing_the_letter_e, authors(:david).comments
- assert !Comment.containing_the_letter_e.empty?
+ assert_not_empty Comment.containing_the_letter_e
- assert_equal authors(:david).comments & Comment.containing_the_letter_e, authors(:david).comments.containing_the_letter_e
+ expected = authors(:david).comments & Comment.containing_the_letter_e
+ assert_equal expected.sort_by(&:id), authors(:david).comments.containing_the_letter_e.sort_by(&:id)
end
def test_scopes_honor_current_scopes_from_when_defined
- assert !Post.ranked_by_comments.limit_by(5).empty?
- assert !authors(:david).posts.ranked_by_comments.limit_by(5).empty?
+ assert_not_empty Post.ranked_by_comments.limit_by(5)
+ assert_not_empty authors(:david).posts.ranked_by_comments.limit_by(5)
assert_not_equal Post.ranked_by_comments.limit_by(5), authors(:david).posts.ranked_by_comments.limit_by(5)
assert_not_equal Post.top(5), authors(:david).posts.top(5)
# Oracle sometimes sorts differently if WHERE condition is changed
@@ -147,15 +151,31 @@ class NamedScopingTest < ActiveRecord::TestCase
assert_equal "The scope body needs to be callable.", e.message
end
+ def test_scopes_name_is_relation_method
+ conflicts = [
+ :records,
+ :to_ary,
+ :to_sql,
+ :explain
+ ]
+
+ conflicts.each do |name|
+ e = assert_raises ArgumentError do
+ Class.new(Post).class_eval { scope name, -> { where(approved: true) } }
+ end
+ assert_match(/You tried to define a scope named \"#{name}\" on the model/, e.message)
+ end
+ end
+
def test_active_records_have_scope_named__all__
- assert !Topic.all.empty?
+ assert_not_empty Topic.all
assert_equal Topic.all.to_a, Topic.base
end
def test_active_records_have_scope_named__scoped__
scope = Topic.where("content LIKE '%Have%'")
- assert !scope.empty?
+ assert_not_empty scope
assert_equal scope, Topic.all.merge!(where: "content LIKE '%Have%'")
end
@@ -167,7 +187,7 @@ class NamedScopingTest < ActiveRecord::TestCase
def test_first_and_last_should_not_use_query_when_results_are_loaded
topics = Topic.base
- topics.reload # force load
+ topics.load # force load
assert_no_queries do
topics.first
topics.last
@@ -178,7 +198,7 @@ class NamedScopingTest < ActiveRecord::TestCase
topics = Topic.base
assert_queries(2) do
topics.empty? # use count query
- topics.collect # force load
+ topics.load # force load
topics.empty? # use loaded (no query)
end
end
@@ -187,7 +207,7 @@ class NamedScopingTest < ActiveRecord::TestCase
topics = Topic.base
assert_queries(2) do
topics.any? # use count query
- topics.collect # force load
+ topics.load # force load
topics.any? # use loaded (no query)
end
end
@@ -203,21 +223,21 @@ class NamedScopingTest < ActiveRecord::TestCase
def test_any_should_not_fire_query_if_scope_loaded
topics = Topic.base
- topics.collect # force load
+ topics.load # force load
assert_no_queries { assert topics.any? }
end
def test_model_class_should_respond_to_any
- assert Topic.any?
+ assert_predicate Topic, :any?
Topic.delete_all
- assert !Topic.any?
+ assert_not_predicate Topic, :any?
end
def test_many_should_not_load_results
topics = Topic.base
assert_queries(2) do
topics.many? # use count query
- topics.collect # force load
+ topics.load # force load
topics.many? # use loaded (no query)
end
end
@@ -233,28 +253,28 @@ class NamedScopingTest < ActiveRecord::TestCase
def test_many_should_not_fire_query_if_scope_loaded
topics = Topic.base
- topics.collect # force load
+ topics.load # force load
assert_no_queries { assert topics.many? }
end
def test_many_should_return_false_if_none_or_one
topics = Topic.base.where(id: 0)
- assert !topics.many?
+ assert_not_predicate topics, :many?
topics = Topic.base.where(id: 1)
- assert !topics.many?
+ assert_not_predicate topics, :many?
end
def test_many_should_return_true_if_more_than_one
- assert Topic.base.many?
+ assert_predicate Topic.base, :many?
end
def test_model_class_should_respond_to_many
Topic.delete_all
- assert !Topic.many?
+ assert_not_predicate Topic, :many?
Topic.create!
- assert !Topic.many?
+ assert_not_predicate Topic, :many?
Topic.create!
- assert Topic.many?
+ assert_predicate Topic, :many?
end
def test_should_build_on_top_of_scope
@@ -283,6 +303,13 @@ class NamedScopingTest < ActiveRecord::TestCase
assert_equal "lifo", topic.author_name
end
+ def test_deprecated_delegating_private_method
+ assert_deprecated do
+ scope = Topic.all.by_private_lifo
+ assert_not scope.instance_variable_get(:@delegate_to_klass)
+ end
+ end
+
def test_reserved_scope_names
klass = Class.new(ActiveRecord::Base) do
self.table_name = "topics"
@@ -384,7 +411,7 @@ class NamedScopingTest < ActiveRecord::TestCase
def test_size_should_use_length_when_results_are_loaded
topics = Topic.base
- topics.reload # force load
+ topics.load # force load
assert_no_queries do
topics.size # use loaded (no query)
end
@@ -403,16 +430,16 @@ class NamedScopingTest < ActiveRecord::TestCase
def test_chaining_applies_last_conditions_when_creating
post = Topic.rejected.new
- assert !post.approved?
+ assert_not_predicate post, :approved?
post = Topic.rejected.approved.new
- assert post.approved?
+ assert_predicate post, :approved?
post = Topic.approved.rejected.new
- assert !post.approved?
+ assert_not_predicate post, :approved?
post = Topic.approved.rejected.approved.new
- assert post.approved?
+ assert_predicate post, :approved?
end
def test_chaining_combines_conditions_when_searching
@@ -461,8 +488,9 @@ class NamedScopingTest < ActiveRecord::TestCase
[:public_method, :protected_method, :private_method].each do |reserved_method|
assert Topic.respond_to?(reserved_method, true)
- ActiveRecord::Base.logger.expects(:warn)
- silence_warnings { Topic.scope(reserved_method, -> {}) }
+ assert_called(ActiveRecord::Base.logger, :warn) do
+ silence_warnings { Topic.scope(reserved_method, -> {}) }
+ end
end
end
@@ -478,7 +506,7 @@ class NamedScopingTest < ActiveRecord::TestCase
def test_index_on_scope
approved = Topic.approved.order("id ASC")
assert_equal topics(:second), approved[0]
- assert approved.loaded?
+ assert_predicate approved, :loaded?
end
def test_nested_scopes_queries_size
@@ -551,17 +579,23 @@ class NamedScopingTest < ActiveRecord::TestCase
assert_equal 1, SpecialComment.where(body: "go crazy").created.count
end
+ def test_model_class_should_respond_to_extending
+ assert_raises OopsError do
+ Comment.unscoped.oops_comments.destroy_all
+ end
+ end
+
def test_model_class_should_respond_to_none
- assert !Topic.none?
+ assert_not_predicate Topic, :none?
Topic.delete_all
- assert Topic.none?
+ assert_predicate Topic, :none?
end
def test_model_class_should_respond_to_one
- assert !Topic.one?
+ assert_not_predicate Topic, :one?
Topic.delete_all
- assert !Topic.one?
+ assert_not_predicate Topic, :one?
Topic.create!
- assert Topic.one?
+ assert_predicate Topic, :one?
end
end
diff --git a/activerecord/test/cases/scoping/relation_scoping_test.rb b/activerecord/test/cases/scoping/relation_scoping_test.rb
index a1ae57fdbb..b4f4379e5e 100644
--- a/activerecord/test/cases/scoping/relation_scoping_test.rb
+++ b/activerecord/test/cases/scoping/relation_scoping_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/post"
require "models/author"
@@ -10,7 +12,7 @@ require "models/person"
require "models/reference"
class RelationScopingTest < ActiveRecord::TestCase
- fixtures :authors, :developers, :projects, :comments, :posts, :developers_projects
+ fixtures :authors, :author_addresses, :developers, :projects, :comments, :posts, :developers_projects
setup do
developers(:david)
@@ -103,7 +105,7 @@ class RelationScopingTest < ActiveRecord::TestCase
Developer.select("id, name").scoping do
developer = Developer.where("name = 'David'").first
assert_equal "David", developer.name
- assert !developer.has_attribute?(:salary)
+ assert_not developer.has_attribute?(:salary)
end
end
@@ -211,34 +213,69 @@ class RelationScopingTest < ActiveRecord::TestCase
def test_current_scope_does_not_pollute_sibling_subclasses
Comment.none.scoping do
- assert_not SpecialComment.all.any?
- assert_not VerySpecialComment.all.any?
- assert_not SubSpecialComment.all.any?
+ assert_not_predicate SpecialComment.all, :any?
+ assert_not_predicate VerySpecialComment.all, :any?
+ assert_not_predicate SubSpecialComment.all, :any?
end
SpecialComment.none.scoping do
- assert Comment.all.any?
- assert VerySpecialComment.all.any?
- assert_not SubSpecialComment.all.any?
+ assert_predicate Comment.all, :any?
+ assert_predicate VerySpecialComment.all, :any?
+ assert_not_predicate SubSpecialComment.all, :any?
end
SubSpecialComment.none.scoping do
- assert Comment.all.any?
- assert VerySpecialComment.all.any?
- assert SpecialComment.all.any?
+ assert_predicate Comment.all, :any?
+ assert_predicate VerySpecialComment.all, :any?
+ assert_predicate SpecialComment.all, :any?
+ end
+ end
+
+ def test_scoping_is_correctly_restored
+ Comment.unscoped do
+ SpecialComment.unscoped.created
+ end
+
+ assert_nil Comment.send(:current_scope)
+ assert_nil SpecialComment.send(:current_scope)
+ end
+
+ def test_scoping_respects_current_class
+ Comment.unscoped do
+ assert_equal "a comment...", Comment.all.what_are_you
+ assert_equal "a special comment...", SpecialComment.all.what_are_you
end
end
- def test_circular_joins_with_current_scope_does_not_crash
+ def test_scoping_respects_sti_constraint
+ Comment.unscoped do
+ assert_equal comments(:greetings), Comment.find(1)
+ assert_raises(ActiveRecord::RecordNotFound) { SpecialComment.find(1) }
+ end
+ end
+
+ def test_scoping_works_in_the_scope_block
+ expected = SpecialPostWithDefaultScope.unscoped.to_a
+ assert_equal expected, SpecialPostWithDefaultScope.unscoped_all
+ end
+
+ def test_circular_joins_with_scoping_does_not_crash
posts = Post.joins(comments: :post).scoping do
- Post.current_scope.first(10)
+ Post.first(10)
end
assert_equal posts, Post.joins(comments: :post).first(10)
end
+
+ def test_circular_left_joins_with_scoping_does_not_crash
+ posts = Post.left_joins(comments: :post).scoping do
+ Post.first(10)
+ end
+ assert_equal posts, Post.left_joins(comments: :post).first(10)
+ end
end
class NestedRelationScopingTest < ActiveRecord::TestCase
- fixtures :authors, :developers, :projects, :comments, :posts
+ fixtures :authors, :author_addresses, :developers, :projects, :comments, :posts
def test_merge_options
Developer.where("salary = 80000").scoping do
@@ -302,7 +339,7 @@ class NestedRelationScopingTest < ActiveRecord::TestCase
def test_nested_exclusive_scope_for_create
comment = Comment.create_with(body: "Hey guys, nested scopes are broken. Please fix!").scoping do
Comment.unscoped.create_with(post_id: 1).scoping do
- assert Comment.new.body.blank?
+ assert_predicate Comment.new.body, :blank?
Comment.create body: "Hey guys"
end
end
diff --git a/activerecord/test/cases/secure_token_test.rb b/activerecord/test/cases/secure_token_test.rb
index 7b9cbee40a..f5fa6aa302 100644
--- a/activerecord/test/cases/secure_token_test.rb
+++ b/activerecord/test/cases/secure_token_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/user"
diff --git a/activerecord/test/cases/serialization_test.rb b/activerecord/test/cases/serialization_test.rb
index ec33ad38f2..932780bfef 100644
--- a/activerecord/test/cases/serialization_test.rb
+++ b/activerecord/test/cases/serialization_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/contact"
require "models/topic"
@@ -65,8 +67,8 @@ class SerializationTest < ActiveRecord::TestCase
klazz.include_root_in_json = false
assert ActiveRecord::Base.include_root_in_json
- assert !klazz.include_root_in_json
- assert !klazz.new.include_root_in_json
+ assert_not klazz.include_root_in_json
+ assert_not klazz.new.include_root_in_json
ensure
ActiveRecord::Base.include_root_in_json = original_root_in_json
end
diff --git a/activerecord/test/cases/serialized_attribute_test.rb b/activerecord/test/cases/serialized_attribute_test.rb
index 673392b4c4..1192b30b14 100644
--- a/activerecord/test/cases/serialized_attribute_test.rb
+++ b/activerecord/test/cases/serialized_attribute_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/topic"
require "models/reply"
@@ -157,6 +159,13 @@ class SerializedAttributeTest < ActiveRecord::TestCase
assert_equal(settings, Topic.find(topic.id).content)
end
+ def test_where_by_serialized_attribute_with_array
+ settings = [ "color" => "green" ]
+ Topic.serialize(:content, Array)
+ topic = Topic.create!(content: settings)
+ assert_equal topic, Topic.where(content: settings).take
+ end
+
def test_where_by_serialized_attribute_with_hash
settings = { "color" => "green" }
Topic.serialize(:content, Hash)
@@ -164,6 +173,13 @@ class SerializedAttributeTest < ActiveRecord::TestCase
assert_equal topic, Topic.where(content: settings).take
end
+ def test_where_by_serialized_attribute_with_hash_in_array
+ settings = { "color" => "green" }
+ Topic.serialize(:content, Hash)
+ topic = Topic.create!(content: settings)
+ assert_equal topic, Topic.where(content: [settings]).take
+ end
+
def test_serialized_default_class
Topic.serialize(:content, Hash)
topic = Topic.new
@@ -277,7 +293,7 @@ class SerializedAttributeTest < ActiveRecord::TestCase
topic = Topic.new(content: nil)
- assert_not topic.content_changed?
+ assert_not_predicate topic, :content_changed?
end
def test_classes_without_no_arg_constructors_are_not_supported
@@ -306,7 +322,7 @@ class SerializedAttributeTest < ActiveRecord::TestCase
topic = Topic.create!(content: {})
topic2 = Topic.create!(content: nil)
- assert_equal [topic, topic2], Topic.where(content: nil)
+ assert_equal [topic, topic2], Topic.where(content: nil).sort_by(&:id)
end
def test_nil_is_always_persisted_as_null
@@ -347,6 +363,34 @@ class SerializedAttributeTest < ActiveRecord::TestCase
topic = model.create!(foo: "bar")
topic.foo
- refute topic.changed?
+ assert_not_predicate topic, :changed?
+ end
+
+ def test_serialized_attribute_works_under_concurrent_initial_access
+ model = Topic.dup
+
+ topic = model.last
+ topic.update group: "1"
+
+ model.serialize :group, JSON
+ model.reset_column_information
+
+ # This isn't strictly necessary for the test, but a little bit of
+ # knowledge of internals allows us to make failures far more likely.
+ model.define_singleton_method(:define_attribute) do |*args|
+ Thread.pass
+ super(*args)
+ end
+
+ threads = 4.times.map do
+ Thread.new do
+ topic.reload.group
+ end
+ end
+
+ # All the threads should retrieve the value knowing it is JSON, and
+ # thus decode it. If this fails, some threads will instead see the
+ # raw string ("1"), or raise an exception.
+ assert_equal [1] * threads.size, threads.map(&:value)
end
end
diff --git a/activerecord/test/cases/statement_cache_test.rb b/activerecord/test/cases/statement_cache_test.rb
index fab3648564..e3c12f68fd 100644
--- a/activerecord/test/cases/statement_cache_test.rb
+++ b/activerecord/test/cases/statement_cache_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/book"
require "models/liquid"
@@ -10,7 +12,6 @@ module ActiveRecord
@connection = ActiveRecord::Base.connection
end
- #Cache v 1.1 tests
def test_statement_cache
Book.create(name: "my book")
Book.create(name: "my other book")
@@ -19,9 +20,9 @@ module ActiveRecord
Book.where(name: params.bind)
end
- b = cache.execute([ "my book" ], Book, Book.connection)
+ b = cache.execute([ "my book" ], Book.connection)
assert_equal "my book", b[0].name
- b = cache.execute([ "my other book" ], Book, Book.connection)
+ b = cache.execute([ "my other book" ], Book.connection)
assert_equal "my other book", b[0].name
end
@@ -33,9 +34,9 @@ module ActiveRecord
Book.where(id: params.bind)
end
- b = cache.execute([ b1.id ], Book, Book.connection)
+ b = cache.execute([ b1.id ], Book.connection)
assert_equal b1.name, b[0].name
- b = cache.execute([ b2.id ], Book, Book.connection)
+ b = cache.execute([ b2.id ], Book.connection)
assert_equal b2.name, b[0].name
end
@@ -49,8 +50,6 @@ module ActiveRecord
assert_equal("my other book", b.name)
end
- #End
-
def test_statement_cache_with_simple_statement
cache = ActiveRecord::StatementCache.create(Book.connection) do |params|
Book.where(name: "my book").where("author_id > 3")
@@ -58,7 +57,7 @@ module ActiveRecord
Book.create(name: "my book", author_id: 4)
- books = cache.execute([], Book, Book.connection)
+ books = cache.execute([], Book.connection)
assert_equal "my book", books[0].name
end
@@ -71,7 +70,7 @@ module ActiveRecord
molecule = salty.molecules.create(name: "dioxane")
molecule.electrons.create(name: "lepton")
- liquids = cache.execute([], Book, Book.connection)
+ liquids = cache.execute([], Book.connection)
assert_equal "salty", liquids[0].name
end
@@ -84,13 +83,13 @@ module ActiveRecord
Book.create(name: "my book")
end
- first_books = cache.execute([], Book, Book.connection)
+ first_books = cache.execute([], Book.connection)
3.times do
Book.create(name: "my book")
end
- additional_books = cache.execute([], Book, Book.connection)
+ additional_books = cache.execute([], Book.connection)
assert first_books != additional_books
end
@@ -103,7 +102,7 @@ module ActiveRecord
Book.find_by(name: "my other book")
end
- refute_equal book, other_book
+ assert_not_equal book, other_book
end
def test_find_by_does_not_use_statement_cache_if_table_name_is_changed
diff --git a/activerecord/test/cases/store_test.rb b/activerecord/test/cases/store_test.rb
index 633a8a0ebc..4457cfbd37 100644
--- a/activerecord/test/cases/store_test.rb
+++ b/activerecord/test/cases/store_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/admin"
require "models/admin/user"
@@ -6,7 +8,12 @@ class StoreTest < ActiveRecord::TestCase
fixtures :'admin/users'
setup do
- @john = Admin::User.create!(name: "John Doe", color: "black", remember_login: true, height: "tall", is_a_good_guy: true)
+ @john = Admin::User.create!(
+ name: "John Doe", color: "black", remember_login: true,
+ height: "tall", is_a_good_guy: true,
+ parent_name: "Quinn", partner_name: "Dallas",
+ partner_birthday: "1997-11-1"
+ )
end
test "reading store attributes through accessors" do
@@ -22,6 +29,21 @@ class StoreTest < ActiveRecord::TestCase
assert_equal "37signals.com", @john.homepage
end
+ test "reading store attributes through accessors with prefix" do
+ assert_equal "Quinn", @john.parent_name
+ assert_nil @john.parent_birthday
+ assert_equal "Dallas", @john.partner_name
+ assert_equal "1997-11-1", @john.partner_birthday
+ end
+
+ test "writing store attributes through accessors with prefix" do
+ @john.partner_name = "River"
+ @john.partner_birthday = "1999-2-11"
+
+ assert_equal "River", @john.partner_name
+ assert_equal "1999-2-11", @john.partner_birthday
+ end
+
test "accessing attributes not exposed by accessors" do
@john.settings[:icecream] = "graeters"
@john.save
@@ -43,7 +65,7 @@ class StoreTest < ActiveRecord::TestCase
test "updating the store will mark it as changed" do
@john.color = "red"
- assert @john.settings_changed?
+ assert_predicate @john, :settings_changed?
end
test "updating the store populates the changed array correctly" do
@@ -54,7 +76,7 @@ class StoreTest < ActiveRecord::TestCase
test "updating the store won't mark it as changed if an attribute isn't changed" do
@john.color = @john.color
- assert !@john.settings_changed?
+ assert_not_predicate @john, :settings_changed?
end
test "object initialization with not nullable column" do
@@ -135,7 +157,7 @@ class StoreTest < ActiveRecord::TestCase
test "updating the store will mark it as changed encoded with JSON" do
@john.height = "short"
- assert @john.json_data_changed?
+ assert_predicate @john, :json_data_changed?
end
test "object initialization with not nullable column encoded with JSON" do
@@ -192,4 +214,38 @@ class StoreTest < ActiveRecord::TestCase
second_dump = YAML.dump(loaded)
assert_equal @john, YAML.load(second_dump)
end
+
+ test "read store attributes through accessors with default suffix" do
+ @john.configs[:two_factor_auth] = true
+ assert_equal true, @john.two_factor_auth_configs
+ end
+
+ test "write store attributes through accessors with default suffix" do
+ @john.two_factor_auth_configs = false
+ assert_equal false, @john.configs[:two_factor_auth]
+ end
+
+ test "read store attributes through accessors with custom suffix" do
+ @john.configs[:login_retry] = 3
+ assert_equal 3, @john.login_retry_config
+ end
+
+ test "write store attributes through accessors with custom suffix" do
+ @john.login_retry_config = 5
+ assert_equal 5, @john.configs[:login_retry]
+ end
+
+ test "read accessor without pre/suffix in the same store as other pre/suffixed accessors still works" do
+ @john.configs[:secret_question] = "What is your high school?"
+ assert_equal "What is your high school?", @john.secret_question
+ end
+
+ test "write accessor without pre/suffix in the same store as other pre/suffixed accessors still works" do
+ @john.secret_question = "What was the Rails version when you first worked on it?"
+ assert_equal "What was the Rails version when you first worked on it?", @john.configs[:secret_question]
+ end
+
+ test "prefix/suffix do not affect stored attributes" do
+ assert_equal [:secret_question, :two_factor_auth, :login_retry], Admin::User.stored_attributes[:configs]
+ end
end
diff --git a/activerecord/test/cases/suppressor_test.rb b/activerecord/test/cases/suppressor_test.rb
index a7d16b7cdb..b68f0033d9 100644
--- a/activerecord/test/cases/suppressor_test.rb
+++ b/activerecord/test/cases/suppressor_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/notification"
require "models/user"
diff --git a/activerecord/test/cases/tasks/database_tasks_test.rb b/activerecord/test/cases/tasks/database_tasks_test.rb
index 5653fd83fd..d674bd562f 100644
--- a/activerecord/test/cases/tasks/database_tasks_test.rb
+++ b/activerecord/test/cases/tasks/database_tasks_test.rb
@@ -1,13 +1,23 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "active_record/tasks/database_tasks"
module ActiveRecord
module DatabaseTasksSetupper
def setup
- @mysql_tasks, @postgresql_tasks, @sqlite_tasks = stub, stub, stub
- ActiveRecord::Tasks::MySQLDatabaseTasks.stubs(:new).returns @mysql_tasks
- ActiveRecord::Tasks::PostgreSQLDatabaseTasks.stubs(:new).returns @postgresql_tasks
- ActiveRecord::Tasks::SQLiteDatabaseTasks.stubs(:new).returns @sqlite_tasks
+ @mysql_tasks, @postgresql_tasks, @sqlite_tasks = Array.new(
+ 3,
+ Class.new do
+ def create; end
+ def drop; end
+ def purge; end
+ def charset; end
+ def collation; end
+ def structure_dump(*); end
+ def structure_load(*); end
+ end.new
+ )
$stdout, @original_stdout = StringIO.new, $stdout
$stderr, @original_stderr = StringIO.new, $stderr
@@ -16,6 +26,16 @@ module ActiveRecord
def teardown
$stdout, $stderr = @original_stdout, @original_stderr
end
+
+ def with_stubbed_new
+ ActiveRecord::Tasks::MySQLDatabaseTasks.stub(:new, @mysql_tasks) do
+ ActiveRecord::Tasks::PostgreSQLDatabaseTasks.stub(:new, @postgresql_tasks) do
+ ActiveRecord::Tasks::SQLiteDatabaseTasks.stub(:new, @sqlite_tasks) do
+ yield
+ end
+ end
+ end
+ end
end
ADAPTERS_TASKS = {
@@ -26,28 +46,62 @@ module ActiveRecord
class DatabaseTasksUtilsTask < ActiveRecord::TestCase
def test_raises_an_error_when_called_with_protected_environment
- ActiveRecord::Migrator.stubs(:current_version).returns(1)
+ protected_environments = ActiveRecord::Base.protected_environments
+ current_env = ActiveRecord::Base.connection.migration_context.current_environment
+
+ assert_called_on_instance_of(
+ ActiveRecord::MigrationContext,
+ :current_version,
+ times: 6,
+ returns: 1
+ ) do
+ assert_not_includes protected_environments, current_env
+ # Assert no error
+ ActiveRecord::Tasks::DatabaseTasks.check_protected_environments!
+
+ ActiveRecord::Base.protected_environments = [current_env]
- protected_environments = ActiveRecord::Base.protected_environments.dup
- current_env = ActiveRecord::Migrator.current_environment
- assert_not_includes protected_environments, current_env
- # Assert no error
- ActiveRecord::Tasks::DatabaseTasks.check_protected_environments!
+ assert_raise(ActiveRecord::ProtectedEnvironmentError) do
+ ActiveRecord::Tasks::DatabaseTasks.check_protected_environments!
+ end
+ end
+ ensure
+ ActiveRecord::Base.protected_environments = protected_environments
+ end
- ActiveRecord::Base.protected_environments << current_env
- assert_raise(ActiveRecord::ProtectedEnvironmentError) do
+ def test_raises_an_error_when_called_with_protected_environment_which_name_is_a_symbol
+ protected_environments = ActiveRecord::Base.protected_environments
+ current_env = ActiveRecord::Base.connection.migration_context.current_environment
+ assert_called_on_instance_of(
+ ActiveRecord::MigrationContext,
+ :current_version,
+ times: 6,
+ returns: 1
+ ) do
+ assert_not_includes protected_environments, current_env
+ # Assert no error
ActiveRecord::Tasks::DatabaseTasks.check_protected_environments!
+
+ ActiveRecord::Base.protected_environments = [current_env.to_sym]
+ assert_raise(ActiveRecord::ProtectedEnvironmentError) do
+ ActiveRecord::Tasks::DatabaseTasks.check_protected_environments!
+ end
end
ensure
ActiveRecord::Base.protected_environments = protected_environments
end
def test_raises_an_error_if_no_migrations_have_been_made
- ActiveRecord::InternalMetadata.stubs(:table_exists?).returns(false)
- ActiveRecord::Migrator.stubs(:current_version).returns(1)
-
- assert_raise(ActiveRecord::NoEnvironmentInSchemaError) do
- ActiveRecord::Tasks::DatabaseTasks.check_protected_environments!
+ ActiveRecord::InternalMetadata.stub(:table_exists?, false) do
+ assert_called_on_instance_of(
+ ActiveRecord::MigrationContext,
+ :current_version,
+ returns: 1
+ ) do
+ assert_raise(ActiveRecord::NoEnvironmentInSchemaError) do
+ ActiveRecord::Tasks::DatabaseTasks.check_protected_environments!
+ end
+ end
end
end
end
@@ -60,11 +114,12 @@ module ActiveRecord
end
instance = klazz.new
- klazz.stubs(:new).returns instance
- instance.expects(:structure_dump).with("awesome-file.sql", nil)
-
- ActiveRecord::Tasks::DatabaseTasks.register_task(/foo/, klazz)
- ActiveRecord::Tasks::DatabaseTasks.structure_dump({ "adapter" => :foo }, "awesome-file.sql")
+ klazz.stub(:new, instance) do
+ assert_called_with(instance, :structure_dump, ["awesome-file.sql", nil]) do
+ ActiveRecord::Tasks::DatabaseTasks.register_task(/foo/, klazz)
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump({ "adapter" => :foo }, "awesome-file.sql")
+ end
+ end
end
def test_unregistered_task
@@ -79,8 +134,11 @@ module ActiveRecord
ADAPTERS_TASKS.each do |k, v|
define_method("test_#{k}_create") do
- eval("@#{v}").expects(:create)
- ActiveRecord::Tasks::DatabaseTasks.create "adapter" => k
+ with_stubbed_new do
+ assert_called(eval("@#{v}"), :create) do
+ ActiveRecord::Tasks::DatabaseTasks.create "adapter" => k
+ end
+ end
end
end
end
@@ -91,6 +149,7 @@ module ActiveRecord
ActiveRecord::Tasks::DatabaseTasks.dump_schema_cache(ActiveRecord::Base.connection, path)
assert File.file?(path)
ensure
+ ActiveRecord::Base.clear_cache!
FileUtils.rm_rf(path)
end
end
@@ -99,59 +158,88 @@ module ActiveRecord
def setup
@configurations = { "development" => { "database" => "my-db" } }
- ActiveRecord::Base.stubs(:configurations).returns(@configurations)
- # To refrain from connecting to a newly created empty DB in sqlite3_mem tests
- ActiveRecord::Base.connection_handler.stubs(:establish_connection)
+ $stdout, @original_stdout = StringIO.new, $stdout
+ $stderr, @original_stderr = StringIO.new, $stderr
end
- def test_ignores_configurations_without_databases
- @configurations["development"].merge!("database" => nil)
+ def teardown
+ $stdout, $stderr = @original_stdout, @original_stderr
+ end
- ActiveRecord::Tasks::DatabaseTasks.expects(:create).never
+ def test_ignores_configurations_without_databases
+ @configurations["development"]["database"] = nil
- ActiveRecord::Tasks::DatabaseTasks.create_all
+ with_stubbed_configurations_establish_connection do
+ assert_not_called(ActiveRecord::Tasks::DatabaseTasks, :create) do
+ ActiveRecord::Tasks::DatabaseTasks.create_all
+ end
+ end
end
def test_ignores_remote_databases
- @configurations["development"].merge!("host" => "my.server.tld")
- $stderr.stubs(:puts).returns(nil)
-
- ActiveRecord::Tasks::DatabaseTasks.expects(:create).never
+ @configurations["development"]["host"] = "my.server.tld"
- ActiveRecord::Tasks::DatabaseTasks.create_all
+ with_stubbed_configurations_establish_connection do
+ assert_not_called(ActiveRecord::Tasks::DatabaseTasks, :create) do
+ ActiveRecord::Tasks::DatabaseTasks.create_all
+ end
+ end
end
def test_warning_for_remote_databases
- @configurations["development"].merge!("host" => "my.server.tld")
+ @configurations["development"]["host"] = "my.server.tld"
- $stderr.expects(:puts).with("This task only modifies local databases. my-db is on a remote host.")
+ with_stubbed_configurations_establish_connection do
+ ActiveRecord::Tasks::DatabaseTasks.create_all
- ActiveRecord::Tasks::DatabaseTasks.create_all
+ assert_match "This task only modifies local databases. my-db is on a remote host.",
+ $stderr.string
+ end
end
def test_creates_configurations_with_local_ip
- @configurations["development"].merge!("host" => "127.0.0.1")
-
- ActiveRecord::Tasks::DatabaseTasks.expects(:create)
+ @configurations["development"]["host"] = "127.0.0.1"
- ActiveRecord::Tasks::DatabaseTasks.create_all
+ with_stubbed_configurations_establish_connection do
+ assert_called(ActiveRecord::Tasks::DatabaseTasks, :create) do
+ ActiveRecord::Tasks::DatabaseTasks.create_all
+ end
+ end
end
def test_creates_configurations_with_local_host
- @configurations["development"].merge!("host" => "localhost")
-
- ActiveRecord::Tasks::DatabaseTasks.expects(:create)
+ @configurations["development"]["host"] = "localhost"
- ActiveRecord::Tasks::DatabaseTasks.create_all
+ with_stubbed_configurations_establish_connection do
+ assert_called(ActiveRecord::Tasks::DatabaseTasks, :create) do
+ ActiveRecord::Tasks::DatabaseTasks.create_all
+ end
+ end
end
def test_creates_configurations_with_blank_hosts
- @configurations["development"].merge!("host" => nil)
-
- ActiveRecord::Tasks::DatabaseTasks.expects(:create)
+ @configurations["development"]["host"] = nil
- ActiveRecord::Tasks::DatabaseTasks.create_all
+ with_stubbed_configurations_establish_connection do
+ assert_called(ActiveRecord::Tasks::DatabaseTasks, :create) do
+ ActiveRecord::Tasks::DatabaseTasks.create_all
+ end
+ end
end
+
+ private
+ def with_stubbed_configurations_establish_connection
+ old_configurations = ActiveRecord::Base.configurations
+ ActiveRecord::Base.configurations = @configurations
+
+ # To refrain from connecting to a newly created empty DB in
+ # sqlite3_mem tests
+ ActiveRecord::Base.connection_handler.stub(:establish_connection, nil) do
+ yield
+ end
+ ensure
+ ActiveRecord::Base.configurations = old_configurations
+ end
end
class DatabaseTasksCreateCurrentTest < ActiveRecord::TestCase
@@ -159,57 +247,211 @@ module ActiveRecord
@configurations = {
"development" => { "database" => "dev-db" },
"test" => { "database" => "test-db" },
- "production" => { "database" => "prod-db" }
+ "production" => { "url" => "abstract://prod-db-host/prod-db" }
}
-
- ActiveRecord::Base.stubs(:configurations).returns(@configurations)
- ActiveRecord::Base.stubs(:establish_connection).returns(true)
end
def test_creates_current_environment_database
- ActiveRecord::Tasks::DatabaseTasks.expects(:create).
- with("database" => "prod-db")
+ with_stubbed_configurations_establish_connection do
+ assert_called_with(
+ ActiveRecord::Tasks::DatabaseTasks,
+ :create,
+ ["database" => "test-db"],
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.create_current(
+ ActiveSupport::StringInquirer.new("test")
+ )
+ end
+ end
+ end
- ActiveRecord::Tasks::DatabaseTasks.create_current(
- ActiveSupport::StringInquirer.new("production")
- )
+ def test_creates_current_environment_database_with_url
+ with_stubbed_configurations_establish_connection do
+ assert_called_with(
+ ActiveRecord::Tasks::DatabaseTasks,
+ :create,
+ ["adapter" => "abstract", "database" => "prod-db", "host" => "prod-db-host"],
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.create_current(
+ ActiveSupport::StringInquirer.new("production")
+ )
+ end
+ end
end
def test_creates_test_and_development_databases_when_env_was_not_specified
- ActiveRecord::Tasks::DatabaseTasks.expects(:create).
- with("database" => "dev-db")
- ActiveRecord::Tasks::DatabaseTasks.expects(:create).
- with("database" => "test-db")
-
- ActiveRecord::Tasks::DatabaseTasks.create_current(
- ActiveSupport::StringInquirer.new("development")
- )
+ with_stubbed_configurations_establish_connection do
+ assert_called_with(
+ ActiveRecord::Tasks::DatabaseTasks,
+ :create,
+ [
+ ["database" => "dev-db"],
+ ["database" => "test-db"]
+ ],
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.create_current(
+ ActiveSupport::StringInquirer.new("development")
+ )
+ end
+ end
end
def test_creates_test_and_development_databases_when_rails_env_is_development
old_env = ENV["RAILS_ENV"]
ENV["RAILS_ENV"] = "development"
- ActiveRecord::Tasks::DatabaseTasks.expects(:create).
- with("database" => "dev-db")
- ActiveRecord::Tasks::DatabaseTasks.expects(:create).
- with("database" => "test-db")
- ActiveRecord::Tasks::DatabaseTasks.create_current(
- ActiveSupport::StringInquirer.new("development")
- )
+ with_stubbed_configurations_establish_connection do
+ assert_called_with(
+ ActiveRecord::Tasks::DatabaseTasks,
+ :create,
+ [
+ ["database" => "dev-db"],
+ ["database" => "test-db"]
+ ],
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.create_current(
+ ActiveSupport::StringInquirer.new("development")
+ )
+ end
+ end
ensure
ENV["RAILS_ENV"] = old_env
end
- def test_establishes_connection_for_the_given_environment
- ActiveRecord::Tasks::DatabaseTasks.stubs(:create).returns true
+ def test_establishes_connection_for_the_given_environments
+ ActiveRecord::Tasks::DatabaseTasks.stub(:create, nil) do
+ assert_called_with(ActiveRecord::Base, :establish_connection, [:development]) do
+ ActiveRecord::Tasks::DatabaseTasks.create_current(
+ ActiveSupport::StringInquirer.new("development")
+ )
+ end
+ end
+ end
- ActiveRecord::Base.expects(:establish_connection).with(:development)
+ private
+ def with_stubbed_configurations_establish_connection
+ old_configurations = ActiveRecord::Base.configurations
+ ActiveRecord::Base.configurations = @configurations
- ActiveRecord::Tasks::DatabaseTasks.create_current(
- ActiveSupport::StringInquirer.new("development")
- )
+ ActiveRecord::Base.connection_handler.stub(:establish_connection, nil) do
+ yield
+ end
+ ensure
+ ActiveRecord::Base.configurations = old_configurations
+ end
+ end
+
+ class DatabaseTasksCreateCurrentThreeTierTest < ActiveRecord::TestCase
+ def setup
+ @configurations = {
+ "development" => { "primary" => { "database" => "dev-db" }, "secondary" => { "database" => "secondary-dev-db" } },
+ "test" => { "primary" => { "database" => "test-db" }, "secondary" => { "database" => "secondary-test-db" } },
+ "production" => { "primary" => { "url" => "abstract://prod-db-host/prod-db" }, "secondary" => { "url" => "abstract://secondary-prod-db-host/secondary-prod-db" } }
+ }
+ end
+
+ def test_creates_current_environment_database
+ with_stubbed_configurations_establish_connection do
+ assert_called_with(
+ ActiveRecord::Tasks::DatabaseTasks,
+ :create,
+ [
+ ["database" => "test-db"],
+ ["database" => "secondary-test-db"]
+ ]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.create_current(
+ ActiveSupport::StringInquirer.new("test")
+ )
+ end
+ end
+ end
+
+ def test_creates_current_environment_database_with_url
+ with_stubbed_configurations_establish_connection do
+ assert_called_with(
+ ActiveRecord::Tasks::DatabaseTasks,
+ :create,
+ [
+ ["adapter" => "abstract", "database" => "prod-db", "host" => "prod-db-host"],
+ ["adapter" => "abstract", "database" => "secondary-prod-db", "host" => "secondary-prod-db-host"]
+ ]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.create_current(
+ ActiveSupport::StringInquirer.new("production")
+ )
+ end
+ end
+ end
+
+ def test_creates_test_and_development_databases_when_env_was_not_specified
+ with_stubbed_configurations_establish_connection do
+ assert_called_with(
+ ActiveRecord::Tasks::DatabaseTasks,
+ :create,
+ [
+ ["database" => "dev-db"],
+ ["database" => "secondary-dev-db"],
+ ["database" => "test-db"],
+ ["database" => "secondary-test-db"]
+ ]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.create_current(
+ ActiveSupport::StringInquirer.new("development")
+ )
+ end
+ end
+ end
+
+ def test_creates_test_and_development_databases_when_rails_env_is_development
+ old_env = ENV["RAILS_ENV"]
+ ENV["RAILS_ENV"] = "development"
+
+ with_stubbed_configurations_establish_connection do
+ assert_called_with(
+ ActiveRecord::Tasks::DatabaseTasks,
+ :create,
+ [
+ ["database" => "dev-db"],
+ ["database" => "secondary-dev-db"],
+ ["database" => "test-db"],
+ ["database" => "secondary-test-db"]
+ ]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.create_current(
+ ActiveSupport::StringInquirer.new("development")
+ )
+ end
+ end
+ ensure
+ ENV["RAILS_ENV"] = old_env
+ end
+
+ def test_establishes_connection_for_the_given_environments_config
+ ActiveRecord::Tasks::DatabaseTasks.stub(:create, nil) do
+ assert_called_with(
+ ActiveRecord::Base,
+ :establish_connection,
+ [:development]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.create_current(
+ ActiveSupport::StringInquirer.new("development")
+ )
+ end
+ end
end
+
+ private
+ def with_stubbed_configurations_establish_connection
+ old_configurations = ActiveRecord::Base.configurations
+ ActiveRecord::Base.configurations = @configurations
+
+ ActiveRecord::Base.connection_handler.stub(:establish_connection, nil) do
+ yield
+ end
+ ensure
+ ActiveRecord::Base.configurations = old_configurations
+ end
end
class DatabaseTasksDropTest < ActiveRecord::TestCase
@@ -217,8 +459,11 @@ module ActiveRecord
ADAPTERS_TASKS.each do |k, v|
define_method("test_#{k}_drop") do
- eval("@#{v}").expects(:drop)
- ActiveRecord::Tasks::DatabaseTasks.drop "adapter" => k
+ with_stubbed_new do
+ assert_called(eval("@#{v}"), :drop) do
+ ActiveRecord::Tasks::DatabaseTasks.drop "adapter" => k
+ end
+ end
end
end
end
@@ -227,57 +472,84 @@ module ActiveRecord
def setup
@configurations = { development: { "database" => "my-db" } }
- ActiveRecord::Base.stubs(:configurations).returns(@configurations)
+ $stdout, @original_stdout = StringIO.new, $stdout
+ $stderr, @original_stderr = StringIO.new, $stderr
end
- def test_ignores_configurations_without_databases
- @configurations[:development].merge!("database" => nil)
+ def teardown
+ $stdout, $stderr = @original_stdout, @original_stderr
+ end
- ActiveRecord::Tasks::DatabaseTasks.expects(:drop).never
+ def test_ignores_configurations_without_databases
+ @configurations[:development]["database"] = nil
- ActiveRecord::Tasks::DatabaseTasks.drop_all
+ with_stubbed_configurations do
+ assert_not_called(ActiveRecord::Tasks::DatabaseTasks, :drop) do
+ ActiveRecord::Tasks::DatabaseTasks.drop_all
+ end
+ end
end
def test_ignores_remote_databases
- @configurations[:development].merge!("host" => "my.server.tld")
- $stderr.stubs(:puts).returns(nil)
-
- ActiveRecord::Tasks::DatabaseTasks.expects(:drop).never
+ @configurations[:development]["host"] = "my.server.tld"
- ActiveRecord::Tasks::DatabaseTasks.drop_all
+ with_stubbed_configurations do
+ assert_not_called(ActiveRecord::Tasks::DatabaseTasks, :drop) do
+ ActiveRecord::Tasks::DatabaseTasks.drop_all
+ end
+ end
end
def test_warning_for_remote_databases
- @configurations[:development].merge!("host" => "my.server.tld")
+ @configurations[:development]["host"] = "my.server.tld"
- $stderr.expects(:puts).with("This task only modifies local databases. my-db is on a remote host.")
+ with_stubbed_configurations do
+ ActiveRecord::Tasks::DatabaseTasks.drop_all
- ActiveRecord::Tasks::DatabaseTasks.drop_all
+ assert_match "This task only modifies local databases. my-db is on a remote host.",
+ $stderr.string
+ end
end
def test_drops_configurations_with_local_ip
- @configurations[:development].merge!("host" => "127.0.0.1")
-
- ActiveRecord::Tasks::DatabaseTasks.expects(:drop)
+ @configurations[:development]["host"] = "127.0.0.1"
- ActiveRecord::Tasks::DatabaseTasks.drop_all
+ with_stubbed_configurations do
+ assert_called(ActiveRecord::Tasks::DatabaseTasks, :drop) do
+ ActiveRecord::Tasks::DatabaseTasks.drop_all
+ end
+ end
end
def test_drops_configurations_with_local_host
- @configurations[:development].merge!("host" => "localhost")
-
- ActiveRecord::Tasks::DatabaseTasks.expects(:drop)
+ @configurations[:development]["host"] = "localhost"
- ActiveRecord::Tasks::DatabaseTasks.drop_all
+ with_stubbed_configurations do
+ assert_called(ActiveRecord::Tasks::DatabaseTasks, :drop) do
+ ActiveRecord::Tasks::DatabaseTasks.drop_all
+ end
+ end
end
def test_drops_configurations_with_blank_hosts
- @configurations[:development].merge!("host" => nil)
-
- ActiveRecord::Tasks::DatabaseTasks.expects(:drop)
+ @configurations[:development]["host"] = nil
- ActiveRecord::Tasks::DatabaseTasks.drop_all
+ with_stubbed_configurations do
+ assert_called(ActiveRecord::Tasks::DatabaseTasks, :drop) do
+ ActiveRecord::Tasks::DatabaseTasks.drop_all
+ end
+ end
end
+
+ private
+ def with_stubbed_configurations
+ old_configurations = ActiveRecord::Base.configurations
+ ActiveRecord::Base.configurations = @configurations
+
+ yield
+ ensure
+ ActiveRecord::Base.configurations = old_configurations
+ end
end
class DatabaseTasksDropCurrentTest < ActiveRecord::TestCase
@@ -285,74 +557,311 @@ module ActiveRecord
@configurations = {
"development" => { "database" => "dev-db" },
"test" => { "database" => "test-db" },
- "production" => { "database" => "prod-db" }
+ "production" => { "url" => "abstract://prod-db-host/prod-db" }
}
-
- ActiveRecord::Base.stubs(:configurations).returns(@configurations)
end
def test_drops_current_environment_database
- ActiveRecord::Tasks::DatabaseTasks.expects(:drop).
- with("database" => "prod-db")
+ with_stubbed_configurations do
+ assert_called_with(ActiveRecord::Tasks::DatabaseTasks, :drop,
+ ["database" => "test-db"]) do
+ ActiveRecord::Tasks::DatabaseTasks.drop_current(
+ ActiveSupport::StringInquirer.new("test")
+ )
+ end
+ end
+ end
- ActiveRecord::Tasks::DatabaseTasks.drop_current(
- ActiveSupport::StringInquirer.new("production")
- )
+ def test_drops_current_environment_database_with_url
+ with_stubbed_configurations do
+ assert_called_with(ActiveRecord::Tasks::DatabaseTasks, :drop,
+ ["adapter" => "abstract", "database" => "prod-db", "host" => "prod-db-host"]) do
+ ActiveRecord::Tasks::DatabaseTasks.drop_current(
+ ActiveSupport::StringInquirer.new("production")
+ )
+ end
+ end
end
def test_drops_test_and_development_databases_when_env_was_not_specified
- ActiveRecord::Tasks::DatabaseTasks.expects(:drop).
- with("database" => "dev-db")
- ActiveRecord::Tasks::DatabaseTasks.expects(:drop).
- with("database" => "test-db")
-
- ActiveRecord::Tasks::DatabaseTasks.drop_current(
- ActiveSupport::StringInquirer.new("development")
- )
+ with_stubbed_configurations do
+ assert_called_with(
+ ActiveRecord::Tasks::DatabaseTasks,
+ :drop,
+ [
+ ["database" => "dev-db"],
+ ["database" => "test-db"]
+ ]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.drop_current(
+ ActiveSupport::StringInquirer.new("development")
+ )
+ end
+ end
end
def test_drops_testand_development_databases_when_rails_env_is_development
old_env = ENV["RAILS_ENV"]
ENV["RAILS_ENV"] = "development"
- ActiveRecord::Tasks::DatabaseTasks.expects(:drop).
- with("database" => "dev-db")
- ActiveRecord::Tasks::DatabaseTasks.expects(:drop).
- with("database" => "test-db")
- ActiveRecord::Tasks::DatabaseTasks.drop_current(
- ActiveSupport::StringInquirer.new("development")
- )
+ with_stubbed_configurations do
+ assert_called_with(
+ ActiveRecord::Tasks::DatabaseTasks,
+ :drop,
+ [
+ ["database" => "dev-db"],
+ ["database" => "test-db"]
+ ]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.drop_current(
+ ActiveSupport::StringInquirer.new("development")
+ )
+ end
+ end
ensure
ENV["RAILS_ENV"] = old_env
end
- end
- class DatabaseTasksMigrateTest < ActiveRecord::TestCase
- self.use_transactional_tests = false
+ private
+ def with_stubbed_configurations
+ old_configurations = ActiveRecord::Base.configurations
+ ActiveRecord::Base.configurations = @configurations
+ yield
+ ensure
+ ActiveRecord::Base.configurations = old_configurations
+ end
+ end
+
+ class DatabaseTasksDropCurrentThreeTierTest < ActiveRecord::TestCase
def setup
- ActiveRecord::Tasks::DatabaseTasks.migrations_paths = "custom/path"
+ @configurations = {
+ "development" => { "primary" => { "database" => "dev-db" }, "secondary" => { "database" => "secondary-dev-db" } },
+ "test" => { "primary" => { "database" => "test-db" }, "secondary" => { "database" => "secondary-test-db" } },
+ "production" => { "primary" => { "url" => "abstract://prod-db-host/prod-db" }, "secondary" => { "url" => "abstract://secondary-prod-db-host/secondary-prod-db" } }
+ }
end
- def teardown
- ActiveRecord::Tasks::DatabaseTasks.migrations_paths = nil
+ def test_drops_current_environment_database
+ with_stubbed_configurations do
+ assert_called_with(
+ ActiveRecord::Tasks::DatabaseTasks,
+ :drop,
+ [
+ ["database" => "test-db"],
+ ["database" => "secondary-test-db"]
+ ]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.drop_current(
+ ActiveSupport::StringInquirer.new("test")
+ )
+ end
+ end
end
- def test_migrate_receives_correct_env_vars
- verbose, version = ENV["VERBOSE"], ENV["VERSION"]
+ def test_drops_current_environment_database_with_url
+ with_stubbed_configurations do
+ assert_called_with(
+ ActiveRecord::Tasks::DatabaseTasks,
+ :drop,
+ [
+ ["adapter" => "abstract", "database" => "prod-db", "host" => "prod-db-host"],
+ ["adapter" => "abstract", "database" => "secondary-prod-db", "host" => "secondary-prod-db-host"]
+ ]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.drop_current(
+ ActiveSupport::StringInquirer.new("production")
+ )
+ end
+ end
+ end
+
+ def test_drops_test_and_development_databases_when_env_was_not_specified
+ with_stubbed_configurations do
+ assert_called_with(
+ ActiveRecord::Tasks::DatabaseTasks,
+ :drop,
+ [
+ ["database" => "dev-db"],
+ ["database" => "secondary-dev-db"],
+ ["database" => "test-db"],
+ ["database" => "secondary-test-db"]
+ ]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.drop_current(
+ ActiveSupport::StringInquirer.new("development")
+ )
+ end
+ end
+ end
- ENV["VERBOSE"] = "false"
- ENV["VERSION"] = "4"
+ def test_drops_testand_development_databases_when_rails_env_is_development
+ old_env = ENV["RAILS_ENV"]
+ ENV["RAILS_ENV"] = "development"
- ActiveRecord::Migrator.expects(:migrate).with("custom/path", 4)
- ActiveRecord::Tasks::DatabaseTasks.migrate
+ with_stubbed_configurations do
+ assert_called_with(
+ ActiveRecord::Tasks::DatabaseTasks,
+ :drop,
+ [
+ ["database" => "dev-db"],
+ ["database" => "secondary-dev-db"],
+ ["database" => "test-db"],
+ ["database" => "secondary-test-db"]
+ ]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.drop_current(
+ ActiveSupport::StringInquirer.new("development")
+ )
+ end
+ end
ensure
- ENV["VERBOSE"], ENV["VERSION"] = verbose, version
+ ENV["RAILS_ENV"] = old_env
+ end
+
+ private
+ def with_stubbed_configurations
+ old_configurations = ActiveRecord::Base.configurations
+ ActiveRecord::Base.configurations = @configurations
+
+ yield
+ ensure
+ ActiveRecord::Base.configurations = old_configurations
+ end
+ end
+
+ if current_adapter?(:SQLite3Adapter) && !in_memory_db?
+ class DatabaseTasksMigrateTest < ActiveRecord::TestCase
+ self.use_transactional_tests = false
+
+ # Use a memory db here to avoid having to rollback at the end
+ setup do
+ migrations_path = MIGRATIONS_ROOT + "/valid"
+ file = ActiveRecord::Base.connection.raw_connection.filename
+ @conn = ActiveRecord::Base.establish_connection adapter: "sqlite3",
+ database: ":memory:", migrations_paths: migrations_path
+ source_db = SQLite3::Database.new file
+ dest_db = ActiveRecord::Base.connection.raw_connection
+ backup = SQLite3::Backup.new(dest_db, "main", source_db, "main")
+ backup.step(-1)
+ backup.finish
+ end
+
+ teardown do
+ @conn.release_connection if @conn
+ ActiveRecord::Base.establish_connection :arunit
+ end
+
+ def test_migrate_set_and_unset_verbose_and_version_env_vars
+ verbose, version = ENV["VERBOSE"], ENV["VERSION"]
+ ENV["VERSION"] = "2"
+ ENV["VERBOSE"] = "false"
+
+ # run down migration because it was already run on copied db
+ assert_empty capture_migration_output
+
+ ENV.delete("VERSION")
+ ENV.delete("VERBOSE")
+
+ # re-run up migration
+ assert_includes capture_migration_output, "migrating"
+ ensure
+ ENV["VERBOSE"], ENV["VERSION"] = verbose, version
+ end
+
+ def test_migrate_set_and_unset_empty_values_for_verbose_and_version_env_vars
+ verbose, version = ENV["VERBOSE"], ENV["VERSION"]
+
+ ENV["VERSION"] = "2"
+ ENV["VERBOSE"] = "false"
+
+ # run down migration because it was already run on copied db
+ assert_empty capture_migration_output
+
+ ENV["VERBOSE"] = ""
+ ENV["VERSION"] = ""
+
+ # re-run up migration
+ assert_includes capture_migration_output, "migrating"
+ ensure
+ ENV["VERBOSE"], ENV["VERSION"] = verbose, version
+ end
+
+ def test_migrate_set_and_unset_nonsense_values_for_verbose_and_version_env_vars
+ verbose, version = ENV["VERBOSE"], ENV["VERSION"]
+
+ # run down migration because it was already run on copied db
+ ENV["VERSION"] = "2"
+ ENV["VERBOSE"] = "false"
+
+ assert_empty capture_migration_output
+
+ ENV["VERBOSE"] = "yes"
+ ENV["VERSION"] = "2"
+
+ # run no migration because 2 was already run
+ assert_empty capture_migration_output
+ ensure
+ ENV["VERBOSE"], ENV["VERSION"] = verbose, version
+ end
+
+ private
+ def capture_migration_output
+ capture(:stdout) do
+ ActiveRecord::Tasks::DatabaseTasks.migrate
+ end
+ end
+ end
+ end
+
+ class DatabaseTasksMigrateErrorTest < ActiveRecord::TestCase
+ self.use_transactional_tests = false
+
+ def test_migrate_raise_error_on_invalid_version_format
+ version = ENV["VERSION"]
+
+ ENV["VERSION"] = "unknown"
+ e = assert_raise(RuntimeError) { ActiveRecord::Tasks::DatabaseTasks.migrate }
+ assert_match(/Invalid format of target version/, e.message)
+
+ ENV["VERSION"] = "0.1.11"
+ e = assert_raise(RuntimeError) { ActiveRecord::Tasks::DatabaseTasks.migrate }
+ assert_match(/Invalid format of target version/, e.message)
+
+ ENV["VERSION"] = "1.1.11"
+ e = assert_raise(RuntimeError) { ActiveRecord::Tasks::DatabaseTasks.migrate }
+ assert_match(/Invalid format of target version/, e.message)
+
+ ENV["VERSION"] = "0 "
+ e = assert_raise(RuntimeError) { ActiveRecord::Tasks::DatabaseTasks.migrate }
+ assert_match(/Invalid format of target version/, e.message)
+
+ ENV["VERSION"] = "1."
+ e = assert_raise(RuntimeError) { ActiveRecord::Tasks::DatabaseTasks.migrate }
+ assert_match(/Invalid format of target version/, e.message)
+
+ ENV["VERSION"] = "1_"
+ e = assert_raise(RuntimeError) { ActiveRecord::Tasks::DatabaseTasks.migrate }
+ assert_match(/Invalid format of target version/, e.message)
+
+ ENV["VERSION"] = "1_name"
+ e = assert_raise(RuntimeError) { ActiveRecord::Tasks::DatabaseTasks.migrate }
+ assert_match(/Invalid format of target version/, e.message)
+ ensure
+ ENV["VERSION"] = version
+ end
+
+ def test_migrate_raise_error_on_failed_check_target_version
+ ActiveRecord::Tasks::DatabaseTasks.stub(:check_target_version, -> { raise "foo" }) do
+ e = assert_raise(RuntimeError) { ActiveRecord::Tasks::DatabaseTasks.migrate }
+ assert_equal "foo", e.message
+ end
end
def test_migrate_clears_schema_cache_afterward
- ActiveRecord::Base.expects(:clear_cache!)
- ActiveRecord::Tasks::DatabaseTasks.migrate
+ assert_called(ActiveRecord::Base, :clear_cache!) do
+ ActiveRecord::Tasks::DatabaseTasks.migrate
+ end
end
end
@@ -361,38 +870,55 @@ module ActiveRecord
ADAPTERS_TASKS.each do |k, v|
define_method("test_#{k}_purge") do
- eval("@#{v}").expects(:purge)
- ActiveRecord::Tasks::DatabaseTasks.purge "adapter" => k
+ with_stubbed_new do
+ assert_called(eval("@#{v}"), :purge) do
+ ActiveRecord::Tasks::DatabaseTasks.purge "adapter" => k
+ end
+ end
end
end
end
class DatabaseTasksPurgeCurrentTest < ActiveRecord::TestCase
def test_purges_current_environment_database
+ old_configurations = ActiveRecord::Base.configurations
configurations = {
"development" => { "database" => "dev-db" },
"test" => { "database" => "test-db" },
"production" => { "database" => "prod-db" }
}
- ActiveRecord::Base.stubs(:configurations).returns(configurations)
- ActiveRecord::Tasks::DatabaseTasks.expects(:purge).
- with("database" => "prod-db")
- ActiveRecord::Base.expects(:establish_connection).with(:production)
+ ActiveRecord::Base.configurations = configurations
- ActiveRecord::Tasks::DatabaseTasks.purge_current("production")
+ assert_called_with(
+ ActiveRecord::Tasks::DatabaseTasks,
+ :purge,
+ ["database" => "prod-db"]
+ ) do
+ assert_called_with(ActiveRecord::Base, :establish_connection, [:production]) do
+ ActiveRecord::Tasks::DatabaseTasks.purge_current("production")
+ end
+ end
+ ensure
+ ActiveRecord::Base.configurations = old_configurations
end
end
class DatabaseTasksPurgeAllTest < ActiveRecord::TestCase
def test_purge_all_local_configurations
+ old_configurations = ActiveRecord::Base.configurations
configurations = { development: { "database" => "my-db" } }
- ActiveRecord::Base.stubs(:configurations).returns(configurations)
-
- ActiveRecord::Tasks::DatabaseTasks.expects(:purge).
- with("database" => "my-db")
-
- ActiveRecord::Tasks::DatabaseTasks.purge_all
+ ActiveRecord::Base.configurations = configurations
+
+ assert_called_with(
+ ActiveRecord::Tasks::DatabaseTasks,
+ :purge,
+ ["database" => "my-db"]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.purge_all
+ end
+ ensure
+ ActiveRecord::Base.configurations = old_configurations
end
end
@@ -401,8 +927,11 @@ module ActiveRecord
ADAPTERS_TASKS.each do |k, v|
define_method("test_#{k}_charset") do
- eval("@#{v}").expects(:charset)
- ActiveRecord::Tasks::DatabaseTasks.charset "adapter" => k
+ with_stubbed_new do
+ assert_called(eval("@#{v}"), :charset) do
+ ActiveRecord::Tasks::DatabaseTasks.charset "adapter" => k
+ end
+ end
end
end
end
@@ -412,19 +941,130 @@ module ActiveRecord
ADAPTERS_TASKS.each do |k, v|
define_method("test_#{k}_collation") do
- eval("@#{v}").expects(:collation)
- ActiveRecord::Tasks::DatabaseTasks.collation "adapter" => k
+ with_stubbed_new do
+ assert_called(eval("@#{v}"), :collation) do
+ ActiveRecord::Tasks::DatabaseTasks.collation "adapter" => k
+ end
+ end
end
end
end
+ class DatabaseTaskTargetVersionTest < ActiveRecord::TestCase
+ def test_target_version_returns_nil_if_version_does_not_exist
+ version = ENV.delete("VERSION")
+ assert_nil ActiveRecord::Tasks::DatabaseTasks.target_version
+ ensure
+ ENV["VERSION"] = version
+ end
+
+ def test_target_version_returns_nil_if_version_is_empty
+ version = ENV["VERSION"]
+
+ ENV["VERSION"] = ""
+ assert_nil ActiveRecord::Tasks::DatabaseTasks.target_version
+ ensure
+ ENV["VERSION"] = version
+ end
+
+ def test_target_version_returns_converted_to_integer_env_version_if_version_exists
+ version = ENV["VERSION"]
+
+ ENV["VERSION"] = "0"
+ assert_equal ENV["VERSION"].to_i, ActiveRecord::Tasks::DatabaseTasks.target_version
+
+ ENV["VERSION"] = "42"
+ assert_equal ENV["VERSION"].to_i, ActiveRecord::Tasks::DatabaseTasks.target_version
+
+ ENV["VERSION"] = "042"
+ assert_equal ENV["VERSION"].to_i, ActiveRecord::Tasks::DatabaseTasks.target_version
+ ensure
+ ENV["VERSION"] = version
+ end
+ end
+
+ class DatabaseTaskCheckTargetVersionTest < ActiveRecord::TestCase
+ def test_check_target_version_does_not_raise_error_on_empty_version
+ version = ENV["VERSION"]
+ ENV["VERSION"] = ""
+ assert_nothing_raised { ActiveRecord::Tasks::DatabaseTasks.check_target_version }
+ ensure
+ ENV["VERSION"] = version
+ end
+
+ def test_check_target_version_does_not_raise_error_if_version_is_not_setted
+ version = ENV.delete("VERSION")
+ assert_nothing_raised { ActiveRecord::Tasks::DatabaseTasks.check_target_version }
+ ensure
+ ENV["VERSION"] = version
+ end
+
+ def test_check_target_version_raises_error_on_invalid_version_format
+ version = ENV["VERSION"]
+
+ ENV["VERSION"] = "unknown"
+ e = assert_raise(RuntimeError) { ActiveRecord::Tasks::DatabaseTasks.check_target_version }
+ assert_match(/Invalid format of target version/, e.message)
+
+ ENV["VERSION"] = "0.1.11"
+ e = assert_raise(RuntimeError) { ActiveRecord::Tasks::DatabaseTasks.check_target_version }
+ assert_match(/Invalid format of target version/, e.message)
+
+ ENV["VERSION"] = "1.1.11"
+ e = assert_raise(RuntimeError) { ActiveRecord::Tasks::DatabaseTasks.check_target_version }
+ assert_match(/Invalid format of target version/, e.message)
+
+ ENV["VERSION"] = "0 "
+ e = assert_raise(RuntimeError) { ActiveRecord::Tasks::DatabaseTasks.check_target_version }
+ assert_match(/Invalid format of target version/, e.message)
+
+ ENV["VERSION"] = "1."
+ e = assert_raise(RuntimeError) { ActiveRecord::Tasks::DatabaseTasks.check_target_version }
+ assert_match(/Invalid format of target version/, e.message)
+
+ ENV["VERSION"] = "1_"
+ e = assert_raise(RuntimeError) { ActiveRecord::Tasks::DatabaseTasks.check_target_version }
+ assert_match(/Invalid format of target version/, e.message)
+
+ ENV["VERSION"] = "1_name"
+ e = assert_raise(RuntimeError) { ActiveRecord::Tasks::DatabaseTasks.check_target_version }
+ assert_match(/Invalid format of target version/, e.message)
+ ensure
+ ENV["VERSION"] = version
+ end
+
+ def test_check_target_version_does_not_raise_error_on_valid_version_format
+ version = ENV["VERSION"]
+
+ ENV["VERSION"] = "0"
+ assert_nothing_raised { ActiveRecord::Tasks::DatabaseTasks.check_target_version }
+
+ ENV["VERSION"] = "1"
+ assert_nothing_raised { ActiveRecord::Tasks::DatabaseTasks.check_target_version }
+
+ ENV["VERSION"] = "001"
+ assert_nothing_raised { ActiveRecord::Tasks::DatabaseTasks.check_target_version }
+
+ ENV["VERSION"] = "001_name.rb"
+ assert_nothing_raised { ActiveRecord::Tasks::DatabaseTasks.check_target_version }
+ ensure
+ ENV["VERSION"] = version
+ end
+ end
+
class DatabaseTasksStructureDumpTest < ActiveRecord::TestCase
include DatabaseTasksSetupper
ADAPTERS_TASKS.each do |k, v|
define_method("test_#{k}_structure_dump") do
- eval("@#{v}").expects(:structure_dump).with("awesome-file.sql", nil)
- ActiveRecord::Tasks::DatabaseTasks.structure_dump({ "adapter" => k }, "awesome-file.sql")
+ with_stubbed_new do
+ assert_called_with(
+ eval("@#{v}"), :structure_dump,
+ ["awesome-file.sql", nil]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump({ "adapter" => k }, "awesome-file.sql")
+ end
+ end
end
end
end
@@ -434,31 +1074,41 @@ module ActiveRecord
ADAPTERS_TASKS.each do |k, v|
define_method("test_#{k}_structure_load") do
- eval("@#{v}").expects(:structure_load).with("awesome-file.sql", nil)
- ActiveRecord::Tasks::DatabaseTasks.structure_load({ "adapter" => k }, "awesome-file.sql")
+ with_stubbed_new do
+ assert_called_with(
+ eval("@#{v}"),
+ :structure_load,
+ ["awesome-file.sql", nil]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.structure_load({ "adapter" => k }, "awesome-file.sql")
+ end
+ end
end
end
end
class DatabaseTasksCheckSchemaFileTest < ActiveRecord::TestCase
def test_check_schema_file
- Kernel.expects(:abort).with(regexp_matches(/awesome-file.sql/))
- ActiveRecord::Tasks::DatabaseTasks.check_schema_file("awesome-file.sql")
+ assert_called_with(Kernel, :abort, [/awesome-file.sql/]) do
+ ActiveRecord::Tasks::DatabaseTasks.check_schema_file("awesome-file.sql")
+ end
end
end
class DatabaseTasksCheckSchemaFileDefaultsTest < ActiveRecord::TestCase
def test_check_schema_file_defaults
- ActiveRecord::Tasks::DatabaseTasks.stubs(:db_dir).returns("/tmp")
- assert_equal "/tmp/schema.rb", ActiveRecord::Tasks::DatabaseTasks.schema_file
+ ActiveRecord::Tasks::DatabaseTasks.stub(:db_dir, "/tmp") do
+ assert_equal "/tmp/schema.rb", ActiveRecord::Tasks::DatabaseTasks.schema_file
+ end
end
end
class DatabaseTasksCheckSchemaFileSpecifiedFormatsTest < ActiveRecord::TestCase
{ ruby: "schema.rb", sql: "structure.sql" }.each_pair do |fmt, filename|
define_method("test_check_schema_file_for_#{fmt}_format") do
- ActiveRecord::Tasks::DatabaseTasks.stubs(:db_dir).returns("/tmp")
- assert_equal "/tmp/#{filename}", ActiveRecord::Tasks::DatabaseTasks.schema_file(fmt)
+ ActiveRecord::Tasks::DatabaseTasks.stub(:db_dir, "/tmp") do
+ assert_equal "/tmp/#{filename}", ActiveRecord::Tasks::DatabaseTasks.schema_file(fmt)
+ end
end
end
end
diff --git a/activerecord/test/cases/tasks/mysql_rake_test.rb b/activerecord/test/cases/tasks/mysql_rake_test.rb
index f30e0958c3..4d6dff68f9 100644
--- a/activerecord/test/cases/tasks/mysql_rake_test.rb
+++ b/activerecord/test/cases/tasks/mysql_rake_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "active_record/tasks/database_tasks"
@@ -5,15 +7,11 @@ if current_adapter?(:Mysql2Adapter)
module ActiveRecord
class MysqlDBCreateTest < ActiveRecord::TestCase
def setup
- @connection = stub(create_database: true)
+ @connection = Class.new { def create_database(*); end }.new
@configuration = {
"adapter" => "mysql2",
"database" => "my-app-db"
}
-
- ActiveRecord::Base.stubs(:connection).returns(@connection)
- ActiveRecord::Base.stubs(:establish_connection).returns(true)
-
$stdout, @original_stdout = StringIO.new, $stdout
$stderr, @original_stderr = StringIO.new, $stderr
end
@@ -23,59 +21,97 @@ if current_adapter?(:Mysql2Adapter)
end
def test_establishes_connection_without_database
- ActiveRecord::Base.expects(:establish_connection).
- with("adapter" => "mysql2", "database" => nil)
-
- ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ ActiveRecord::Base.stub(:connection, @connection) do
+ assert_called_with(
+ ActiveRecord::Base,
+ :establish_connection,
+ [
+ [ "adapter" => "mysql2", "database" => nil ],
+ [ "adapter" => "mysql2", "database" => "my-app-db" ],
+ ]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
+ end
end
def test_creates_database_with_no_default_options
- @connection.expects(:create_database).
- with("my-app-db", {})
-
- ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ with_stubbed_connection_establish_connection do
+ assert_called_with(@connection, :create_database, ["my-app-db", {}]) do
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
+ end
end
def test_creates_database_with_given_encoding
- @connection.expects(:create_database).
- with("my-app-db", charset: "latin1")
-
- ActiveRecord::Tasks::DatabaseTasks.create @configuration.merge("encoding" => "latin1")
+ with_stubbed_connection_establish_connection do
+ assert_called_with(@connection, :create_database, ["my-app-db", charset: "latin1"]) do
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration.merge("encoding" => "latin1")
+ end
+ end
end
def test_creates_database_with_given_collation
- @connection.expects(:create_database).
- with("my-app-db", collation: "latin1_swedish_ci")
-
- ActiveRecord::Tasks::DatabaseTasks.create @configuration.merge("collation" => "latin1_swedish_ci")
+ with_stubbed_connection_establish_connection do
+ assert_called_with(
+ @connection,
+ :create_database,
+ ["my-app-db", collation: "latin1_swedish_ci"]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration.merge("collation" => "latin1_swedish_ci")
+ end
+ end
end
def test_establishes_connection_to_database
- ActiveRecord::Base.expects(:establish_connection).with(@configuration)
-
- ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ ActiveRecord::Base.stub(:connection, @connection) do
+ assert_called_with(
+ ActiveRecord::Base,
+ :establish_connection,
+ [
+ ["adapter" => "mysql2", "database" => nil],
+ [@configuration]
+ ]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
+ end
end
def test_when_database_created_successfully_outputs_info_to_stdout
- ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ with_stubbed_connection_establish_connection do
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
- assert_equal "Created database 'my-app-db'\n", $stdout.string
+ assert_equal "Created database 'my-app-db'\n", $stdout.string
+ end
end
def test_create_when_database_exists_outputs_info_to_stderr
- ActiveRecord::Base.connection.stubs(:create_database).raises(
- ActiveRecord::Tasks::DatabaseAlreadyExists
- )
+ with_stubbed_connection_establish_connection do
+ ActiveRecord::Base.connection.stub(
+ :create_database,
+ proc { raise ActiveRecord::Tasks::DatabaseAlreadyExists }
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+
+ assert_equal "Database 'my-app-db' already exists\n", $stderr.string
+ end
+ end
+ end
- ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ private
- assert_equal "Database 'my-app-db' already exists\n", $stderr.string
- end
+ def with_stubbed_connection_establish_connection
+ ActiveRecord::Base.stub(:establish_connection, nil) do
+ ActiveRecord::Base.stub(:connection, @connection) do
+ yield
+ end
+ end
+ end
end
- class MysqlDBCreateAsRootTest < ActiveRecord::TestCase
+ class MysqlDBCreateWithInvalidPermissionsTest < ActiveRecord::TestCase
def setup
- @connection = stub("Connection", create_database: true)
@error = Mysql2::Error.new("Invalid permissions")
@configuration = {
"adapter" => "mysql2",
@@ -83,15 +119,6 @@ if current_adapter?(:Mysql2Adapter)
"username" => "pat",
"password" => "wossname"
}
-
- $stdin.stubs(:gets).returns("secret\n")
- $stdout.stubs(:print).returns(nil)
- @error.stubs(:errno).returns(1045)
- ActiveRecord::Base.stubs(:connection).returns(@connection)
- ActiveRecord::Base.stubs(:establish_connection).
- raises(@error).
- then.returns(true)
-
$stdout, @original_stdout = StringIO.new, $stdout
$stderr, @original_stderr = StringIO.new, $stderr
end
@@ -100,88 +127,22 @@ if current_adapter?(:Mysql2Adapter)
$stdout, $stderr = @original_stdout, @original_stderr
end
- def test_root_password_is_requested
- assert_permissions_granted_for("pat")
- $stdin.expects(:gets).returns("secret\n")
-
- ActiveRecord::Tasks::DatabaseTasks.create @configuration
- end
-
- def test_connection_established_as_root
- assert_permissions_granted_for("pat")
- ActiveRecord::Base.expects(:establish_connection).with(
- "adapter" => "mysql2",
- "database" => nil,
- "username" => "root",
- "password" => "secret"
- )
-
- ActiveRecord::Tasks::DatabaseTasks.create @configuration
- end
-
- def test_database_created_by_root
- assert_permissions_granted_for("pat")
- @connection.expects(:create_database).
- with("my-app-db", {})
-
- ActiveRecord::Tasks::DatabaseTasks.create @configuration
- end
-
- def test_grant_privileges_for_normal_user
- assert_permissions_granted_for("pat")
- ActiveRecord::Tasks::DatabaseTasks.create @configuration
- end
-
- def test_do_not_grant_privileges_for_root_user
- @configuration["username"] = "root"
- @configuration["password"] = ""
- ActiveRecord::Tasks::DatabaseTasks.create @configuration
- end
-
- def test_connection_established_as_normal_user
- assert_permissions_granted_for("pat")
- ActiveRecord::Base.expects(:establish_connection).returns do
- ActiveRecord::Base.expects(:establish_connection).with(
- "adapter" => "mysql2",
- "database" => "my-app-db",
- "username" => "pat",
- "password" => "secret"
- )
-
- raise @error
+ def test_raises_error
+ ActiveRecord::Base.stub(:establish_connection, -> * { raise @error }) do
+ assert_raises(Mysql2::Error, "Invalid permissions") do
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
end
-
- ActiveRecord::Tasks::DatabaseTasks.create @configuration
end
-
- def test_sends_output_to_stderr_when_other_errors
- @error.stubs(:errno).returns(42)
-
- $stderr.expects(:puts).at_least_once.returns(nil)
-
- ActiveRecord::Tasks::DatabaseTasks.create @configuration
- end
-
- private
-
- def assert_permissions_granted_for(db_user)
- db_name = @configuration["database"]
- db_password = @configuration["password"]
- @connection.expects(:execute).with("GRANT ALL PRIVILEGES ON #{db_name}.* TO '#{db_user}'@'localhost' IDENTIFIED BY '#{db_password}' WITH GRANT OPTION;")
- end
end
class MySQLDBDropTest < ActiveRecord::TestCase
def setup
- @connection = stub(drop_database: true)
+ @connection = Class.new { def drop_database(name); end }.new
@configuration = {
"adapter" => "mysql2",
"database" => "my-app-db"
}
-
- ActiveRecord::Base.stubs(:connection).returns(@connection)
- ActiveRecord::Base.stubs(:establish_connection).returns(true)
-
$stdout, @original_stdout = StringIO.new, $stdout
$stderr, @original_stderr = StringIO.new, $stderr
end
@@ -191,91 +152,130 @@ if current_adapter?(:Mysql2Adapter)
end
def test_establishes_connection_to_mysql_database
- ActiveRecord::Base.expects(:establish_connection).with @configuration
-
- ActiveRecord::Tasks::DatabaseTasks.drop @configuration
+ ActiveRecord::Base.stub(:connection, @connection) do
+ assert_called_with(
+ ActiveRecord::Base,
+ :establish_connection,
+ [@configuration]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.drop @configuration
+ end
+ end
end
def test_drops_database
- @connection.expects(:drop_database).with("my-app-db")
-
- ActiveRecord::Tasks::DatabaseTasks.drop @configuration
+ with_stubbed_connection_establish_connection do
+ assert_called_with(@connection, :drop_database, ["my-app-db"]) do
+ ActiveRecord::Tasks::DatabaseTasks.drop @configuration
+ end
+ end
end
def test_when_database_dropped_successfully_outputs_info_to_stdout
- ActiveRecord::Tasks::DatabaseTasks.drop @configuration
+ with_stubbed_connection_establish_connection do
+ ActiveRecord::Tasks::DatabaseTasks.drop @configuration
- assert_equal "Dropped database 'my-app-db'\n", $stdout.string
+ assert_equal "Dropped database 'my-app-db'\n", $stdout.string
+ end
end
+
+ private
+
+ def with_stubbed_connection_establish_connection
+ ActiveRecord::Base.stub(:establish_connection, nil) do
+ ActiveRecord::Base.stub(:connection, @connection) do
+ yield
+ end
+ end
+ end
end
class MySQLPurgeTest < ActiveRecord::TestCase
def setup
- @connection = stub(recreate_database: true)
+ @connection = Class.new { def recreate_database(*); end }.new
@configuration = {
"adapter" => "mysql2",
"database" => "test-db"
}
-
- ActiveRecord::Base.stubs(:connection).returns(@connection)
- ActiveRecord::Base.stubs(:establish_connection).returns(true)
end
def test_establishes_connection_to_the_appropriate_database
- ActiveRecord::Base.expects(:establish_connection).with(@configuration)
-
- ActiveRecord::Tasks::DatabaseTasks.purge @configuration
+ ActiveRecord::Base.stub(:connection, @connection) do
+ assert_called_with(
+ ActiveRecord::Base,
+ :establish_connection,
+ [@configuration]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.purge @configuration
+ end
+ end
end
def test_recreates_database_with_no_default_options
- @connection.expects(:recreate_database).
- with("test-db", {})
-
- ActiveRecord::Tasks::DatabaseTasks.purge @configuration
+ with_stubbed_connection_establish_connection do
+ assert_called_with(@connection, :recreate_database, ["test-db", {}]) do
+ ActiveRecord::Tasks::DatabaseTasks.purge @configuration
+ end
+ end
end
def test_recreates_database_with_the_given_options
- @connection.expects(:recreate_database).
- with("test-db", charset: "latin", collation: "latin1_swedish_ci")
-
- ActiveRecord::Tasks::DatabaseTasks.purge @configuration.merge(
- "encoding" => "latin", "collation" => "latin1_swedish_ci")
+ with_stubbed_connection_establish_connection do
+ assert_called_with(
+ @connection,
+ :recreate_database,
+ ["test-db", charset: "latin", collation: "latin1_swedish_ci"]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.purge @configuration.merge(
+ "encoding" => "latin", "collation" => "latin1_swedish_ci")
+ end
+ end
end
+
+ private
+
+ def with_stubbed_connection_establish_connection
+ ActiveRecord::Base.stub(:establish_connection, nil) do
+ ActiveRecord::Base.stub(:connection, @connection) do
+ yield
+ end
+ end
+ end
end
class MysqlDBCharsetTest < ActiveRecord::TestCase
def setup
- @connection = stub(create_database: true)
+ @connection = Class.new { def charset; end }.new
@configuration = {
"adapter" => "mysql2",
"database" => "my-app-db"
}
-
- ActiveRecord::Base.stubs(:connection).returns(@connection)
- ActiveRecord::Base.stubs(:establish_connection).returns(true)
end
def test_db_retrieves_charset
- @connection.expects(:charset)
- ActiveRecord::Tasks::DatabaseTasks.charset @configuration
+ ActiveRecord::Base.stub(:connection, @connection) do
+ assert_called(@connection, :charset) do
+ ActiveRecord::Tasks::DatabaseTasks.charset @configuration
+ end
+ end
end
end
class MysqlDBCollationTest < ActiveRecord::TestCase
def setup
- @connection = stub(create_database: true)
+ @connection = Class.new { def collation; end }.new
@configuration = {
"adapter" => "mysql2",
"database" => "my-app-db"
}
-
- ActiveRecord::Base.stubs(:connection).returns(@connection)
- ActiveRecord::Base.stubs(:establish_connection).returns(true)
end
def test_db_retrieves_collation
- @connection.expects(:collation)
- ActiveRecord::Tasks::DatabaseTasks.collation @configuration
+ ActiveRecord::Base.stub(:connection, @connection) do
+ assert_called_with(@connection, :collation) do
+ ActiveRecord::Tasks::DatabaseTasks.collation @configuration
+ end
+ end
end
end
@@ -289,14 +289,19 @@ if current_adapter?(:Mysql2Adapter)
def test_structure_dump
filename = "awesome-file.sql"
- Kernel.expects(:system).with("mysqldump", "--result-file", filename, "--no-data", "--routines", "--skip-comments", "test-db").returns(true)
-
- ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, filename)
+ assert_called_with(
+ Kernel,
+ :system,
+ ["mysqldump", "--result-file", filename, "--no-data", "--routines", "--skip-comments", "test-db"],
+ returns: true
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, filename)
+ end
end
def test_structure_dump_with_extra_flags
filename = "awesome-file.sql"
- expected_command = ["mysqldump", "--result-file", filename, "--no-data", "--routines", "--skip-comments", "--noop", "test-db"]
+ expected_command = ["mysqldump", "--noop", "--result-file", filename, "--no-data", "--routines", "--skip-comments", "test-db"]
assert_called_with(Kernel, :system, expected_command, returns: true) do
with_structure_dump_flags(["--noop"]) do
@@ -305,34 +310,61 @@ if current_adapter?(:Mysql2Adapter)
end
end
- def test_warn_when_external_structure_dump_command_execution_fails
+ def test_structure_dump_with_ignore_tables
filename = "awesome-file.sql"
- Kernel.expects(:system)
- .with("mysqldump", "--result-file", filename, "--no-data", "--routines", "--skip-comments", "test-db")
- .returns(false)
+ ActiveRecord::SchemaDumper.stub(:ignore_tables, ["foo", "bar"]) do
+ assert_called_with(
+ Kernel,
+ :system,
+ ["mysqldump", "--result-file", filename, "--no-data", "--routines", "--skip-comments", "--ignore-table=test-db.foo", "--ignore-table=test-db.bar", "test-db"],
+ returns: true
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, filename)
+ end
+ end
+ end
- e = assert_raise(RuntimeError) {
- ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, filename)
- }
- assert_match(/^failed to execute: `mysqldump`$/, e.message)
+ def test_warn_when_external_structure_dump_command_execution_fails
+ filename = "awesome-file.sql"
+ assert_called_with(
+ Kernel,
+ :system,
+ ["mysqldump", "--result-file", filename, "--no-data", "--routines", "--skip-comments", "test-db"],
+ returns: false
+ ) do
+ e = assert_raise(RuntimeError) {
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, filename)
+ }
+ assert_match(/^failed to execute: `mysqldump`$/, e.message)
+ end
end
def test_structure_dump_with_port_number
filename = "awesome-file.sql"
- Kernel.expects(:system).with("mysqldump", "--port=10000", "--result-file", filename, "--no-data", "--routines", "--skip-comments", "test-db").returns(true)
-
- ActiveRecord::Tasks::DatabaseTasks.structure_dump(
- @configuration.merge("port" => 10000),
- filename)
+ assert_called_with(
+ Kernel,
+ :system,
+ ["mysqldump", "--port=10000", "--result-file", filename, "--no-data", "--routines", "--skip-comments", "test-db"],
+ returns: true
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump(
+ @configuration.merge("port" => 10000),
+ filename)
+ end
end
def test_structure_dump_with_ssl
filename = "awesome-file.sql"
- Kernel.expects(:system).with("mysqldump", "--ssl-ca=ca.crt", "--result-file", filename, "--no-data", "--routines", "--skip-comments", "test-db").returns(true)
-
- ActiveRecord::Tasks::DatabaseTasks.structure_dump(
- @configuration.merge("sslca" => "ca.crt"),
- filename)
+ assert_called_with(
+ Kernel,
+ :system,
+ ["mysqldump", "--ssl-ca=ca.crt", "--result-file", filename, "--no-data", "--routines", "--skip-comments", "test-db"],
+ returns: true
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump(
+ @configuration.merge("sslca" => "ca.crt"),
+ filename)
+ end
end
private
@@ -355,7 +387,7 @@ if current_adapter?(:Mysql2Adapter)
def test_structure_load
filename = "awesome-file.sql"
- expected_command = ["mysql", "--execute", %{SET FOREIGN_KEY_CHECKS = 0; SOURCE #{filename}; SET FOREIGN_KEY_CHECKS = 1}, "--database", "test-db", "--noop"]
+ expected_command = ["mysql", "--noop", "--execute", %{SET FOREIGN_KEY_CHECKS = 0; SOURCE #{filename}; SET FOREIGN_KEY_CHECKS = 1}, "--database", "test-db"]
assert_called_with(Kernel, :system, expected_command, returns: true) do
with_structure_load_flags(["--noop"]) do
diff --git a/activerecord/test/cases/tasks/postgresql_rake_test.rb b/activerecord/test/cases/tasks/postgresql_rake_test.rb
index a23100c32a..8c6e8d79f3 100644
--- a/activerecord/test/cases/tasks/postgresql_rake_test.rb
+++ b/activerecord/test/cases/tasks/postgresql_rake_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "active_record/tasks/database_tasks"
@@ -5,15 +7,11 @@ if current_adapter?(:PostgreSQLAdapter)
module ActiveRecord
class PostgreSQLDBCreateTest < ActiveRecord::TestCase
def setup
- @connection = stub(create_database: true)
+ @connection = Class.new { def create_database(*); end }.new
@configuration = {
"adapter" => "postgresql",
"database" => "my-app-db"
}
-
- ActiveRecord::Base.stubs(:connection).returns(@connection)
- ActiveRecord::Base.stubs(:establish_connection).returns(true)
-
$stdout, @original_stdout = StringIO.new, $stdout
$stderr, @original_stderr = StringIO.new, $stderr
end
@@ -23,82 +21,141 @@ if current_adapter?(:PostgreSQLAdapter)
end
def test_establishes_connection_to_postgresql_database
- ActiveRecord::Base.expects(:establish_connection).with(
- "adapter" => "postgresql",
- "database" => "postgres",
- "schema_search_path" => "public"
- )
-
- ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ ActiveRecord::Base.stub(:connection, @connection) do
+ assert_called_with(
+ ActiveRecord::Base,
+ :establish_connection,
+ [
+ [
+ "adapter" => "postgresql",
+ "database" => "postgres",
+ "schema_search_path" => "public"
+ ],
+ [
+ "adapter" => "postgresql",
+ "database" => "my-app-db"
+ ]
+ ]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
+ end
end
def test_creates_database_with_default_encoding
- @connection.expects(:create_database).
- with("my-app-db", @configuration.merge("encoding" => "utf8"))
-
- ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ with_stubbed_connection_establish_connection do
+ assert_called_with(
+ @connection,
+ :create_database,
+ ["my-app-db", @configuration.merge("encoding" => "utf8")]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
+ end
end
def test_creates_database_with_given_encoding
- @connection.expects(:create_database).
- with("my-app-db", @configuration.merge("encoding" => "latin"))
-
- ActiveRecord::Tasks::DatabaseTasks.create @configuration.
- merge("encoding" => "latin")
+ with_stubbed_connection_establish_connection do
+ assert_called_with(
+ @connection,
+ :create_database,
+ ["my-app-db", @configuration.merge("encoding" => "latin")]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration.
+ merge("encoding" => "latin")
+ end
+ end
end
def test_creates_database_with_given_collation_and_ctype
- @connection.expects(:create_database).
- with("my-app-db", @configuration.merge("encoding" => "utf8", "collation" => "ja_JP.UTF8", "ctype" => "ja_JP.UTF8"))
-
- ActiveRecord::Tasks::DatabaseTasks.create @configuration.
- merge("collation" => "ja_JP.UTF8", "ctype" => "ja_JP.UTF8")
+ with_stubbed_connection_establish_connection do
+ assert_called_with(
+ @connection,
+ :create_database,
+ [
+ "my-app-db",
+ @configuration.merge(
+ "encoding" => "utf8",
+ "collation" => "ja_JP.UTF8",
+ "ctype" => "ja_JP.UTF8"
+ )
+ ]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration.
+ merge("collation" => "ja_JP.UTF8", "ctype" => "ja_JP.UTF8")
+ end
+ end
end
def test_establishes_connection_to_new_database
- ActiveRecord::Base.expects(:establish_connection).with(@configuration)
-
- ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ ActiveRecord::Base.stub(:connection, @connection) do
+ assert_called_with(
+ ActiveRecord::Base,
+ :establish_connection,
+ [
+ [
+ "adapter" => "postgresql",
+ "database" => "postgres",
+ "schema_search_path" => "public"
+ ],
+ [
+ @configuration
+ ]
+ ]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
+ end
end
def test_db_create_with_error_prints_message
- ActiveRecord::Base.stubs(:establish_connection).raises(Exception)
-
- $stderr.stubs(:puts).returns(true)
- $stderr.expects(:puts).
- with("Couldn't create database for #{@configuration.inspect}")
-
- assert_raises(Exception) { ActiveRecord::Tasks::DatabaseTasks.create @configuration }
+ ActiveRecord::Base.stub(:connection, @connection) do
+ ActiveRecord::Base.stub(:establish_connection, -> * { raise Exception }) do
+ assert_raises(Exception) { ActiveRecord::Tasks::DatabaseTasks.create @configuration }
+ assert_match "Couldn't create '#{@configuration['database']}' database. Please check your configuration.", $stderr.string
+ end
+ end
end
def test_when_database_created_successfully_outputs_info_to_stdout
- ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ with_stubbed_connection_establish_connection do
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
- assert_equal "Created database 'my-app-db'\n", $stdout.string
+ assert_equal "Created database 'my-app-db'\n", $stdout.string
+ end
end
def test_create_when_database_exists_outputs_info_to_stderr
- ActiveRecord::Base.connection.stubs(:create_database).raises(
- ActiveRecord::Tasks::DatabaseAlreadyExists
- )
+ with_stubbed_connection_establish_connection do
+ ActiveRecord::Base.connection.stub(
+ :create_database,
+ proc { raise ActiveRecord::Tasks::DatabaseAlreadyExists }
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+
+ assert_equal "Database 'my-app-db' already exists\n", $stderr.string
+ end
+ end
+ end
- ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ private
- assert_equal "Database 'my-app-db' already exists\n", $stderr.string
- end
+ def with_stubbed_connection_establish_connection
+ ActiveRecord::Base.stub(:connection, @connection) do
+ ActiveRecord::Base.stub(:establish_connection, nil) do
+ yield
+ end
+ end
+ end
end
class PostgreSQLDBDropTest < ActiveRecord::TestCase
def setup
- @connection = stub(drop_database: true)
+ @connection = Class.new { def drop_database(*); end }.new
@configuration = {
"adapter" => "postgresql",
"database" => "my-app-db"
}
-
- ActiveRecord::Base.stubs(:connection).returns(@connection)
- ActiveRecord::Base.stubs(:establish_connection).returns(true)
-
$stdout, @original_stdout = StringIO.new, $stdout
$stderr, @original_stderr = StringIO.new, $stderr
end
@@ -108,132 +165,221 @@ if current_adapter?(:PostgreSQLAdapter)
end
def test_establishes_connection_to_postgresql_database
- ActiveRecord::Base.expects(:establish_connection).with(
- "adapter" => "postgresql",
- "database" => "postgres",
- "schema_search_path" => "public"
- )
-
- ActiveRecord::Tasks::DatabaseTasks.drop @configuration
+ ActiveRecord::Base.stub(:connection, @connection) do
+ assert_called_with(
+ ActiveRecord::Base,
+ :establish_connection,
+ [
+ "adapter" => "postgresql",
+ "database" => "postgres",
+ "schema_search_path" => "public"
+ ]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.drop @configuration
+ end
+ end
end
def test_drops_database
- @connection.expects(:drop_database).with("my-app-db")
-
- ActiveRecord::Tasks::DatabaseTasks.drop @configuration
+ with_stubbed_connection_establish_connection do
+ assert_called_with(
+ @connection,
+ :drop_database,
+ ["my-app-db"]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.drop @configuration
+ end
+ end
end
def test_when_database_dropped_successfully_outputs_info_to_stdout
- ActiveRecord::Tasks::DatabaseTasks.drop @configuration
+ with_stubbed_connection_establish_connection do
+ ActiveRecord::Tasks::DatabaseTasks.drop @configuration
- assert_equal "Dropped database 'my-app-db'\n", $stdout.string
+ assert_equal "Dropped database 'my-app-db'\n", $stdout.string
+ end
end
+
+ private
+
+ def with_stubbed_connection_establish_connection
+ ActiveRecord::Base.stub(:connection, @connection) do
+ ActiveRecord::Base.stub(:establish_connection, nil) do
+ yield
+ end
+ end
+ end
end
class PostgreSQLPurgeTest < ActiveRecord::TestCase
def setup
- @connection = stub(create_database: true, drop_database: true)
+ @connection = Class.new do
+ def create_database(*); end
+ def drop_database(*); end
+ end.new
@configuration = {
"adapter" => "postgresql",
"database" => "my-app-db"
}
-
- ActiveRecord::Base.stubs(:connection).returns(@connection)
- ActiveRecord::Base.stubs(:clear_active_connections!).returns(true)
- ActiveRecord::Base.stubs(:establish_connection).returns(true)
end
def test_clears_active_connections
- ActiveRecord::Base.expects(:clear_active_connections!)
-
- ActiveRecord::Tasks::DatabaseTasks.purge @configuration
+ with_stubbed_connection do
+ ActiveRecord::Base.stub(:establish_connection, nil) do
+ assert_called(ActiveRecord::Base, :clear_active_connections!) do
+ ActiveRecord::Tasks::DatabaseTasks.purge @configuration
+ end
+ end
+ end
end
def test_establishes_connection_to_postgresql_database
- ActiveRecord::Base.expects(:establish_connection).with(
- "adapter" => "postgresql",
- "database" => "postgres",
- "schema_search_path" => "public"
- )
-
- ActiveRecord::Tasks::DatabaseTasks.purge @configuration
+ with_stubbed_connection do
+ assert_called_with(
+ ActiveRecord::Base,
+ :establish_connection,
+ [
+ [
+ "adapter" => "postgresql",
+ "database" => "postgres",
+ "schema_search_path" => "public"
+ ],
+ [
+ "adapter" => "postgresql",
+ "database" => "my-app-db"
+ ]
+ ]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.purge @configuration
+ end
+ end
end
def test_drops_database
- @connection.expects(:drop_database).with("my-app-db")
-
- ActiveRecord::Tasks::DatabaseTasks.purge @configuration
+ with_stubbed_connection do
+ ActiveRecord::Base.stub(:establish_connection, nil) do
+ assert_called_with(@connection, :drop_database, ["my-app-db"]) do
+ ActiveRecord::Tasks::DatabaseTasks.purge @configuration
+ end
+ end
+ end
end
def test_creates_database
- @connection.expects(:create_database).
- with("my-app-db", @configuration.merge("encoding" => "utf8"))
-
- ActiveRecord::Tasks::DatabaseTasks.purge @configuration
+ with_stubbed_connection do
+ ActiveRecord::Base.stub(:establish_connection, nil) do
+ assert_called_with(
+ @connection,
+ :create_database,
+ ["my-app-db", @configuration.merge("encoding" => "utf8")]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.purge @configuration
+ end
+ end
+ end
end
def test_establishes_connection
- ActiveRecord::Base.expects(:establish_connection).with(@configuration)
-
- ActiveRecord::Tasks::DatabaseTasks.purge @configuration
+ with_stubbed_connection do
+ assert_called_with(
+ ActiveRecord::Base,
+ :establish_connection,
+ [
+ [
+ "adapter" => "postgresql",
+ "database" => "postgres",
+ "schema_search_path" => "public"
+ ],
+ [
+ @configuration
+ ]
+ ]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.purge @configuration
+ end
+ end
end
+
+ private
+
+ def with_stubbed_connection
+ ActiveRecord::Base.stub(:connection, @connection) do
+ yield
+ end
+ end
end
class PostgreSQLDBCharsetTest < ActiveRecord::TestCase
def setup
- @connection = stub(create_database: true)
+ @connection = Class.new do
+ def create_database(*); end
+ def encoding; end
+ end.new
@configuration = {
"adapter" => "postgresql",
"database" => "my-app-db"
}
-
- ActiveRecord::Base.stubs(:connection).returns(@connection)
- ActiveRecord::Base.stubs(:establish_connection).returns(true)
end
def test_db_retrieves_charset
- @connection.expects(:encoding)
- ActiveRecord::Tasks::DatabaseTasks.charset @configuration
+ ActiveRecord::Base.stub(:connection, @connection) do
+ assert_called(@connection, :encoding) do
+ ActiveRecord::Tasks::DatabaseTasks.charset @configuration
+ end
+ end
end
end
class PostgreSQLDBCollationTest < ActiveRecord::TestCase
def setup
- @connection = stub(create_database: true)
+ @connection = Class.new { def collation; end }.new
@configuration = {
"adapter" => "postgresql",
"database" => "my-app-db"
}
-
- ActiveRecord::Base.stubs(:connection).returns(@connection)
- ActiveRecord::Base.stubs(:establish_connection).returns(true)
end
def test_db_retrieves_collation
- @connection.expects(:collation)
- ActiveRecord::Tasks::DatabaseTasks.collation @configuration
+ ActiveRecord::Base.stub(:connection, @connection) do
+ assert_called(@connection, :collation) do
+ ActiveRecord::Tasks::DatabaseTasks.collation @configuration
+ end
+ end
end
end
class PostgreSQLStructureDumpTest < ActiveRecord::TestCase
def setup
- @connection = stub(structure_dump: true)
@configuration = {
"adapter" => "postgresql",
"database" => "my-app-db"
}
- @filename = "awesome-file.sql"
+ @filename = "/tmp/awesome-file.sql"
+ FileUtils.touch(@filename)
+ end
- ActiveRecord::Base.stubs(:connection).returns(@connection)
- ActiveRecord::Base.stubs(:establish_connection).returns(true)
- Kernel.stubs(:system)
- File.stubs(:open)
+ def teardown
+ FileUtils.rm_f(@filename)
end
def test_structure_dump
- Kernel.expects(:system).with("pg_dump", "-s", "-x", "-O", "-f", @filename, "my-app-db").returns(true)
+ assert_called_with(
+ Kernel,
+ :system,
+ ["pg_dump", "-s", "-x", "-O", "-f", @filename, "my-app-db"],
+ returns: true
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, @filename)
+ end
+ end
+
+ def test_structure_dump_header_comments_removed
+ Kernel.stub(:system, true) do
+ File.write(@filename, "-- header comment\n\n-- more header comment\n statement \n-- lower comment\n")
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, @filename)
- ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, @filename)
+ assert_equal [" statement \n", "-- lower comment\n"], File.readlines(@filename).first(2)
+ end
end
def test_structure_dump_with_extra_flags
@@ -246,29 +392,76 @@ if current_adapter?(:PostgreSQLAdapter)
end
end
+ def test_structure_dump_with_ignore_tables
+ assert_called(
+ ActiveRecord::SchemaDumper,
+ :ignore_tables,
+ returns: ["foo", "bar"]
+ ) do
+ assert_called_with(
+ Kernel,
+ :system,
+ ["pg_dump", "-s", "-x", "-O", "-f", @filename, "-T", "foo", "-T", "bar", "my-app-db"],
+ returns: true
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, @filename)
+ end
+ end
+ end
+
def test_structure_dump_with_schema_search_path
@configuration["schema_search_path"] = "foo,bar"
- Kernel.expects(:system).with("pg_dump", "-s", "-x", "-O", "-f", @filename, "--schema=foo", "--schema=bar", "my-app-db").returns(true)
-
- ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, @filename)
+ assert_called_with(
+ Kernel,
+ :system,
+ ["pg_dump", "-s", "-x", "-O", "-f", @filename, "--schema=foo", "--schema=bar", "my-app-db"],
+ returns: true
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, @filename)
+ end
end
def test_structure_dump_with_schema_search_path_and_dump_schemas_all
@configuration["schema_search_path"] = "foo,bar"
- Kernel.expects(:system).with("pg_dump", "-s", "-x", "-O", "-f", @filename, "my-app-db").returns(true)
-
- with_dump_schemas(:all) do
- ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, @filename)
+ assert_called_with(
+ Kernel,
+ :system,
+ ["pg_dump", "-s", "-x", "-O", "-f", @filename, "my-app-db"],
+ returns: true
+ ) do
+ with_dump_schemas(:all) do
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, @filename)
+ end
end
end
def test_structure_dump_with_dump_schemas_string
- Kernel.expects(:system).with("pg_dump", "-s", "-x", "-O", "-f", @filename, "--schema=foo", "--schema=bar", "my-app-db").returns(true)
+ assert_called_with(
+ Kernel,
+ :system,
+ ["pg_dump", "-s", "-x", "-O", "-f", @filename, "--schema=foo", "--schema=bar", "my-app-db"],
+ returns: true
+ ) do
+ with_dump_schemas("foo,bar") do
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, @filename)
+ end
+ end
+ end
- with_dump_schemas("foo,bar") do
- ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, @filename)
+ def test_structure_dump_execution_fails
+ filename = "awesome-file.sql"
+ assert_called_with(
+ Kernel,
+ :system,
+ ["pg_dump", "-s", "-x", "-O", "-f", filename, "my-app-db"],
+ returns: nil
+ ) do
+ e = assert_raise(RuntimeError) do
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, filename)
+ end
+ assert_match("failed to execute:", e.message)
end
end
@@ -292,22 +485,22 @@ if current_adapter?(:PostgreSQLAdapter)
class PostgreSQLStructureLoadTest < ActiveRecord::TestCase
def setup
- @connection = stub
@configuration = {
"adapter" => "postgresql",
"database" => "my-app-db"
}
-
- ActiveRecord::Base.stubs(:connection).returns(@connection)
- ActiveRecord::Base.stubs(:establish_connection).returns(true)
- Kernel.stubs(:system)
end
def test_structure_load
filename = "awesome-file.sql"
- Kernel.expects(:system).with("psql", "-v", "ON_ERROR_STOP=1", "-q", "-f", filename, @configuration["database"]).returns(true)
-
- ActiveRecord::Tasks::DatabaseTasks.structure_load(@configuration, filename)
+ assert_called_with(
+ Kernel,
+ :system,
+ ["psql", "-v", "ON_ERROR_STOP=1", "-q", "-f", filename, @configuration["database"]],
+ returns: true
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.structure_load(@configuration, filename)
+ end
end
def test_structure_load_with_extra_flags
@@ -323,9 +516,14 @@ if current_adapter?(:PostgreSQLAdapter)
def test_structure_load_accepts_path_with_spaces
filename = "awesome file.sql"
- Kernel.expects(:system).with("psql", "-v", "ON_ERROR_STOP=1", "-q", "-f", filename, @configuration["database"]).returns(true)
-
- ActiveRecord::Tasks::DatabaseTasks.structure_load(@configuration, filename)
+ assert_called_with(
+ Kernel,
+ :system,
+ ["psql", "-v", "ON_ERROR_STOP=1", "-q", "-f", filename, @configuration["database"]],
+ returns: true
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.structure_load(@configuration, filename)
+ end
end
private
diff --git a/activerecord/test/cases/tasks/sqlite_rake_test.rb b/activerecord/test/cases/tasks/sqlite_rake_test.rb
index 0d917f3f6c..c1092b97c1 100644
--- a/activerecord/test/cases/tasks/sqlite_rake_test.rb
+++ b/activerecord/test/cases/tasks/sqlite_rake_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "active_record/tasks/database_tasks"
require "pathname"
@@ -7,16 +9,10 @@ if current_adapter?(:SQLite3Adapter)
class SqliteDBCreateTest < ActiveRecord::TestCase
def setup
@database = "db_create.sqlite3"
- @connection = stub :connection
@configuration = {
"adapter" => "sqlite3",
"database" => @database
}
-
- File.stubs(:exist?).returns(false)
- ActiveRecord::Base.stubs(:connection).returns(@connection)
- ActiveRecord::Base.stubs(:establish_connection).returns(true)
-
$stdout, @original_stdout = StringIO.new, $stdout
$stderr, @original_stderr = StringIO.new, $stderr
end
@@ -26,63 +22,62 @@ if current_adapter?(:SQLite3Adapter)
end
def test_db_checks_database_exists
- File.expects(:exist?).with(@database).returns(false)
-
- ActiveRecord::Tasks::DatabaseTasks.create @configuration, "/rails/root"
+ ActiveRecord::Base.stub(:establish_connection, nil) do
+ assert_called_with(File, :exist?, [@database], returns: false) do
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration, "/rails/root"
+ end
+ end
end
def test_when_db_created_successfully_outputs_info_to_stdout
- ActiveRecord::Tasks::DatabaseTasks.create @configuration, "/rails/root"
+ ActiveRecord::Base.stub(:establish_connection, nil) do
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration, "/rails/root"
- assert_equal "Created database '#{@database}'\n", $stdout.string
+ assert_equal "Created database '#{@database}'\n", $stdout.string
+ end
end
def test_db_create_when_file_exists
- File.stubs(:exist?).returns(true)
-
- ActiveRecord::Tasks::DatabaseTasks.create @configuration, "/rails/root"
+ File.stub(:exist?, true) do
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration, "/rails/root"
- assert_equal "Database '#{@database}' already exists\n", $stderr.string
+ assert_equal "Database '#{@database}' already exists\n", $stderr.string
+ end
end
def test_db_create_with_file_does_nothing
- File.stubs(:exist?).returns(true)
- $stderr.stubs(:puts).returns(nil)
-
- ActiveRecord::Base.expects(:establish_connection).never
-
- ActiveRecord::Tasks::DatabaseTasks.create @configuration, "/rails/root"
+ File.stub(:exist?, true) do
+ assert_not_called(ActiveRecord::Base, :establish_connection) do
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration, "/rails/root"
+ end
+ end
end
def test_db_create_establishes_a_connection
- ActiveRecord::Base.expects(:establish_connection).with(@configuration)
-
- ActiveRecord::Tasks::DatabaseTasks.create @configuration, "/rails/root"
+ assert_called_with(ActiveRecord::Base, :establish_connection, [@configuration]) do
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration, "/rails/root"
+ end
end
def test_db_create_with_error_prints_message
- ActiveRecord::Base.stubs(:establish_connection).raises(Exception)
-
- $stderr.stubs(:puts).returns(true)
- $stderr.expects(:puts).
- with("Couldn't create database for #{@configuration.inspect}")
-
- assert_raises(Exception) { ActiveRecord::Tasks::DatabaseTasks.create @configuration, "/rails/root" }
+ ActiveRecord::Base.stub(:establish_connection, proc { raise Exception }) do
+ assert_raises(Exception) { ActiveRecord::Tasks::DatabaseTasks.create @configuration, "/rails/root" }
+ assert_match "Couldn't create '#{@configuration['database']}' database. Please check your configuration.", $stderr.string
+ end
end
end
class SqliteDBDropTest < ActiveRecord::TestCase
def setup
@database = "db_create.sqlite3"
- @path = stub(to_s: "/absolute/path", absolute?: true)
@configuration = {
"adapter" => "sqlite3",
"database" => @database
}
-
- Pathname.stubs(:new).returns(@path)
- File.stubs(:join).returns("/former/relative/path")
- FileUtils.stubs(:rm).returns(true)
+ @path = Class.new do
+ def to_s; "/absolute/path" end
+ def absolute?; true end
+ end.new
$stdout, @original_stdout = StringIO.new, $stdout
$stderr, @original_stderr = StringIO.new, $stderr
@@ -93,77 +88,76 @@ if current_adapter?(:SQLite3Adapter)
end
def test_creates_path_from_database
- Pathname.expects(:new).with(@database).returns(@path)
-
- ActiveRecord::Tasks::DatabaseTasks.drop @configuration, "/rails/root"
+ assert_called_with(Pathname, :new, [@database], returns: @path) do
+ ActiveRecord::Tasks::DatabaseTasks.drop @configuration, "/rails/root"
+ end
end
def test_removes_file_with_absolute_path
- File.stubs(:exist?).returns(true)
- @path.stubs(:absolute?).returns(true)
-
- FileUtils.expects(:rm).with("/absolute/path")
-
- ActiveRecord::Tasks::DatabaseTasks.drop @configuration, "/rails/root"
+ Pathname.stub(:new, @path) do
+ assert_called_with(FileUtils, :rm, ["/absolute/path"]) do
+ ActiveRecord::Tasks::DatabaseTasks.drop @configuration, "/rails/root"
+ end
+ end
end
def test_generates_absolute_path_with_given_root
- @path.stubs(:absolute?).returns(false)
-
- File.expects(:join).with("/rails/root", @path).
- returns("/former/relative/path")
-
- ActiveRecord::Tasks::DatabaseTasks.drop @configuration, "/rails/root"
+ Pathname.stub(:new, @path) do
+ @path.stub(:absolute?, false) do
+ assert_called_with(File, :join, ["/rails/root", @path],
+ returns: "/former/relative/path"
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.drop @configuration, "/rails/root"
+ end
+ end
+ end
end
def test_removes_file_with_relative_path
- File.stubs(:exist?).returns(true)
- @path.stubs(:absolute?).returns(false)
-
- FileUtils.expects(:rm).with("/former/relative/path")
-
- ActiveRecord::Tasks::DatabaseTasks.drop @configuration, "/rails/root"
+ File.stub(:join, "/former/relative/path") do
+ @path.stub(:absolute?, false) do
+ assert_called_with(FileUtils, :rm, ["/former/relative/path"]) do
+ ActiveRecord::Tasks::DatabaseTasks.drop @configuration, "/rails/root"
+ end
+ end
+ end
end
def test_when_db_dropped_successfully_outputs_info_to_stdout
- ActiveRecord::Tasks::DatabaseTasks.drop @configuration, "/rails/root"
+ FileUtils.stub(:rm, nil) do
+ ActiveRecord::Tasks::DatabaseTasks.drop @configuration, "/rails/root"
- assert_equal "Dropped database '#{@database}'\n", $stdout.string
+ assert_equal "Dropped database '#{@database}'\n", $stdout.string
+ end
end
end
class SqliteDBCharsetTest < ActiveRecord::TestCase
def setup
@database = "db_create.sqlite3"
- @connection = stub :connection
+ @connection = Class.new { def encoding; end }.new
@configuration = {
"adapter" => "sqlite3",
"database" => @database
}
-
- File.stubs(:exist?).returns(false)
- ActiveRecord::Base.stubs(:connection).returns(@connection)
- ActiveRecord::Base.stubs(:establish_connection).returns(true)
end
def test_db_retrieves_charset
- @connection.expects(:encoding)
- ActiveRecord::Tasks::DatabaseTasks.charset @configuration, "/rails/root"
+ ActiveRecord::Base.stub(:connection, @connection) do
+ assert_called(@connection, :encoding) do
+ ActiveRecord::Tasks::DatabaseTasks.charset @configuration, "/rails/root"
+ end
+ end
end
end
class SqliteDBCollationTest < ActiveRecord::TestCase
def setup
@database = "db_create.sqlite3"
- @connection = stub :connection
@configuration = {
"adapter" => "sqlite3",
"database" => @database
}
-
- File.stubs(:exist?).returns(false)
- ActiveRecord::Base.stubs(:connection).returns(@connection)
- ActiveRecord::Base.stubs(:establish_connection).returns(true)
end
def test_db_retrieves_collation
@@ -180,6 +174,9 @@ if current_adapter?(:SQLite3Adapter)
"adapter" => "sqlite3",
"database" => @database
}
+
+ `sqlite3 #{@database} 'CREATE TABLE bar(id INTEGER)'`
+ `sqlite3 #{@database} 'CREATE TABLE foo(id INTEGER)'`
end
def test_structure_dump
@@ -189,10 +186,57 @@ if current_adapter?(:SQLite3Adapter)
ActiveRecord::Tasks::DatabaseTasks.structure_dump @configuration, filename, "/rails/root"
assert File.exist?(dbfile)
assert File.exist?(filename)
+ assert_match(/CREATE TABLE foo/, File.read(filename))
+ assert_match(/CREATE TABLE bar/, File.read(filename))
+ ensure
+ FileUtils.rm_f(filename)
+ FileUtils.rm_f(dbfile)
+ end
+
+ def test_structure_dump_with_ignore_tables
+ dbfile = @database
+ filename = "awesome-file.sql"
+ assert_called(ActiveRecord::SchemaDumper, :ignore_tables, returns: ["foo"]) do
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, filename, "/rails/root")
+ end
+ assert File.exist?(dbfile)
+ assert File.exist?(filename)
+ assert_match(/bar/, File.read(filename))
+ assert_no_match(/foo/, File.read(filename))
ensure
FileUtils.rm_f(filename)
FileUtils.rm_f(dbfile)
end
+
+ def test_structure_dump_execution_fails
+ dbfile = @database
+ filename = "awesome-file.sql"
+ assert_called_with(
+ Kernel,
+ :system,
+ ["sqlite3", "--noop", "db_create.sqlite3", ".schema", out: "awesome-file.sql"],
+ returns: nil
+ ) do
+ e = assert_raise(RuntimeError) do
+ with_structure_dump_flags(["--noop"]) do
+ quietly { ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, filename, "/rails/root") }
+ end
+ end
+ assert_match("failed to execute:", e.message)
+ end
+ ensure
+ FileUtils.rm_f(filename)
+ FileUtils.rm_f(dbfile)
+ end
+
+ private
+ def with_structure_dump_flags(flags)
+ old = ActiveRecord::Tasks::DatabaseTasks.structure_dump_flags
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump_flags = flags
+ yield
+ ensure
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump_flags = old
+ end
end
class SqliteStructureLoadTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/test_case.rb b/activerecord/test/cases/test_case.rb
index 31b11c19f7..40947767f3 100644
--- a/activerecord/test/cases/test_case.rb
+++ b/activerecord/test/cases/test_case.rb
@@ -1,4 +1,6 @@
-require "active_support/test_case"
+# frozen_string_literal: true
+
+require "active_support"
require "active_support/testing/autorun"
require "active_support/testing/method_call_assertions"
require "active_support/testing/stream"
@@ -29,6 +31,7 @@ module ActiveRecord
end
def capture_sql
+ ActiveRecord::Base.connection.materialize_transactions
SQLCounter.clear_log
yield
SQLCounter.log_all.dup
@@ -46,6 +49,7 @@ module ActiveRecord
def assert_queries(num = 1, options = {})
ignore_none = options.fetch(:ignore_none) { num == :any }
+ ActiveRecord::Base.connection.materialize_transactions
SQLCounter.clear_log
x = yield
the_log = ignore_none ? SQLCounter.log_all : SQLCounter.log
@@ -75,6 +79,10 @@ module ActiveRecord
model.reset_column_information
model.column_names.include?(column_name.to_s)
end
+
+ def frozen_error_class
+ Object.const_defined?(:FrozenError) ? FrozenError : RuntimeError
+ end
end
class PostgreSQLTestCase < TestCase
@@ -108,9 +116,9 @@ module ActiveRecord
# FIXME: this needs to be refactored so specific database can add their own
# ignored SQL, or better yet, use a different notification for the queries
# instead examining the SQL content.
- oracle_ignored = [/^select .*nextval/i, /^SAVEPOINT/, /^ROLLBACK TO/, /^\s*select .* from all_triggers/im, /^\s*select .* from all_constraints/im, /^\s*select .* from all_tab_cols/im]
+ oracle_ignored = [/^select .*nextval/i, /^SAVEPOINT/, /^ROLLBACK TO/, /^\s*select .* from all_triggers/im, /^\s*select .* from all_constraints/im, /^\s*select .* from all_tab_cols/im, /^\s*select .* from all_sequences/im]
mysql_ignored = [/^SHOW FULL TABLES/i, /^SHOW FULL FIELDS/, /^SHOW CREATE TABLE /i, /^SHOW VARIABLES /, /^\s*SELECT (?:column_name|table_name)\b.*\bFROM information_schema\.(?:key_column_usage|tables)\b/im]
- postgresql_ignored = [/^\s*select\b.*\bfrom\b.*pg_namespace\b/im, /^\s*select tablename\b.*from pg_tables\b/im, /^\s*select\b.*\battname\b.*\bfrom\b.*\bpg_attribute\b/im, /^SHOW search_path/i]
+ postgresql_ignored = [/^\s*select\b.*\bfrom\b.*pg_namespace\b/im, /^\s*select tablename\b.*from pg_tables\b/im, /^\s*select\b.*\battname\b.*\bfrom\b.*\bpg_attribute\b/im, /^SHOW search_path/i, /^\s*SELECT\b.*::regtype::oid\b/im]
sqlite3_ignored = [/^\s*SELECT name\b.*\bFROM sqlite_master/im, /^\s*SELECT sql\b.*\bFROM sqlite_master/im]
[oracle_ignored, mysql_ignored, postgresql_ignored, sqlite3_ignored].each do |db_ignored_sql|
diff --git a/activerecord/test/cases/test_fixtures_test.rb b/activerecord/test/cases/test_fixtures_test.rb
index 58d3bea3a2..4411410eda 100644
--- a/activerecord/test/cases/test_fixtures_test.rb
+++ b/activerecord/test/cases/test_fixtures_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
class TestFixturesTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/time_precision_test.rb b/activerecord/test/cases/time_precision_test.rb
index 09c585167e..086500de38 100644
--- a/activerecord/test/cases/time_precision_test.rb
+++ b/activerecord/test/cases/time_precision_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "support/schema_dumping_helper"
@@ -25,6 +27,24 @@ if subsecond_precision_supported?
assert_equal 6, Foo.columns_hash["finish"].precision
end
+ def test_time_precision_is_truncated_on_assignment
+ @connection.create_table(:foos, force: true)
+ @connection.add_column :foos, :start, :time, precision: 0
+ @connection.add_column :foos, :finish, :time, precision: 6
+
+ time = ::Time.now.change(nsec: 123456789)
+ foo = Foo.new(start: time, finish: time)
+
+ assert_equal 0, foo.start.nsec
+ assert_equal 123456000, foo.finish.nsec
+
+ foo.save!
+ foo.reload
+
+ assert_equal 0, foo.start.nsec
+ assert_equal 123456000, foo.finish.nsec
+ end
+
def test_passing_precision_to_time_does_not_set_limit
@connection.create_table(:foos, force: true) do |t|
t.time :start, precision: 3
diff --git a/activerecord/test/cases/timestamp_test.rb b/activerecord/test/cases/timestamp_test.rb
index 39b40e3411..75ecd6fc40 100644
--- a/activerecord/test/cases/timestamp_test.rb
+++ b/activerecord/test/cases/timestamp_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "support/ddl_helper"
require "models/developer"
@@ -88,12 +90,22 @@ class TimestampTest < ActiveRecord::TestCase
@developer.touch(:created_at)
end
- assert !@developer.created_at_changed? , "created_at should not be changed"
- assert !@developer.changed?, "record should not be changed"
+ assert_not @developer.created_at_changed?, "created_at should not be changed"
+ assert_not @developer.changed?, "record should not be changed"
assert_not_equal previously_created_at, @developer.created_at
assert_not_equal @previously_updated_at, @developer.updated_at
end
+ def test_touching_update_at_attribute_as_symbol_updates_timestamp
+ travel(1.second) do
+ @developer.touch(:updated_at)
+ end
+
+ assert_not @developer.updated_at_changed?
+ assert_not @developer.changed?
+ assert_not_equal @previously_updated_at, @developer.updated_at
+ end
+
def test_touching_an_attribute_updates_it
task = Task.first
previous_value = task.ending
@@ -137,13 +149,13 @@ class TimestampTest < ActiveRecord::TestCase
def test_touching_a_no_touching_object
Developer.no_touching do
- assert @developer.no_touching?
- assert !@owner.no_touching?
+ assert_predicate @developer, :no_touching?
+ assert_not_predicate @owner, :no_touching?
@developer.touch
end
- assert !@developer.no_touching?
- assert !@owner.no_touching?
+ assert_not_predicate @developer, :no_touching?
+ assert_not_predicate @owner, :no_touching?
assert_equal @previously_updated_at, @developer.updated_at
end
@@ -160,26 +172,26 @@ class TimestampTest < ActiveRecord::TestCase
def test_global_no_touching
ActiveRecord::Base.no_touching do
- assert @developer.no_touching?
- assert @owner.no_touching?
+ assert_predicate @developer, :no_touching?
+ assert_predicate @owner, :no_touching?
@developer.touch
end
- assert !@developer.no_touching?
- assert !@owner.no_touching?
+ assert_not_predicate @developer, :no_touching?
+ assert_not_predicate @owner, :no_touching?
assert_equal @previously_updated_at, @developer.updated_at
end
def test_no_touching_threadsafe
Thread.new do
Developer.no_touching do
- assert @developer.no_touching?
+ assert_predicate @developer, :no_touching?
sleep(1)
end
end
- assert !@developer.no_touching?
+ assert_not_predicate @developer, :no_touching?
end
def test_no_touching_with_callbacks
@@ -235,7 +247,7 @@ class TimestampTest < ActiveRecord::TestCase
pet = Pet.new(owner: klass.new)
pet.save!
- assert pet.owner.new_record?
+ assert_predicate pet.owner, :new_record?
end
def test_saving_a_record_with_a_belongs_to_that_specifies_touching_a_specific_attribute_the_parent_should_update_that_attribute
@@ -444,17 +456,6 @@ class TimestampTest < ActiveRecord::TestCase
toy = Toy.first
assert_equal ["created_at", "updated_at"], toy.send(:all_timestamp_attributes_in_model)
end
-
- def test_index_is_created_for_both_timestamps
- ActiveRecord::Base.connection.create_table(:foos, force: true) do |t|
- t.timestamps null: true, index: true
- end
-
- indexes = ActiveRecord::Base.connection.indexes("foos")
- assert_equal ["created_at", "updated_at"], indexes.flat_map(&:columns).sort
- ensure
- ActiveRecord::Base.connection.drop_table(:foos)
- end
end
class TimestampsWithoutTransactionTest < ActiveRecord::TestCase
@@ -473,4 +474,15 @@ class TimestampsWithoutTransactionTest < ActiveRecord::TestCase
assert_nil post.updated_at
end
end
+
+ def test_index_is_created_for_both_timestamps
+ ActiveRecord::Base.connection.create_table(:foos, force: true) do |t|
+ t.timestamps null: true, index: true
+ end
+
+ indexes = ActiveRecord::Base.connection.indexes("foos")
+ assert_equal ["created_at", "updated_at"], indexes.flat_map(&:columns).sort
+ ensure
+ ActiveRecord::Base.connection.drop_table(:foos)
+ end
end
diff --git a/activerecord/test/cases/touch_later_test.rb b/activerecord/test/cases/touch_later_test.rb
index d1e8c649d9..925a4609a2 100644
--- a/activerecord/test/cases/touch_later_test.rb
+++ b/activerecord/test/cases/touch_later_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/invoice"
require "models/line_item"
@@ -11,7 +13,7 @@ class TouchLaterTest < ActiveRecord::TestCase
def test_touch_laster_raise_if_non_persisted
invoice = Invoice.new
Invoice.transaction do
- assert_not invoice.persisted?
+ assert_not_predicate invoice, :persisted?
assert_raises(ActiveRecord::ActiveRecordError) do
invoice.touch_later
end
@@ -21,7 +23,7 @@ class TouchLaterTest < ActiveRecord::TestCase
def test_touch_later_dont_set_dirty_attributes
invoice = Invoice.create!
invoice.touch_later
- assert_not invoice.changed?
+ assert_not_predicate invoice, :changed?
end
def test_touch_later_respects_no_touching_policy
diff --git a/activerecord/test/cases/transaction_callbacks_test.rb b/activerecord/test/cases/transaction_callbacks_test.rb
index 391bbe8877..c0be45eee7 100644
--- a/activerecord/test/cases/transaction_callbacks_test.rb
+++ b/activerecord/test/cases/transaction_callbacks_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/owner"
require "models/pet"
@@ -137,6 +139,23 @@ class TransactionCallbacksTest < ActiveRecord::TestCase
assert_equal [], reply.history
end
+ def test_only_call_after_commit_on_destroy_after_transaction_commits_for_destroyed_new_record
+ new_record = TopicWithCallbacks.new(title: "New topic", written_on: Date.today)
+ add_transaction_execution_blocks new_record
+
+ new_record.destroy
+ assert_equal [:commit_on_destroy], new_record.history
+ end
+
+ def test_save_in_after_create_commit_wont_invoke_extra_after_create_commit
+ new_record = TopicWithCallbacks.new(title: "New topic", written_on: Date.today)
+ add_transaction_execution_blocks new_record
+ new_record.after_commit_block(:create) { |r| r.save! }
+
+ new_record.save!
+ assert_equal [:commit_on_create, :commit_on_update], new_record.history
+ end
+
def test_only_call_after_commit_on_create_and_doesnt_leaky
r = ReplyWithCallbacks.new(content: "foo")
r.save_on_after_create = true
@@ -156,13 +175,13 @@ class TransactionCallbacksTest < ActiveRecord::TestCase
def test_only_call_after_commit_on_top_level_transactions
@first.after_commit_block { |r| r.history << :after_commit }
- assert @first.history.empty?
+ assert_empty @first.history
@first.transaction do
@first.transaction(requires_new: true) do
@first.touch
end
- assert @first.history.empty?
+ assert_empty @first.history
end
assert_equal [:after_commit], @first.history
end
@@ -365,6 +384,26 @@ class TransactionCallbacksTest < ActiveRecord::TestCase
assert_match(/:on conditions for after_commit and after_rollback callbacks have to be one of \[:create, :destroy, :update\]/, e.message)
end
+ def test_after_commit_chain_not_called_on_errors
+ record_1 = TopicWithCallbacks.create!
+ record_2 = TopicWithCallbacks.create!
+ record_3 = TopicWithCallbacks.create!
+ callbacks = []
+ record_1.after_commit_block { raise }
+ record_2.after_commit_block { callbacks << record_2.id }
+ record_3.after_commit_block { callbacks << record_3.id }
+ begin
+ TopicWithCallbacks.transaction do
+ record_1.save!
+ record_2.save!
+ record_3.save!
+ end
+ rescue
+ # From record_1.after_commit
+ end
+ assert_equal [], callbacks
+ end
+
def test_saving_a_record_with_a_belongs_to_that_specifies_touching_the_parent_should_call_callbacks_on_the_parent_object
pet = Pet.first
owner = pet.owner
@@ -392,6 +431,28 @@ class TransactionCallbacksTest < ActiveRecord::TestCase
end
end
+class TransactionAfterCommitCallbacksWithOptimisticLockingTest < ActiveRecord::TestCase
+ class PersonWithCallbacks < ActiveRecord::Base
+ self.table_name = :people
+
+ after_create_commit { |record| record.history << :commit_on_create }
+ after_update_commit { |record| record.history << :commit_on_update }
+ after_destroy_commit { |record| record.history << :commit_on_destroy }
+
+ def history
+ @history ||= []
+ end
+ end
+
+ def test_after_commit_callbacks_with_optimistic_locking
+ person = PersonWithCallbacks.create!(first_name: "first name")
+ person.update!(first_name: "another name")
+ person.destroy
+
+ assert_equal [:commit_on_create, :commit_on_update, :commit_on_destroy], person.history
+ end
+end
+
class CallbacksOnMultipleActionsTest < ActiveRecord::TestCase
self.use_transactional_tests = false
@@ -516,7 +577,7 @@ class TransactionEnrollmentCallbacksTest < ActiveRecord::TestCase
@topic.content = "foo"
@topic.save!
end
- assert @topic.history.empty?
+ assert_empty @topic.history
end
def test_commit_run_transactions_callbacks_with_explicit_enrollment
@@ -536,7 +597,7 @@ class TransactionEnrollmentCallbacksTest < ActiveRecord::TestCase
@topic.save!
raise ActiveRecord::Rollback
end
- assert @topic.history.empty?
+ assert_empty @topic.history
end
def test_rollback_run_transactions_callbacks_with_explicit_enrollment
@@ -551,3 +612,43 @@ class TransactionEnrollmentCallbacksTest < ActiveRecord::TestCase
assert_equal [:rollback], @topic.history
end
end
+
+class CallbacksOnActionAndConditionTest < ActiveRecord::TestCase
+ self.use_transactional_tests = false
+
+ class TopicWithCallbacksOnActionAndCondition < ActiveRecord::Base
+ self.table_name = :topics
+
+ after_commit(on: [:create, :update], if: :run_callback?) { |record| record.history << :create_or_update }
+
+ def clear_history
+ @history = []
+ end
+
+ def history
+ @history ||= []
+ end
+
+ def run_callback?
+ self.history << :run_callback?
+ true
+ end
+
+ attr_accessor :save_before_commit_history, :update_title
+ end
+
+ def test_callback_on_action_with_condition
+ topic = TopicWithCallbacksOnActionAndCondition.new
+ topic.save
+ assert_equal [:run_callback?, :create_or_update], topic.history
+
+ topic.clear_history
+ topic.approved = true
+ topic.save
+ assert_equal [:run_callback?, :create_or_update], topic.history
+
+ topic.clear_history
+ topic.destroy
+ assert_equal [], topic.history
+ end
+end
diff --git a/activerecord/test/cases/transaction_isolation_test.rb b/activerecord/test/cases/transaction_isolation_test.rb
index 58abfadaf4..9663955f1f 100644
--- a/activerecord/test/cases/transaction_isolation_test.rb
+++ b/activerecord/test/cases/transaction_isolation_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
unless ActiveRecord::Base.connection.supports_transaction_isolation?
@@ -9,13 +11,11 @@ unless ActiveRecord::Base.connection.supports_transaction_isolation?
test "setting the isolation level raises an error" do
assert_raises(ActiveRecord::TransactionIsolationError) do
- Tag.transaction(isolation: :serializable) {}
+ Tag.transaction(isolation: :serializable) { Tag.connection.materialize_transactions }
end
end
end
-end
-
-if ActiveRecord::Base.connection.supports_transaction_isolation?
+else
class TransactionIsolationTest < ActiveRecord::TestCase
self.use_transactional_tests = false
diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb
index 111495c481..b13cf88c00 100644
--- a/activerecord/test/cases/transactions_test.rb
+++ b/activerecord/test/cases/transactions_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/topic"
require "models/reply"
@@ -10,7 +12,7 @@ require "models/movie"
class TransactionTest < ActiveRecord::TestCase
self.use_transactional_tests = false
- fixtures :topics, :developers, :authors, :posts
+ fixtures :topics, :developers, :authors, :author_addresses, :posts
def setup
@first, @second = Topic.find(1, 2).sort_by(&:id)
@@ -18,22 +20,22 @@ class TransactionTest < ActiveRecord::TestCase
def test_persisted_in_a_model_with_custom_primary_key_after_failed_save
movie = Movie.create
- assert !movie.persisted?
+ assert_not_predicate movie, :persisted?
end
def test_raise_after_destroy
- assert_not @first.frozen?
+ assert_not_predicate @first, :frozen?
assert_raises(RuntimeError) {
Topic.transaction do
@first.destroy
- assert @first.frozen?
+ assert_predicate @first, :frozen?
raise
end
}
assert @first.reload
- assert_not @first.frozen?
+ assert_not_predicate @first, :frozen?
end
def test_successful
@@ -45,7 +47,7 @@ class TransactionTest < ActiveRecord::TestCase
end
assert Topic.find(1).approved?, "First should have been approved"
- assert !Topic.find(2).approved?, "Second should have been unapproved"
+ assert_not Topic.find(2).approved?, "Second should have been unapproved"
end
def transaction_with_return
@@ -78,7 +80,7 @@ class TransactionTest < ActiveRecord::TestCase
assert committed
assert Topic.find(1).approved?, "First should have been approved"
- assert !Topic.find(2).approved?, "Second should have been unapproved"
+ assert_not Topic.find(2).approved?, "Second should have been unapproved"
ensure
Topic.connection.class_eval do
remove_method :commit_db_transaction
@@ -119,7 +121,7 @@ class TransactionTest < ActiveRecord::TestCase
end
assert Topic.find(1).approved?, "First should have been approved"
- assert !Topic.find(2).approved?, "Second should have been unapproved"
+ assert_not Topic.find(2).approved?, "Second should have been unapproved"
end
def test_failing_on_exception
@@ -136,9 +138,9 @@ class TransactionTest < ActiveRecord::TestCase
end
assert @first.approved?, "First should still be changed in the objects"
- assert !@second.approved?, "Second should still be changed in the objects"
+ assert_not @second.approved?, "Second should still be changed in the objects"
- assert !Topic.find(1).approved?, "First shouldn't have been approved"
+ assert_not Topic.find(1).approved?, "First shouldn't have been approved"
assert Topic.find(2).approved?, "Second should still be approved"
end
@@ -150,20 +152,20 @@ class TransactionTest < ActiveRecord::TestCase
@first.approved = true
e = assert_raises(RuntimeError) { @first.save }
assert_equal "Make the transaction rollback", e.message
- assert !Topic.find(1).approved?
+ assert_not_predicate Topic.find(1), :approved?
end
def test_rolling_back_in_a_callback_rollbacks_before_save
def @first.before_save_for_transaction
raise ActiveRecord::Rollback
end
- assert !@first.approved
+ assert_not @first.approved
Topic.transaction do
@first.approved = true
@first.save!
end
- assert !Topic.find(@first.id).approved?, "Should not commit the approved flag"
+ assert_not Topic.find(@first.id).approved?, "Should not commit the approved flag"
end
def test_raising_exception_in_nested_transaction_restore_state_in_save
@@ -184,7 +186,7 @@ class TransactionTest < ActiveRecord::TestCase
author = Author.create! name: "foo"
author.name = nil
assert_not author.save
- assert_not author.new_record?
+ assert_not_predicate author, :new_record?
end
def test_update_should_rollback_on_failure
@@ -192,7 +194,7 @@ class TransactionTest < ActiveRecord::TestCase
posts_count = author.posts.size
assert posts_count > 0
status = author.update(name: nil, post_ids: [])
- assert !status
+ assert_not status
assert_equal posts_count, author.posts.reload.size
end
@@ -210,7 +212,7 @@ class TransactionTest < ActiveRecord::TestCase
add_cancelling_before_destroy_with_db_side_effect_to_topic @first
nbooks_before_destroy = Book.count
status = @first.destroy
- assert !status
+ assert_not status
@first.reload
assert_equal nbooks_before_destroy, Book.count
end
@@ -222,7 +224,7 @@ class TransactionTest < ActiveRecord::TestCase
original_author_name = @first.author_name
@first.author_name += "_this_should_not_end_up_in_the_db"
status = @first.save
- assert !status
+ assert_not status
assert_equal original_author_name, @first.reload.author_name
assert_equal nbooks_before_save, Book.count
end
@@ -286,7 +288,19 @@ class TransactionTest < ActiveRecord::TestCase
}
new_topic = topic.create(title: "A new topic")
- assert !new_topic.persisted?, "The topic should not be persisted"
+ assert_not new_topic.persisted?, "The topic should not be persisted"
+ assert_nil new_topic.id, "The topic should not have an ID"
+ end
+
+ def test_callback_rollback_in_create_with_rollback_exception
+ topic = Class.new(Topic) {
+ def after_create_for_transaction
+ raise ActiveRecord::Rollback
+ end
+ }
+
+ new_topic = topic.create(title: "A new topic")
+ assert_not new_topic.persisted?, "The topic should not be persisted"
assert_nil new_topic.id, "The topic should not have an ID"
end
@@ -301,7 +315,77 @@ class TransactionTest < ActiveRecord::TestCase
end
assert Topic.find(1).approved?, "First should have been approved"
- assert !Topic.find(2).approved?, "Second should have been unapproved"
+ assert_not Topic.find(2).approved?, "Second should have been unapproved"
+ end
+
+ def test_nested_transaction_with_new_transaction_applies_parent_state_on_rollback
+ topic_one = Topic.new(title: "A new topic")
+ topic_two = Topic.new(title: "Another new topic")
+
+ Topic.transaction do
+ topic_one.save
+
+ Topic.transaction(requires_new: true) do
+ topic_two.save
+
+ assert_predicate topic_one, :persisted?
+ assert_predicate topic_two, :persisted?
+ end
+
+ raise ActiveRecord::Rollback
+ end
+
+ assert_not_predicate topic_one, :persisted?
+ assert_not_predicate topic_two, :persisted?
+ end
+
+ def test_nested_transaction_without_new_transaction_applies_parent_state_on_rollback
+ topic_one = Topic.new(title: "A new topic")
+ topic_two = Topic.new(title: "Another new topic")
+
+ Topic.transaction do
+ topic_one.save
+
+ Topic.transaction do
+ topic_two.save
+
+ assert_predicate topic_one, :persisted?
+ assert_predicate topic_two, :persisted?
+ end
+
+ raise ActiveRecord::Rollback
+ end
+
+ assert_not_predicate topic_one, :persisted?
+ assert_not_predicate topic_two, :persisted?
+ end
+
+ def test_double_nested_transaction_applies_parent_state_on_rollback
+ topic_one = Topic.new(title: "A new topic")
+ topic_two = Topic.new(title: "Another new topic")
+ topic_three = Topic.new(title: "Another new topic of course")
+
+ Topic.transaction do
+ topic_one.save
+
+ Topic.transaction do
+ topic_two.save
+
+ Topic.transaction do
+ topic_three.save
+ end
+ end
+
+ assert_predicate topic_one, :persisted?
+ assert_predicate topic_two, :persisted?
+ assert_predicate topic_three, :persisted?
+
+ raise ActiveRecord::Rollback
+ end
+
+ assert_not_predicate topic_one, :persisted?
+ assert_not_predicate topic_two, :persisted?
+ assert_not_predicate topic_three, :persisted?
end
def test_manually_rolling_back_a_transaction
@@ -315,9 +399,9 @@ class TransactionTest < ActiveRecord::TestCase
end
assert @first.approved?, "First should still be changed in the objects"
- assert !@second.approved?, "Second should still be changed in the objects"
+ assert_not @second.approved?, "Second should still be changed in the objects"
- assert !Topic.find(1).approved?, "First shouldn't have been approved"
+ assert_not Topic.find(1).approved?, "First shouldn't have been approved"
assert Topic.find(2).approved?, "Second should still be approved"
end
@@ -345,8 +429,8 @@ class TransactionTest < ActiveRecord::TestCase
end
end
- assert @first.reload.approved?
- assert !@second.reload.approved?
+ assert_predicate @first.reload, :approved?
+ assert_not_predicate @second.reload, :approved?
end if Topic.connection.supports_savepoints?
def test_force_savepoint_on_instance
@@ -366,8 +450,8 @@ class TransactionTest < ActiveRecord::TestCase
end
end
- assert @first.reload.approved?
- assert !@second.reload.approved?
+ assert_predicate @first.reload, :approved?
+ assert_not_predicate @second.reload, :approved?
end if Topic.connection.supports_savepoints?
def test_no_savepoint_in_nested_transaction_without_force
@@ -387,8 +471,8 @@ class TransactionTest < ActiveRecord::TestCase
end
end
- assert !@first.reload.approved?
- assert !@second.reload.approved?
+ assert_not_predicate @first.reload, :approved?
+ assert_not_predicate @second.reload, :approved?
end if Topic.connection.supports_savepoints?
def test_many_savepoints
@@ -444,12 +528,12 @@ class TransactionTest < ActiveRecord::TestCase
@first.approved = false
@first.save!
Topic.connection.rollback_to_savepoint("first")
- assert @first.reload.approved?
+ assert_predicate @first.reload, :approved?
@first.approved = false
@first.save!
Topic.connection.release_savepoint("first")
- assert_not @first.reload.approved?
+ assert_not_predicate @first.reload, :approved?
end
end if Topic.connection.supports_savepoints?
@@ -489,10 +573,9 @@ class TransactionTest < ActiveRecord::TestCase
assert_called(Topic.connection, :begin_db_transaction) do
Topic.connection.stub(:commit_db_transaction, -> { raise("OH NOES") }) do
assert_called(Topic.connection, :rollback_db_transaction) do
-
e = assert_raise RuntimeError do
Topic.transaction do
- # do nothing
+ Topic.connection.materialize_transactions
end
end
assert_equal "OH NOES", e.message
@@ -504,12 +587,12 @@ class TransactionTest < ActiveRecord::TestCase
def test_rollback_when_saving_a_frozen_record
topic = Topic.new(title: "test")
topic.freeze
- e = assert_raise(RuntimeError) { topic.save }
+ e = assert_raise(frozen_error_class) { topic.save }
# Not good enough, but we can't do much
# about it since there is no specific error
# for frozen objects.
assert_match(/frozen/i, e.message)
- assert !topic.persisted?, "not persisted"
+ assert_not topic.persisted?, "not persisted"
assert_nil topic.id
assert topic.frozen?, "not frozen"
end
@@ -536,9 +619,9 @@ class TransactionTest < ActiveRecord::TestCase
thread.join
assert @first.approved?, "First should still be changed in the objects"
- assert !@second.approved?, "Second should still be changed in the objects"
+ assert_not @second.approved?, "Second should still be changed in the objects"
- assert !Topic.find(1).approved?, "First shouldn't have been approved"
+ assert_not Topic.find(1).approved?, "First shouldn't have been approved"
assert Topic.find(2).approved?, "Second should still be approved"
end
@@ -569,15 +652,15 @@ class TransactionTest < ActiveRecord::TestCase
raise ActiveRecord::Rollback
end
- assert !topic_1.persisted?, "not persisted"
+ assert_not topic_1.persisted?, "not persisted"
assert_nil topic_1.id
- assert !topic_2.persisted?, "not persisted"
+ assert_not topic_2.persisted?, "not persisted"
assert_nil topic_2.id
- assert !topic_3.persisted?, "not persisted"
+ assert_not topic_3.persisted?, "not persisted"
assert_nil topic_3.id
assert @first.persisted?, "persisted"
assert_not_nil @first.id
- assert !@second.destroyed?, "not destroyed"
+ assert_not @second.destroyed?, "not destroyed"
end
def test_restore_frozen_state_after_double_destroy
@@ -591,8 +674,130 @@ class TransactionTest < ActiveRecord::TestCase
raise ActiveRecord::Rollback
end
- assert_not reply.frozen?
- assert_not topic.frozen?
+ assert_not_predicate reply, :frozen?
+ assert_not_predicate topic, :frozen?
+ end
+
+ def test_restore_new_record_after_double_save
+ topic = Topic.new
+
+ Topic.transaction do
+ topic.save!
+ topic.save!
+ raise ActiveRecord::Rollback
+ end
+
+ assert_nil topic.id
+ assert_predicate topic, :new_record?
+ end
+
+ def test_dont_restore_new_record_in_subsequent_transaction
+ topic = Topic.new
+
+ Topic.transaction do
+ topic.save!
+ topic.save!
+ end
+
+ Topic.transaction do
+ topic.save!
+ raise ActiveRecord::Rollback
+ end
+
+ assert_predicate topic, :persisted?
+ assert_not_predicate topic, :new_record?
+ end
+
+ def test_restore_id_after_rollback
+ topic = Topic.new
+
+ Topic.transaction do
+ topic.save!
+ raise ActiveRecord::Rollback
+ end
+
+ assert_nil topic.id
+ end
+
+ def test_restore_custom_primary_key_after_rollback
+ movie = Movie.new(name: "foo")
+
+ Movie.transaction do
+ movie.save!
+ raise ActiveRecord::Rollback
+ end
+
+ assert_nil movie.movieid
+ end
+
+ def test_assign_id_after_rollback
+ topic = Topic.create!
+
+ Topic.transaction do
+ topic.save!
+ raise ActiveRecord::Rollback
+ end
+
+ topic.id = nil
+ assert_nil topic.id
+ end
+
+ def test_assign_custom_primary_key_after_rollback
+ movie = Movie.create!(name: "foo")
+
+ Movie.transaction do
+ movie.save!
+ raise ActiveRecord::Rollback
+ end
+
+ movie.movieid = nil
+ assert_nil movie.movieid
+ end
+
+ def test_read_attribute_after_rollback
+ topic = Topic.new
+
+ Topic.transaction do
+ topic.save!
+ raise ActiveRecord::Rollback
+ end
+
+ assert_nil topic.read_attribute(:id)
+ end
+
+ def test_read_attribute_with_custom_primary_key_after_rollback
+ movie = Movie.new(name: "foo")
+
+ Movie.transaction do
+ movie.save!
+ raise ActiveRecord::Rollback
+ end
+
+ assert_nil movie.read_attribute(:movieid)
+ end
+
+ def test_write_attribute_after_rollback
+ topic = Topic.create!
+
+ Topic.transaction do
+ topic.save!
+ raise ActiveRecord::Rollback
+ end
+
+ topic.write_attribute(:id, nil)
+ assert_nil topic.id
+ end
+
+ def test_write_attribute_with_custom_primary_key_after_rollback
+ movie = Movie.create!(name: "foo")
+
+ Movie.transaction do
+ movie.save!
+ raise ActiveRecord::Rollback
+ end
+
+ movie.write_attribute(:movieid, nil)
+ assert_nil movie.movieid
end
def test_rollback_of_frozen_records
@@ -655,28 +860,66 @@ class TransactionTest < ActiveRecord::TestCase
connection = Topic.connection
transaction = ActiveRecord::ConnectionAdapters::TransactionManager.new(connection).begin_transaction
- assert transaction.open?
- assert !transaction.state.rolledback?
- assert !transaction.state.committed?
+ assert_predicate transaction, :open?
+ assert_not_predicate transaction.state, :rolledback?
+ assert_not_predicate transaction.state, :committed?
transaction.rollback
- assert transaction.state.rolledback?
- assert !transaction.state.committed?
+ assert_predicate transaction.state, :rolledback?
+ assert_not_predicate transaction.state, :committed?
end
def test_transactions_state_from_commit
connection = Topic.connection
transaction = ActiveRecord::ConnectionAdapters::TransactionManager.new(connection).begin_transaction
- assert transaction.open?
- assert !transaction.state.rolledback?
- assert !transaction.state.committed?
+ assert_predicate transaction, :open?
+ assert_not_predicate transaction.state, :rolledback?
+ assert_not_predicate transaction.state, :committed?
+
+ transaction.commit
+
+ assert_not_predicate transaction.state, :rolledback?
+ assert_predicate transaction.state, :committed?
+ end
+
+ def test_set_state_method_is_deprecated
+ connection = Topic.connection
+ transaction = ActiveRecord::ConnectionAdapters::TransactionManager.new(connection).begin_transaction
+
+ transaction.commit
+
+ assert_deprecated do
+ transaction.state.set_state(:rolledback)
+ end
+ end
+
+ def test_mark_transaction_state_as_committed
+ connection = Topic.connection
+ transaction = ActiveRecord::ConnectionAdapters::TransactionManager.new(connection).begin_transaction
+
+ transaction.rollback
+
+ assert_equal :committed, transaction.state.commit!
+ end
+
+ def test_mark_transaction_state_as_rolledback
+ connection = Topic.connection
+ transaction = ActiveRecord::ConnectionAdapters::TransactionManager.new(connection).begin_transaction
transaction.commit
- assert !transaction.state.rolledback?
- assert transaction.state.committed?
+ assert_equal :rolledback, transaction.state.rollback!
+ end
+
+ def test_mark_transaction_state_as_nil
+ connection = Topic.connection
+ transaction = ActiveRecord::ConnectionAdapters::TransactionManager.new(connection).begin_transaction
+
+ transaction.commit
+
+ assert_nil transaction.state.nullify!
end
def test_transaction_rollback_with_primarykeyless_tables
@@ -700,6 +943,76 @@ class TransactionTest < ActiveRecord::TestCase
connection.drop_table "transaction_without_primary_keys", if_exists: true
end
+ def test_empty_transaction_is_not_materialized
+ assert_no_queries do
+ Topic.transaction {}
+ end
+ end
+
+ def test_unprepared_statement_materializes_transaction
+ assert_sql(/BEGIN/i, /COMMIT/i) do
+ Topic.transaction { Topic.where("1=1").first }
+ end
+ end
+
+ if ActiveRecord::Base.connection.prepared_statements
+ def test_prepared_statement_materializes_transaction
+ Topic.first
+
+ assert_sql(/BEGIN/i, /COMMIT/i) do
+ Topic.transaction { Topic.first }
+ end
+ end
+ end
+
+ def test_savepoint_does_not_materialize_transaction
+ assert_no_queries do
+ Topic.transaction do
+ Topic.transaction(requires_new: true) {}
+ end
+ end
+ end
+
+ def test_raising_does_not_materialize_transaction
+ assert_raise(RuntimeError) do
+ assert_no_queries do
+ Topic.transaction { raise }
+ end
+ end
+ end
+
+ def test_accessing_raw_connection_materializes_transaction
+ assert_sql(/BEGIN/i, /COMMIT/i) do
+ Topic.transaction { Topic.connection.raw_connection }
+ end
+ end
+
+ def test_accessing_raw_connection_disables_lazy_transactions
+ Topic.connection.raw_connection
+
+ assert_sql(/BEGIN/i, /COMMIT/i) do
+ Topic.transaction {}
+ end
+ end
+
+ def test_checking_in_connection_reenables_lazy_transactions
+ connection = Topic.connection_pool.checkout
+ connection.raw_connection
+ Topic.connection_pool.checkin connection
+
+ assert_no_queries do
+ connection.transaction {}
+ end
+ end
+
+ def test_transactions_can_be_manually_materialized
+ assert_sql(/BEGIN/i, /COMMIT/i) do
+ Topic.transaction do
+ Topic.connection.materialize_transactions
+ end
+ end
+ end
+
private
%w(validation save destroy).each do |filter|
@@ -727,7 +1040,7 @@ class TransactionsWithTransactionalFixturesTest < ActiveRecord::TestCase
raise
end
rescue
- assert !@first.reload.approved?
+ assert_not_predicate @first.reload, :approved?
end
end
@@ -748,31 +1061,29 @@ class TransactionsWithTransactionalFixturesTest < ActiveRecord::TestCase
end
end
- assert !@first.reload.approved?
+ assert_not_predicate @first.reload, :approved?
end
end if Topic.connection.supports_savepoints?
-if current_adapter?(:PostgreSQLAdapter)
+if ActiveRecord::Base.connection.supports_transaction_isolation?
class ConcurrentTransactionTest < TransactionTest
# This will cause transactions to overlap and fail unless they are performed on
# separate database connections.
- unless in_memory_db?
- def test_transaction_per_thread
- threads = 3.times.map do
- Thread.new do
- Topic.transaction do
- topic = Topic.find(1)
- topic.approved = !topic.approved?
- assert topic.save!
- topic.approved = !topic.approved?
- assert topic.save!
- end
- Topic.connection.close
+ def test_transaction_per_thread
+ threads = 3.times.map do
+ Thread.new do
+ Topic.transaction do
+ topic = Topic.find(1)
+ topic.approved = !topic.approved?
+ assert topic.save!
+ topic.approved = !topic.approved?
+ assert topic.save!
end
+ Topic.connection.close
end
-
- threads.each(&:join)
end
+
+ threads.each(&:join)
end
# Test for dirty reads among simultaneous transactions.
diff --git a/activerecord/test/cases/type/adapter_specific_registry_test.rb b/activerecord/test/cases/type/adapter_specific_registry_test.rb
index 8b836b4793..b58bdd5549 100644
--- a/activerecord/test/cases/type/adapter_specific_registry_test.rb
+++ b/activerecord/test/cases/type/adapter_specific_registry_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
module ActiveRecord
diff --git a/activerecord/test/cases/type/date_time_test.rb b/activerecord/test/cases/type/date_time_test.rb
index 6848619ece..c9558e25b5 100644
--- a/activerecord/test/cases/type/date_time_test.rb
+++ b/activerecord/test/cases/type/date_time_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/task"
diff --git a/activerecord/test/cases/type/integer_test.rb b/activerecord/test/cases/type/integer_test.rb
index 368b6d7199..15d1a675a1 100644
--- a/activerecord/test/cases/type/integer_test.rb
+++ b/activerecord/test/cases/type/integer_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/company"
diff --git a/activerecord/test/cases/type/string_test.rb b/activerecord/test/cases/type/string_test.rb
index a95da864fa..9e7810a6a5 100644
--- a/activerecord/test/cases/type/string_test.rb
+++ b/activerecord/test/cases/type/string_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
module ActiveRecord
@@ -7,16 +9,16 @@ module ActiveRecord
klass.table_name = "authors"
author = klass.create!(name: "Sean")
- assert_not author.changed?
+ assert_not_predicate author, :changed?
author.name << " Griffin"
- assert author.name_changed?
+ assert_predicate author, :name_changed?
author.save!
author.reload
assert_equal "Sean Griffin", author.name
- assert_not author.changed?
+ assert_not_predicate author, :changed?
end
end
end
diff --git a/activerecord/test/cases/type/type_map_test.rb b/activerecord/test/cases/type/type_map_test.rb
index 2959d36466..f3699c11a2 100644
--- a/activerecord/test/cases/type/type_map_test.rb
+++ b/activerecord/test/cases/type/type_map_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
module ActiveRecord
diff --git a/activerecord/test/cases/type/unsigned_integer_test.rb b/activerecord/test/cases/type/unsigned_integer_test.rb
index 1cd4dbc2c5..dd05cf3fff 100644
--- a/activerecord/test/cases/type/unsigned_integer_test.rb
+++ b/activerecord/test/cases/type/unsigned_integer_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
module ActiveRecord
diff --git a/activerecord/test/cases/type_test.rb b/activerecord/test/cases/type_test.rb
index d45a9b3141..93ae563c8b 100644
--- a/activerecord/test/cases/type_test.rb
+++ b/activerecord/test/cases/type_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
class TypeTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/types_test.rb b/activerecord/test/cases/types_test.rb
index 11476ea0ef..3f7fb0a604 100644
--- a/activerecord/test/cases/types_test.rb
+++ b/activerecord/test/cases/types_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
module ActiveRecord
diff --git a/activerecord/test/cases/unconnected_test.rb b/activerecord/test/cases/unconnected_test.rb
index b210584644..9eefc32745 100644
--- a/activerecord/test/cases/unconnected_test.rb
+++ b/activerecord/test/cases/unconnected_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
class TestRecord < ActiveRecord::Base
@@ -28,6 +30,6 @@ class TestUnconnectedAdapter < ActiveRecord::TestCase
end
def test_underlying_adapter_no_longer_active
- assert !@underlying.active?, "Removed adapter should no longer be active"
+ assert_not @underlying.active?, "Removed adapter should no longer be active"
end
end
diff --git a/activerecord/test/cases/unsafe_raw_sql_test.rb b/activerecord/test/cases/unsafe_raw_sql_test.rb
new file mode 100644
index 0000000000..d5d8f2a09a
--- /dev/null
+++ b/activerecord/test/cases/unsafe_raw_sql_test.rb
@@ -0,0 +1,319 @@
+# frozen_string_literal: true
+
+require "cases/helper"
+require "models/post"
+require "models/comment"
+
+class UnsafeRawSqlTest < ActiveRecord::TestCase
+ fixtures :posts, :comments
+
+ test "order: allows string column name" do
+ ids_expected = Post.order(Arel.sql("title")).pluck(:id)
+
+ ids_depr = with_unsafe_raw_sql_deprecated { Post.order("title").pluck(:id) }
+ ids_disabled = with_unsafe_raw_sql_disabled { Post.order("title").pluck(:id) }
+
+ assert_equal ids_expected, ids_depr
+ assert_equal ids_expected, ids_disabled
+ end
+
+ test "order: allows symbol column name" do
+ ids_expected = Post.order(Arel.sql("title")).pluck(:id)
+
+ ids_depr = with_unsafe_raw_sql_deprecated { Post.order(:title).pluck(:id) }
+ ids_disabled = with_unsafe_raw_sql_disabled { Post.order(:title).pluck(:id) }
+
+ assert_equal ids_expected, ids_depr
+ assert_equal ids_expected, ids_disabled
+ end
+
+ test "order: allows downcase symbol direction" do
+ ids_expected = Post.order(Arel.sql("title") => Arel.sql("asc")).pluck(:id)
+
+ ids_depr = with_unsafe_raw_sql_deprecated { Post.order(title: :asc).pluck(:id) }
+ ids_disabled = with_unsafe_raw_sql_disabled { Post.order(title: :asc).pluck(:id) }
+
+ assert_equal ids_expected, ids_depr
+ assert_equal ids_expected, ids_disabled
+ end
+
+ test "order: allows upcase symbol direction" do
+ ids_expected = Post.order(Arel.sql("title") => Arel.sql("ASC")).pluck(:id)
+
+ ids_depr = with_unsafe_raw_sql_deprecated { Post.order(title: :ASC).pluck(:id) }
+ ids_disabled = with_unsafe_raw_sql_disabled { Post.order(title: :ASC).pluck(:id) }
+
+ assert_equal ids_expected, ids_depr
+ assert_equal ids_expected, ids_disabled
+ end
+
+ test "order: allows string direction" do
+ ids_expected = Post.order(Arel.sql("title") => Arel.sql("asc")).pluck(:id)
+
+ ids_depr = with_unsafe_raw_sql_deprecated { Post.order(title: "asc").pluck(:id) }
+ ids_disabled = with_unsafe_raw_sql_disabled { Post.order(title: "asc").pluck(:id) }
+
+ assert_equal ids_expected, ids_depr
+ assert_equal ids_expected, ids_disabled
+ end
+
+ test "order: allows multiple columns" do
+ ids_expected = Post.order(Arel.sql("author_id"), Arel.sql("title")).pluck(:id)
+
+ ids_depr = with_unsafe_raw_sql_deprecated { Post.order(:author_id, :title).pluck(:id) }
+ ids_disabled = with_unsafe_raw_sql_disabled { Post.order(:author_id, :title).pluck(:id) }
+
+ assert_equal ids_expected, ids_depr
+ assert_equal ids_expected, ids_disabled
+ end
+
+ test "order: allows mixed" do
+ ids_expected = Post.order(Arel.sql("author_id"), Arel.sql("title") => Arel.sql("asc")).pluck(:id)
+
+ ids_depr = with_unsafe_raw_sql_deprecated { Post.order(:author_id, title: :asc).pluck(:id) }
+ ids_disabled = with_unsafe_raw_sql_disabled { Post.order(:author_id, title: :asc).pluck(:id) }
+
+ assert_equal ids_expected, ids_depr
+ assert_equal ids_expected, ids_disabled
+ end
+
+ test "order: allows table and column name" do
+ ids_expected = Post.order(Arel.sql("title")).pluck(:id)
+
+ ids_depr = with_unsafe_raw_sql_deprecated { Post.order("posts.title").pluck(:id) }
+ ids_disabled = with_unsafe_raw_sql_disabled { Post.order("posts.title").pluck(:id) }
+
+ assert_equal ids_expected, ids_depr
+ assert_equal ids_expected, ids_disabled
+ end
+
+ test "order: allows column name and direction in string" do
+ ids_expected = Post.order(Arel.sql("title desc")).pluck(:id)
+
+ ids_depr = with_unsafe_raw_sql_deprecated { Post.order("title desc").pluck(:id) }
+ ids_disabled = with_unsafe_raw_sql_disabled { Post.order("title desc").pluck(:id) }
+
+ assert_equal ids_expected, ids_depr
+ assert_equal ids_expected, ids_disabled
+ end
+
+ test "order: allows table name, column name and direction in string" do
+ ids_expected = Post.order(Arel.sql("title desc")).pluck(:id)
+
+ ids_depr = with_unsafe_raw_sql_deprecated { Post.order("posts.title desc").pluck(:id) }
+ ids_disabled = with_unsafe_raw_sql_disabled { Post.order("posts.title desc").pluck(:id) }
+
+ assert_equal ids_expected, ids_depr
+ assert_equal ids_expected, ids_disabled
+ end
+
+ test "order: allows NULLS FIRST and NULLS LAST too" do
+ raise "precondition failed" if Post.count < 2
+
+ # Ensure there are NULL and non-NULL post types.
+ Post.first.update_column(:type, nil)
+ Post.last.update_column(:type, "Programming")
+
+ ["asc", "desc", ""].each do |direction|
+ %w(first last).each do |position|
+ ids_expected = Post.order(Arel.sql("type #{direction} nulls #{position}")).pluck(:id)
+
+ ids_depr = with_unsafe_raw_sql_deprecated { Post.order("type #{direction} nulls #{position}").pluck(:id) }
+ ids_disabled = with_unsafe_raw_sql_disabled { Post.order("type #{direction} nulls #{position}").pluck(:id) }
+
+ assert_equal ids_expected, ids_depr
+ assert_equal ids_expected, ids_disabled
+ end
+ end
+ end if current_adapter?(:PostgreSQLAdapter)
+
+ test "order: disallows invalid column name" do
+ with_unsafe_raw_sql_disabled do
+ assert_raises(ActiveRecord::UnknownAttributeReference) do
+ Post.order("len(title) asc").pluck(:id)
+ end
+ end
+ end
+
+ test "order: disallows invalid direction" do
+ with_unsafe_raw_sql_disabled do
+ assert_raises(ArgumentError) do
+ Post.order(title: :foo).pluck(:id)
+ end
+ end
+ end
+
+ test "order: disallows invalid column with direction" do
+ with_unsafe_raw_sql_disabled do
+ assert_raises(ActiveRecord::UnknownAttributeReference) do
+ Post.order("len(title)" => :asc).pluck(:id)
+ end
+ end
+ end
+
+ test "order: always allows Arel" do
+ ids_depr = with_unsafe_raw_sql_deprecated { Post.order(Arel.sql("length(title)")).pluck(:title) }
+ ids_disabled = with_unsafe_raw_sql_disabled { Post.order(Arel.sql("length(title)")).pluck(:title) }
+
+ assert_equal ids_depr, ids_disabled
+ end
+
+ test "order: allows Arel.sql with binds" do
+ ids_expected = Post.order(Arel.sql("REPLACE(title, 'misc', 'zzzz'), id")).pluck(:id)
+
+ ids_depr = with_unsafe_raw_sql_deprecated { Post.order([Arel.sql("REPLACE(title, ?, ?), id"), "misc", "zzzz"]).pluck(:id) }
+ ids_disabled = with_unsafe_raw_sql_disabled { Post.order([Arel.sql("REPLACE(title, ?, ?), id"), "misc", "zzzz"]).pluck(:id) }
+
+ assert_equal ids_expected, ids_depr
+ assert_equal ids_expected, ids_disabled
+ end
+
+ test "order: disallows invalid bind statement" do
+ with_unsafe_raw_sql_disabled do
+ assert_raises(ActiveRecord::UnknownAttributeReference) do
+ Post.order(["REPLACE(title, ?, ?), id", "misc", "zzzz"]).pluck(:id)
+ end
+ end
+ end
+
+ test "order: disallows invalid Array arguments" do
+ with_unsafe_raw_sql_disabled do
+ assert_raises(ActiveRecord::UnknownAttributeReference) do
+ Post.order(["author_id", "length(title)"]).pluck(:id)
+ end
+ end
+ end
+
+ test "order: allows valid Array arguments" do
+ ids_expected = Post.order(Arel.sql("author_id, length(title)")).pluck(:id)
+
+ ids_depr = with_unsafe_raw_sql_deprecated { Post.order(["author_id", Arel.sql("length(title)")]).pluck(:id) }
+ ids_disabled = with_unsafe_raw_sql_disabled { Post.order(["author_id", Arel.sql("length(title)")]).pluck(:id) }
+
+ assert_equal ids_expected, ids_depr
+ assert_equal ids_expected, ids_disabled
+ end
+
+ test "order: logs deprecation warning for unrecognized column" do
+ with_unsafe_raw_sql_deprecated do
+ assert_deprecated(/Dangerous query method/) do
+ Post.order("length(title)")
+ end
+ end
+ end
+
+ test "pluck: allows string column name" do
+ titles_expected = Post.pluck(Arel.sql("title"))
+
+ titles_depr = with_unsafe_raw_sql_deprecated { Post.pluck("title") }
+ titles_disabled = with_unsafe_raw_sql_disabled { Post.pluck("title") }
+
+ assert_equal titles_expected, titles_depr
+ assert_equal titles_expected, titles_disabled
+ end
+
+ test "pluck: allows symbol column name" do
+ titles_expected = Post.pluck(Arel.sql("title"))
+
+ titles_depr = with_unsafe_raw_sql_deprecated { Post.pluck(:title) }
+ titles_disabled = with_unsafe_raw_sql_disabled { Post.pluck(:title) }
+
+ assert_equal titles_expected, titles_depr
+ assert_equal titles_expected, titles_disabled
+ end
+
+ test "pluck: allows multiple column names" do
+ values_expected = Post.pluck(Arel.sql("title"), Arel.sql("id"))
+
+ values_depr = with_unsafe_raw_sql_deprecated { Post.pluck(:title, :id) }
+ values_disabled = with_unsafe_raw_sql_disabled { Post.pluck(:title, :id) }
+
+ assert_equal values_expected, values_depr
+ assert_equal values_expected, values_disabled
+ end
+
+ test "pluck: allows column names with includes" do
+ values_expected = Post.includes(:comments).pluck(Arel.sql("title"), Arel.sql("id"))
+
+ values_depr = with_unsafe_raw_sql_deprecated { Post.includes(:comments).pluck(:title, :id) }
+ values_disabled = with_unsafe_raw_sql_disabled { Post.includes(:comments).pluck(:title, :id) }
+
+ assert_equal values_expected, values_depr
+ assert_equal values_expected, values_disabled
+ end
+
+ test "pluck: allows auto-generated attributes" do
+ values_expected = Post.pluck(Arel.sql("tags_count"))
+
+ values_depr = with_unsafe_raw_sql_deprecated { Post.pluck(:tags_count) }
+ values_disabled = with_unsafe_raw_sql_disabled { Post.pluck(:tags_count) }
+
+ assert_equal values_expected, values_depr
+ assert_equal values_expected, values_disabled
+ end
+
+ test "pluck: allows table and column names" do
+ titles_expected = Post.pluck(Arel.sql("title"))
+
+ titles_depr = with_unsafe_raw_sql_deprecated { Post.pluck("posts.title") }
+ titles_disabled = with_unsafe_raw_sql_disabled { Post.pluck("posts.title") }
+
+ assert_equal titles_expected, titles_depr
+ assert_equal titles_expected, titles_disabled
+ end
+
+ test "pluck: disallows invalid column name" do
+ with_unsafe_raw_sql_disabled do
+ assert_raises(ActiveRecord::UnknownAttributeReference) do
+ Post.pluck("length(title)")
+ end
+ end
+ end
+
+ test "pluck: disallows invalid column name amongst valid names" do
+ with_unsafe_raw_sql_disabled do
+ assert_raises(ActiveRecord::UnknownAttributeReference) do
+ Post.pluck(:title, "length(title)")
+ end
+ end
+ end
+
+ test "pluck: disallows invalid column names with includes" do
+ with_unsafe_raw_sql_disabled do
+ assert_raises(ActiveRecord::UnknownAttributeReference) do
+ Post.includes(:comments).pluck(:title, "length(title)")
+ end
+ end
+ end
+
+ test "pluck: always allows Arel" do
+ values_depr = with_unsafe_raw_sql_deprecated { Post.includes(:comments).pluck(:title, Arel.sql("length(title)")) }
+ values_disabled = with_unsafe_raw_sql_disabled { Post.includes(:comments).pluck(:title, Arel.sql("length(title)")) }
+
+ assert_equal values_depr, values_disabled
+ end
+
+ test "pluck: logs deprecation warning" do
+ with_unsafe_raw_sql_deprecated do
+ assert_deprecated(/Dangerous query method/) do
+ Post.includes(:comments).pluck(:title, "length(title)")
+ end
+ end
+ end
+
+ def with_unsafe_raw_sql_disabled(&blk)
+ with_config(:disabled, &blk)
+ end
+
+ def with_unsafe_raw_sql_deprecated(&blk)
+ with_config(:deprecated, &blk)
+ end
+
+ def with_config(new_value, &blk)
+ old_value = ActiveRecord::Base.allow_unsafe_raw_sql
+ ActiveRecord::Base.allow_unsafe_raw_sql = new_value
+ blk.call
+ ensure
+ ActiveRecord::Base.allow_unsafe_raw_sql = old_value
+ end
+end
diff --git a/activerecord/test/cases/validations/absence_validation_test.rb b/activerecord/test/cases/validations/absence_validation_test.rb
index 870619e4e7..8235a54d8a 100644
--- a/activerecord/test/cases/validations/absence_validation_test.rb
+++ b/activerecord/test/cases/validations/absence_validation_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/face"
require "models/interest"
@@ -11,8 +13,8 @@ class AbsenceValidationTest < ActiveRecord::TestCase
validates_absence_of :name
end
- assert boy_klass.new.valid?
- assert_not boy_klass.new(name: "Alex").valid?
+ assert_predicate boy_klass.new, :valid?
+ assert_not_predicate boy_klass.new(name: "Alex"), :valid?
end
def test_has_one_marked_for_destruction
@@ -42,7 +44,7 @@ class AbsenceValidationTest < ActiveRecord::TestCase
assert_not boy.valid?, "should not be valid if has_many association is present"
i2.mark_for_destruction
- assert boy.valid?
+ assert_predicate boy, :valid?
end
def test_does_not_call_to_a_on_associations
@@ -63,11 +65,11 @@ class AbsenceValidationTest < ActiveRecord::TestCase
Interest.validates_absence_of(:token)
interest = Interest.create!(topic: "Thought Leadering")
- assert interest.valid?
+ assert_predicate interest, :valid?
interest.token = "tl"
- assert interest.invalid?
+ assert_predicate interest, :invalid?
end
end
end
diff --git a/activerecord/test/cases/validations/association_validation_test.rb b/activerecord/test/cases/validations/association_validation_test.rb
index f5ceb27d97..ce6d42b34b 100644
--- a/activerecord/test/cases/validations/association_validation_test.rb
+++ b/activerecord/test/cases/validations/association_validation_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/topic"
require "models/reply"
@@ -14,14 +16,14 @@ class AssociationValidationTest < ActiveRecord::TestCase
Reply.validates_presence_of(:content)
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[:replies].any?
+ assert_not_predicate t, :valid?
+ assert_predicate t.errors[:replies], :any?
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?
+ assert_predicate t, :valid?
end
def test_validates_associated_one
@@ -29,10 +31,10 @@ class AssociationValidationTest < ActiveRecord::TestCase
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[:topic].any?
+ assert_not_predicate r, :valid?
+ assert_predicate r.errors[:topic], :any?
r.topic.content = "non-empty"
- assert r.valid?
+ assert_predicate r, :valid?
end
def test_validates_associated_marked_for_destruction
@@ -40,9 +42,9 @@ class AssociationValidationTest < ActiveRecord::TestCase
Reply.validates_presence_of(:content)
t = Topic.new
t.replies << Reply.new
- assert t.invalid?
+ assert_predicate t, :invalid?
t.replies.first.mark_for_destruction
- assert t.valid?
+ assert_predicate t, :valid?
end
def test_validates_associated_without_marked_for_destruction
@@ -54,7 +56,7 @@ class AssociationValidationTest < ActiveRecord::TestCase
Topic.validates_associated(:replies)
t = Topic.new
t.define_singleton_method(:replies) { [reply.new] }
- assert t.valid?
+ assert_predicate t, :valid?
end
def test_validates_associated_with_custom_message_using_quotes
@@ -69,11 +71,11 @@ class AssociationValidationTest < ActiveRecord::TestCase
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[:topic].any?
+ assert_not_predicate r, :valid?
+ assert_predicate r.errors[:topic], :any?
r.topic = Topic.first
- assert r.valid?
+ assert_predicate r, :valid?
end
def test_validates_presence_of_belongs_to_association__parent_is_new_record
diff --git a/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb b/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb
index a57065ba75..703c24b340 100644
--- a/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb
+++ b/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/topic"
diff --git a/activerecord/test/cases/validations/i18n_validation_test.rb b/activerecord/test/cases/validations/i18n_validation_test.rb
index fd88a3ea67..b7c52ea18c 100644
--- a/activerecord/test/cases/validations/i18n_validation_test.rb
+++ b/activerecord/test/cases/validations/i18n_validation_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/topic"
require "models/reply"
diff --git a/activerecord/test/cases/validations/length_validation_test.rb b/activerecord/test/cases/validations/length_validation_test.rb
index ba45c6dcc1..1fbcdc271b 100644
--- a/activerecord/test/cases/validations/length_validation_test.rb
+++ b/activerecord/test/cases/validations/length_validation_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/owner"
require "models/pet"
@@ -15,48 +17,48 @@ class LengthValidationTest < ActiveRecord::TestCase
def test_validates_size_of_association
assert_nothing_raised { @owner.validates_size_of :pets, minimum: 1 }
o = @owner.new("name" => "nopets")
- assert !o.save
- assert o.errors[:pets].any?
+ assert_not o.save
+ assert_predicate o.errors[:pets], :any?
o.pets.build("name" => "apet")
- assert o.valid?
+ assert_predicate o, :valid?
end
def test_validates_size_of_association_using_within
assert_nothing_raised { @owner.validates_size_of :pets, within: 1..2 }
o = @owner.new("name" => "nopets")
- assert !o.save
- assert o.errors[:pets].any?
+ assert_not o.save
+ assert_predicate o.errors[:pets], :any?
o.pets.build("name" => "apet")
- assert o.valid?
+ assert_predicate o, :valid?
2.times { o.pets.build("name" => "apet") }
- assert !o.save
- assert o.errors[:pets].any?
+ assert_not o.save
+ assert_predicate o.errors[:pets], :any?
end
def test_validates_size_of_association_utf8
@owner.validates_size_of :pets, minimum: 1
o = @owner.new("name" => "あいうえおかきくけこ")
- assert !o.save
- assert o.errors[:pets].any?
+ assert_not o.save
+ assert_predicate o.errors[:pets], :any?
o.pets.build("name" => "あいうえおかきくけこ")
- assert o.valid?
+ assert_predicate o, :valid?
end
def test_validates_size_of_respects_records_marked_for_destruction
@owner.validates_size_of :pets, minimum: 1
owner = @owner.new
assert_not owner.save
- assert owner.errors[:pets].any?
+ assert_predicate owner.errors[:pets], :any?
pet = owner.pets.build
- assert owner.valid?
+ assert_predicate owner, :valid?
assert owner.save
pet_count = Pet.count
- assert_not owner.update_attributes pets_attributes: [ { _destroy: 1, id: pet.id } ]
- assert_not owner.valid?
- assert owner.errors[:pets].any?
+ assert_not owner.update pets_attributes: [ { _destroy: 1, id: pet.id } ]
+ assert_not_predicate owner, :valid?
+ assert_predicate owner.errors[:pets], :any?
assert_equal pet_count, Pet.count
end
@@ -68,11 +70,11 @@ class LengthValidationTest < ActiveRecord::TestCase
pet = Pet.create!(name: "Fancy Pants", nickname: "Fancy")
- assert pet.valid?
+ assert_predicate pet, :valid?
pet.nickname = ""
- assert pet.invalid?
+ assert_predicate pet, :invalid?
end
end
end
diff --git a/activerecord/test/cases/validations/presence_validation_test.rb b/activerecord/test/cases/validations/presence_validation_test.rb
index 13956e26ec..63c3f67da2 100644
--- a/activerecord/test/cases/validations/presence_validation_test.rb
+++ b/activerecord/test/cases/validations/presence_validation_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/man"
require "models/face"
@@ -13,10 +15,10 @@ class PresenceValidationTest < ActiveRecord::TestCase
def test_validates_presence_of_non_association
Boy.validates_presence_of(:name)
b = Boy.new
- assert b.invalid?
+ assert_predicate b, :invalid?
b.name = "Alex"
- assert b.valid?
+ assert_predicate b, :valid?
end
def test_validates_presence_of_has_one
@@ -31,23 +33,23 @@ class PresenceValidationTest < ActiveRecord::TestCase
b = Boy.new
f = Face.new
b.face = f
- assert b.valid?
+ assert_predicate b, :valid?
f.mark_for_destruction
- assert b.invalid?
+ assert_predicate b, :invalid?
end
def test_validates_presence_of_has_many_marked_for_destruction
Boy.validates_presence_of(:interests)
b = Boy.new
b.interests << [i1 = Interest.new, i2 = Interest.new]
- assert b.valid?
+ assert_predicate b, :valid?
i1.mark_for_destruction
- assert b.valid?
+ assert_predicate b, :valid?
i2.mark_for_destruction
- assert b.invalid?
+ assert_predicate b, :invalid?
end
def test_validates_presence_doesnt_convert_to_array
@@ -72,11 +74,11 @@ class PresenceValidationTest < ActiveRecord::TestCase
Interest.validates_presence_of(:abbreviation)
interest = Interest.create!(topic: "Thought Leadering", abbreviation: "tl")
- assert interest.valid?
+ assert_predicate interest, :valid?
interest.abbreviation = ""
- assert interest.invalid?
+ assert_predicate interest, :invalid?
end
end
diff --git a/activerecord/test/cases/validations/uniqueness_validation_test.rb b/activerecord/test/cases/validations/uniqueness_validation_test.rb
index 277280b42e..8f6f47e5fb 100644
--- a/activerecord/test/cases/validations/uniqueness_validation_test.rb
+++ b/activerecord/test/cases/validations/uniqueness_validation_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/topic"
require "models/reply"
@@ -60,7 +62,7 @@ class TopicWithAfterCreate < Topic
after_create :set_author
def set_author
- update_attributes!(author_name: "#{title} #{id}")
+ update!(author_name: "#{title} #{id}")
end
end
@@ -81,8 +83,8 @@ class UniquenessValidationTest < ActiveRecord::TestCase
assert t.save, "Should still save t as unique"
t2 = Topic.new("title" => "I'm uniqué!")
- assert !t2.valid?, "Shouldn't be valid"
- assert !t2.save, "Shouldn't save t2 as unique"
+ assert_not t2.valid?, "Shouldn't be valid"
+ assert_not t2.save, "Shouldn't save t2 as unique"
assert_equal ["has already been taken"], t2.errors[:title]
t2.title = "Now I am really also unique"
@@ -94,7 +96,7 @@ class UniquenessValidationTest < ActiveRecord::TestCase
Topic.validates_uniqueness_of(:new_title)
topic = Topic.new(new_title: "abc")
- assert topic.valid?
+ assert_predicate topic, :valid?
end
def test_validates_uniqueness_with_nil_value
@@ -104,8 +106,8 @@ class UniquenessValidationTest < ActiveRecord::TestCase
assert t.save, "Should save t as unique"
t2 = Topic.new("title" => nil)
- assert !t2.valid?, "Shouldn't be valid"
- assert !t2.save, "Shouldn't save t2 as unique"
+ assert_not t2.valid?, "Shouldn't be valid"
+ assert_not t2.save, "Shouldn't save t2 as unique"
assert_equal ["has already been taken"], t2.errors[:title]
end
@@ -114,7 +116,7 @@ class UniquenessValidationTest < ActiveRecord::TestCase
Topic.create!("title" => "abc")
t2 = Topic.new("title" => "abc")
- assert !t2.valid?
+ assert_not_predicate t2, :valid?
assert t2.errors[:title]
end
@@ -144,7 +146,7 @@ class UniquenessValidationTest < ActiveRecord::TestCase
assert r1.valid?, "Saving r1"
r2 = t.replies.create "title" => "r2", "content" => "hello world"
- assert !r2.valid?, "Saving r2 first time"
+ assert_not r2.valid?, "Saving r2 first time"
r2.content = "something else"
assert r2.save, "Saving r2 second time"
@@ -154,6 +156,13 @@ class UniquenessValidationTest < ActiveRecord::TestCase
assert r3.valid?, "Saving r3"
end
+ def test_validate_uniqueness_with_scope_invalid_syntax
+ error = assert_raises(ArgumentError) do
+ Reply.validates_uniqueness_of(:content, scope: { parent_id: false })
+ end
+ assert_match(/Pass a symbol or an array of symbols instead/, error.to_s)
+ end
+
def test_validate_uniqueness_with_object_scope
Reply.validates_uniqueness_of(:content, scope: :topic)
@@ -163,7 +172,7 @@ class UniquenessValidationTest < ActiveRecord::TestCase
assert r1.valid?, "Saving r1"
r2 = t.replies.create "title" => "r2", "content" => "hello world"
- assert !r2.valid?, "Saving r2 first time"
+ assert_not r2.valid?, "Saving r2 first time"
end
def test_validate_uniqueness_with_polymorphic_object_scope
@@ -184,7 +193,7 @@ class UniquenessValidationTest < ActiveRecord::TestCase
assert r1.valid?, "Saving r1"
r2 = ReplyWithTitleObject.create "title" => "r1", "content" => "hello world"
- assert !r2.valid?, "Saving r2 first time"
+ assert_not r2.valid?, "Saving r2 first time"
end
def test_validate_uniqueness_with_object_arg
@@ -196,7 +205,7 @@ class UniquenessValidationTest < ActiveRecord::TestCase
assert r1.valid?, "Saving r1"
r2 = t.replies.create "title" => "r2", "content" => "hello world"
- assert !r2.valid?, "Saving r2 first time"
+ assert_not r2.valid?, "Saving r2 first time"
end
def test_validate_uniqueness_scoped_to_defining_class
@@ -206,7 +215,7 @@ class UniquenessValidationTest < ActiveRecord::TestCase
assert r1.valid?, "Saving r1"
r2 = t.silly_unique_replies.create "title" => "r2", "content" => "a barrel of fun"
- assert !r2.valid?, "Saving r2"
+ assert_not r2.valid?, "Saving r2"
# Should succeed as validates_uniqueness_of only applies to
# UniqueReply and its subclasses
@@ -223,19 +232,19 @@ class UniquenessValidationTest < ActiveRecord::TestCase
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."
+ assert_not 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"
+ assert_not 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."
+ assert_not r3.save, "Saving r3 the third time."
end
def test_validate_case_insensitive_uniqueness
@@ -248,17 +257,17 @@ class UniquenessValidationTest < ActiveRecord::TestCase
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[:title].any?
- assert t2.errors[:parent_id].any?
+ assert_not t2.valid?, "Shouldn't be valid"
+ assert_not t2.save, "Shouldn't save t2 as unique"
+ assert_predicate t2.errors[:title], :any?
+ assert_predicate t2.errors[:parent_id], :any?
assert_equal ["has already been taken"], t2.errors[:title]
t2.title = "I'm truly UNIQUE!"
- assert !t2.valid?, "Shouldn't be valid"
- assert !t2.save, "Shouldn't save t2 as unique"
- assert t2.errors[:title].empty?
- assert t2.errors[:parent_id].any?
+ assert_not t2.valid?, "Shouldn't be valid"
+ assert_not t2.save, "Shouldn't save t2 as unique"
+ assert_empty t2.errors[:title]
+ assert_predicate t2.errors[:parent_id], :any?
t2.parent_id = 4
assert t2.save, "Should now save t2 as unique"
@@ -274,8 +283,8 @@ class UniquenessValidationTest < ActiveRecord::TestCase
# If database hasn't UTF-8 character set, this test fails
if Topic.all.merge!(select: "LOWER(title) AS title").find(t_utf8.id).title == "я тоже уникальный!"
t2_utf8 = Topic.new("title" => "я тоже УНИКАЛЬНЫЙ!")
- assert !t2_utf8.valid?, "Shouldn't be valid"
- assert !t2_utf8.save, "Shouldn't save t2_utf8 as unique"
+ assert_not t2_utf8.valid?, "Shouldn't be valid"
+ assert_not t2_utf8.save, "Shouldn't save t2_utf8 as unique"
end
end
@@ -317,15 +326,15 @@ class UniquenessValidationTest < ActiveRecord::TestCase
t2 = Topic.new("title" => "I'M UNIQUE!")
assert t2.valid?, "Should be valid"
assert t2.save, "Should save t2 as unique"
- assert t2.errors[:title].empty?
- assert t2.errors[:parent_id].empty?
+ assert_empty t2.errors[:title]
+ assert_empty t2.errors[:parent_id]
assert_not_equal ["has already been taken"], t2.errors[:title]
t3 = Topic.new("title" => "I'M uNiQUe!")
assert t3.valid?, "Should be valid"
assert t3.save, "Should save t2 as unique"
- assert t3.errors[:title].empty?
- assert t3.errors[:parent_id].empty?
+ assert_empty t3.errors[:title]
+ assert_empty t3.errors[:parent_id]
assert_not_equal ["has already been taken"], t3.errors[:title]
end
@@ -334,13 +343,13 @@ class UniquenessValidationTest < ActiveRecord::TestCase
Topic.create!("title" => 101)
t2 = Topic.new("title" => 101)
- assert !t2.valid?
+ assert_not_predicate t2, :valid?
assert t2.errors[:title]
end
def test_validate_uniqueness_with_non_standard_table_names
i1 = WarehouseThing.create(value: 1000)
- assert !i1.valid?, "i1 should not be valid"
+ assert_not i1.valid?, "i1 should not be valid"
assert i1.errors[:value].any?, "Should not be empty"
end
@@ -351,7 +360,7 @@ class UniquenessValidationTest < ActiveRecord::TestCase
t1 = Topic.new("title" => "I'm unique!", "author_name" => "Mary")
assert t1.save
t2 = Topic.new("title" => "I'm unique!", "author_name" => "David")
- assert !t2.valid?
+ assert_not_predicate t2, :valid?
end
end
@@ -385,7 +394,7 @@ class UniquenessValidationTest < ActiveRecord::TestCase
def test_validate_uniqueness_with_limit_and_utf8
if current_adapter?(:SQLite3Adapter)
- # Event.title has limit 5, but does SQLite doesn't truncate.
+ # Event.title has limit 5, but SQLite doesn't truncate.
e1 = Event.create(title: "一二三四五六七八")
assert e1.valid?, "Could not create an event with a unique 8 characters title"
@@ -408,12 +417,12 @@ class UniquenessValidationTest < ActiveRecord::TestCase
# 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_not w2.valid?, "w2 shouldn't be valid"
assert w2.errors[:name].any?, "Should have errors for name"
assert_equal ["has already been taken"], w2.errors[:name], "Should have uniqueness message for name"
w3 = Conjurer.new(name: "Rincewind", city: "Quirm")
- assert !w3.valid?, "w3 shouldn't be valid"
+ assert_not w3.valid?, "w3 shouldn't be valid"
assert w3.errors[:name].any?, "Should have errors for name"
assert_equal ["has already been taken"], w3.errors[:name], "Should have uniqueness message for name"
@@ -421,12 +430,12 @@ class UniquenessValidationTest < ActiveRecord::TestCase
assert w4.valid?, "Saving w4"
w5 = Thaumaturgist.new(name: "The Amazing Bonko", city: "Lancre")
- assert !w5.valid?, "w5 shouldn't be valid"
+ assert_not w5.valid?, "w5 shouldn't be valid"
assert w5.errors[:name].any?, "Should have errors for name"
assert_equal ["has already been taken"], w5.errors[:name], "Should have uniqueness message for name"
w6 = Thaumaturgist.new(name: "Mustrum Ridcully", city: "Quirm")
- assert !w6.valid?, "w6 shouldn't be valid"
+ assert_not w6.valid?, "w6 shouldn't be valid"
assert w6.errors[:city].any?, "Should have errors for city"
assert_equal ["has already been taken"], w6.errors[:city], "Should have uniqueness message for city"
end
@@ -437,7 +446,7 @@ class UniquenessValidationTest < ActiveRecord::TestCase
Topic.create("title" => "I'm an unapproved topic", "approved" => false)
t3 = Topic.new("title" => "I'm a topic", "approved" => true)
- assert !t3.valid?, "t3 shouldn't be valid"
+ assert_not t3.valid?, "t3 shouldn't be valid"
t4 = Topic.new("title" => "I'm an unapproved topic", "approved" => false)
assert t4.valid?, "t4 should be valid"
@@ -451,16 +460,16 @@ class UniquenessValidationTest < ActiveRecord::TestCase
def test_validate_uniqueness_on_existing_relation
event = Event.create
- assert TopicWithUniqEvent.create(event: event).valid?
+ assert_predicate TopicWithUniqEvent.create(event: event), :valid?
topic = TopicWithUniqEvent.new(event: event)
- assert_not topic.valid?
+ assert_not_predicate topic, :valid?
assert_equal ["has already been taken"], topic.errors[:event]
end
def test_validate_uniqueness_on_empty_relation
topic = TopicWithUniqEvent.new
- assert topic.valid?
+ assert_predicate topic, :valid?
end
def test_validate_uniqueness_of_custom_primary_key
@@ -479,7 +488,7 @@ class UniquenessValidationTest < ActiveRecord::TestCase
key2 = klass.create!(key_number: 11)
key2.key_number = 10
- assert_not key2.valid?
+ assert_not_predicate key2, :valid?
end
def test_validate_uniqueness_without_primary_key
@@ -492,8 +501,8 @@ class UniquenessValidationTest < ActiveRecord::TestCase
end
abc = klass.create!(dashboard_id: "abc")
- assert klass.new(dashboard_id: "xyz").valid?
- assert_not klass.new(dashboard_id: "abc").valid?
+ assert_predicate klass.new(dashboard_id: "xyz"), :valid?
+ assert_not_predicate klass.new(dashboard_id: "abc"), :valid?
abc.dashboard_id = "def"
@@ -521,7 +530,7 @@ class UniquenessValidationTest < ActiveRecord::TestCase
assert topic.author_name.start_with?("Title1")
topic2 = TopicWithAfterCreate.new(title: "Title1")
- refute topic2.valid?
+ assert_not_predicate topic2, :valid?
assert_equal(["has already been taken"], topic2.errors[:title])
end
@@ -541,7 +550,7 @@ class UniquenessValidationTest < ActiveRecord::TestCase
assert_empty item.errors
item2 = CoolTopic.new(id: item.id, title: "MyItem2")
- refute item2.valid?
+ assert_not_predicate item2, :valid?
assert_equal(["has already been taken"], item2.errors[:id])
end
diff --git a/activerecord/test/cases/validations_repair_helper.rb b/activerecord/test/cases/validations_repair_helper.rb
index b30666d876..6dc3b64b2b 100644
--- a/activerecord/test/cases/validations_repair_helper.rb
+++ b/activerecord/test/cases/validations_repair_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ActiveRecord
module ValidationsRepairHelper
extend ActiveSupport::Concern
diff --git a/activerecord/test/cases/validations_test.rb b/activerecord/test/cases/validations_test.rb
index 5d9aa99497..66763c727f 100644
--- a/activerecord/test/cases/validations_test.rb
+++ b/activerecord/test/cases/validations_test.rb
@@ -1,11 +1,13 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/topic"
require "models/reply"
-require "models/person"
require "models/developer"
require "models/computer"
require "models/parrot"
require "models/company"
+require "models/price_estimate"
class ValidationsTest < ActiveRecord::TestCase
fixtures :topics, :developers
@@ -17,7 +19,7 @@ class ValidationsTest < ActiveRecord::TestCase
def test_valid_uses_create_context_when_new
r = WrongReply.new
r.title = "Wrong Create"
- assert_not r.valid?
+ assert_not_predicate r, :valid?
assert r.errors[:title].any?, "A reply with a bad title should mark that attribute as invalid"
assert_equal ["is Wrong Create"], r.errors[:title], "A reply with a bad content should contain an error"
end
@@ -37,7 +39,7 @@ class ValidationsTest < ActiveRecord::TestCase
def test_valid_using_special_context
r = WrongReply.new(title: "Valid title")
- assert !r.valid?(:special_case)
+ assert_not r.valid?(:special_case)
assert_equal "Invalid", r.errors[:author_name].join
r.author_name = "secret"
@@ -123,7 +125,7 @@ class ValidationsTest < ActiveRecord::TestCase
def test_save_without_validation
reply = WrongReply.new
- assert !reply.save
+ assert_not reply.save
assert reply.save(validate: false)
end
@@ -137,7 +139,7 @@ class ValidationsTest < ActiveRecord::TestCase
def test_throw_away_typing
d = Developer.new("name" => "David", "salary" => "100,000")
- assert !d.valid?
+ assert_not_predicate d, :valid?
assert_equal 100, d.salary
assert_equal "100,000", d.salary_before_type_cast
end
@@ -164,7 +166,37 @@ class ValidationsTest < ActiveRecord::TestCase
topic = klass.new(wibble: "123-4567")
topic.wibble.gsub!("-", "")
- assert topic.valid?
+ assert_predicate topic, :valid?
+ end
+
+ def test_numericality_validation_checks_against_raw_value
+ klass = Class.new(Topic) do
+ def self.model_name
+ ActiveModel::Name.new(self, nil, "Topic")
+ end
+ attribute :wibble, :decimal, scale: 2, precision: 9
+ validates_numericality_of :wibble, greater_than_or_equal_to: BigDecimal("97.18")
+ end
+
+ assert_not_predicate klass.new(wibble: "97.179"), :valid?
+ assert_not_predicate klass.new(wibble: 97.179), :valid?
+ assert_not_predicate klass.new(wibble: BigDecimal("97.179")), :valid?
+ end
+
+ def test_numericality_validator_wont_be_affected_by_custom_getter
+ price_estimate = PriceEstimate.new(price: 50)
+
+ assert_equal "$50.00", price_estimate.price
+ assert_equal 50, price_estimate.price_before_type_cast
+ assert_equal 50, price_estimate.read_attribute(:price)
+
+ assert_predicate price_estimate, :price_came_from_user?
+ assert_predicate price_estimate, :valid?
+
+ price_estimate.save!
+
+ assert_not_predicate price_estimate, :price_came_from_user?
+ assert_predicate price_estimate, :valid?
end
def test_acceptance_validator_doesnt_require_db_connection
diff --git a/activerecord/test/cases/view_test.rb b/activerecord/test/cases/view_test.rb
index 07288568e8..7e2d66c62a 100644
--- a/activerecord/test/cases/view_test.rb
+++ b/activerecord/test/cases/view_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/book"
require "support/schema_dumping_helper"
@@ -154,7 +156,9 @@ if ActiveRecord::Base.connection.supports_views?
end
# sqlite dose not support CREATE, INSERT, and DELETE for VIEW
- if current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter, :SQLServerAdapter)
+ if current_adapter?(:Mysql2Adapter, :SQLServerAdapter) ||
+ current_adapter?(:PostgreSQLAdapter) && ActiveRecord::Base.connection.postgresql_version >= 90300
+
class UpdateableViewTest < ActiveRecord::TestCase
self.use_transactional_tests = false
fixtures :books
diff --git a/activerecord/test/cases/yaml_serialization_test.rb b/activerecord/test/cases/yaml_serialization_test.rb
index 1571b31329..60ebdce178 100644
--- a/activerecord/test/cases/yaml_serialization_test.rb
+++ b/activerecord/test/cases/yaml_serialization_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "models/topic"
require "models/reply"
@@ -5,7 +7,7 @@ require "models/post"
require "models/author"
class YamlSerializationTest < ActiveRecord::TestCase
- fixtures :topics, :authors, :posts
+ fixtures :topics, :authors, :author_addresses, :posts
def test_to_yaml_with_time_with_zone_should_not_raise_exception
with_timezone_config aware_attributes: true, zone: "Pacific Time (US & Canada)" do
@@ -94,7 +96,7 @@ class YamlSerializationTest < ActiveRecord::TestCase
def test_deserializing_rails_41_yaml
topic = YAML.load(yaml_fixture("rails_4_1"))
- assert topic.new_record?
+ assert_predicate topic, :new_record?
assert_nil topic.id
assert_equal "The First Topic", topic.title
assert_equal({ omg: :lol }, topic.content)
@@ -103,7 +105,7 @@ class YamlSerializationTest < ActiveRecord::TestCase
def test_deserializing_rails_4_2_0_yaml
topic = YAML.load(yaml_fixture("rails_4_2_0"))
- assert_not topic.new_record?
+ assert_not_predicate topic, :new_record?
assert_equal 1, topic.id
assert_equal "The First Topic", topic.title
assert_equal("Have a nice day", topic.content)
@@ -119,12 +121,20 @@ class YamlSerializationTest < ActiveRecord::TestCase
assert_equal author.changes, dumped.changes
end
+ def test_yaml_encoding_keeps_false_values
+ topic = Topic.first
+ topic.approved = false
+ dumped = YAML.load(YAML.dump(topic))
+
+ assert_equal false, dumped.approved
+ end
+
private
def yaml_fixture(file_name)
path = File.expand_path(
- "../../support/yaml_compatibility_fixtures/#{file_name}.yml",
- __FILE__
+ "../support/yaml_compatibility_fixtures/#{file_name}.yml",
+ __dir__
)
File.read(path)
end