ibm_db 5.6.1 → 5.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (592) hide show
  1. checksums.yaml +4 -4
  2. data/ext/Makefile +13 -9
  3. data/ext/extconf.rb +176 -110
  4. data/ext/ibm_db.c +60 -0
  5. data/ext/ibm_db.o +0 -0
  6. data/ext/ibm_db.so +0 -0
  7. data/ext/mkmf.log +6 -6
  8. data/ext/ruby_ibm_db_cli.c +14 -10
  9. data/ext/ruby_ibm_db_cli.h +4 -0
  10. data/ext/ruby_ibm_db_cli.o +0 -0
  11. data/lib/IBM_DB.rb +2 -2
  12. data/lib/active_record/connection_adapters/ibm_db_adapter.rb +1254 -277
  13. data/test/active_record/connection_adapters/fake_legacy_adapter.rb +17 -0
  14. data/test/active_record/connection_adapters/fake_misleading_legacy_adapter.rb +8 -0
  15. data/test/activejob/destroy_association_async_job_test.rb +62 -0
  16. data/test/activejob/destroy_association_async_test.rb +147 -11
  17. data/test/activejob/job_runtime_test.rb +41 -0
  18. data/test/activejob/unloadable_base_job.rb +3 -0
  19. data/test/cases/active_record_schema_test.rb +196 -0
  20. data/test/cases/active_record_test.rb +21 -0
  21. data/test/cases/adapter_prevent_writes_test.rb +26 -202
  22. data/test/cases/adapter_test.rb +592 -82
  23. data/test/cases/adapters/abstract_mysql_adapter/active_schema_test.rb +210 -0
  24. data/test/cases/adapters/abstract_mysql_adapter/adapter_prevent_writes_test.rb +111 -0
  25. data/test/cases/adapters/abstract_mysql_adapter/auto_increment_test.rb +48 -0
  26. data/test/cases/adapters/abstract_mysql_adapter/bind_parameter_test.rb +91 -0
  27. data/test/cases/adapters/abstract_mysql_adapter/case_sensitivity_test.rb +65 -0
  28. data/test/cases/adapters/abstract_mysql_adapter/charset_collation_test.rb +85 -0
  29. data/test/cases/adapters/abstract_mysql_adapter/connection_test.rb +267 -0
  30. data/test/cases/adapters/abstract_mysql_adapter/count_deleted_rows_with_lock_test.rb +29 -0
  31. data/test/cases/adapters/abstract_mysql_adapter/json_test.rb +24 -0
  32. data/test/cases/adapters/abstract_mysql_adapter/mysql_boolean_test.rb +102 -0
  33. data/test/cases/adapters/abstract_mysql_adapter/mysql_enum_test.rb +47 -0
  34. data/test/cases/adapters/abstract_mysql_adapter/mysql_explain_test.rb +71 -0
  35. data/test/cases/adapters/abstract_mysql_adapter/nested_deadlock_test.rb +191 -0
  36. data/test/cases/adapters/abstract_mysql_adapter/optimizer_hints_test.rb +69 -0
  37. data/test/cases/adapters/abstract_mysql_adapter/quoting_test.rb +49 -0
  38. data/test/cases/adapters/abstract_mysql_adapter/schema_migrations_test.rb +68 -0
  39. data/test/cases/adapters/abstract_mysql_adapter/schema_test.rb +130 -0
  40. data/test/cases/adapters/abstract_mysql_adapter/set_test.rb +32 -0
  41. data/test/cases/adapters/abstract_mysql_adapter/sp_test.rb +39 -0
  42. data/test/cases/adapters/abstract_mysql_adapter/sql_types_test.rb +16 -0
  43. data/test/cases/adapters/abstract_mysql_adapter/table_options_test.rb +125 -0
  44. data/test/cases/adapters/abstract_mysql_adapter/transaction_test.rb +204 -0
  45. data/test/cases/adapters/abstract_mysql_adapter/unsigned_type_test.rb +77 -0
  46. data/test/cases/adapters/abstract_mysql_adapter/virtual_column_test.rb +69 -0
  47. data/test/cases/adapters/abstract_mysql_adapter/warnings_test.rb +109 -0
  48. data/test/cases/adapters/mysql2/check_constraint_quoting_test.rb +35 -0
  49. data/test/cases/adapters/mysql2/dbconsole_test.rb +87 -0
  50. data/test/cases/adapters/mysql2/mysql2_adapter_test.rb +136 -10
  51. data/test/cases/adapters/mysql2/mysql2_rake_test.rb +433 -0
  52. data/test/cases/adapters/postgresql/active_schema_test.rb +8 -4
  53. data/test/cases/adapters/postgresql/array_test.rb +6 -6
  54. data/test/cases/adapters/postgresql/bind_parameter_test.rb +52 -0
  55. data/test/cases/adapters/postgresql/bit_string_test.rb +2 -2
  56. data/test/cases/adapters/postgresql/bytea_test.rb +3 -3
  57. data/test/cases/adapters/postgresql/case_insensitive_test.rb +1 -1
  58. data/test/cases/adapters/postgresql/change_schema_test.rb +32 -1
  59. data/test/cases/adapters/postgresql/cidr_test.rb +17 -0
  60. data/test/cases/adapters/postgresql/citext_test.rb +7 -1
  61. data/test/cases/adapters/postgresql/collation_test.rb +1 -1
  62. data/test/cases/adapters/postgresql/composite_test.rb +1 -1
  63. data/test/cases/adapters/postgresql/connection_test.rb +43 -36
  64. data/test/cases/adapters/postgresql/create_unlogged_tables_test.rb +3 -2
  65. data/test/cases/adapters/postgresql/datatype_test.rb +9 -4
  66. data/test/cases/adapters/postgresql/date_test.rb +2 -2
  67. data/test/cases/adapters/postgresql/dbconsole_test.rb +109 -0
  68. data/test/cases/adapters/postgresql/deferred_constraints_test.rb +55 -0
  69. data/test/cases/adapters/postgresql/domain_test.rb +1 -1
  70. data/test/cases/adapters/postgresql/enum_test.rb +208 -8
  71. data/test/cases/adapters/postgresql/explain_test.rb +25 -6
  72. data/test/cases/adapters/postgresql/extension_migration_test.rb +51 -9
  73. data/test/cases/adapters/postgresql/foreign_table_test.rb +3 -3
  74. data/test/cases/adapters/postgresql/full_text_test.rb +1 -1
  75. data/test/cases/adapters/postgresql/geometric_test.rb +13 -6
  76. data/test/cases/adapters/postgresql/hstore_test.rb +22 -32
  77. data/test/cases/adapters/postgresql/infinity_test.rb +13 -2
  78. data/test/cases/adapters/postgresql/integer_test.rb +1 -1
  79. data/test/cases/adapters/postgresql/interval_test.rb +9 -17
  80. data/test/cases/adapters/postgresql/invertible_migration_test.rb +139 -0
  81. data/test/cases/adapters/postgresql/json_test.rb +5 -0
  82. data/test/cases/adapters/postgresql/ltree_test.rb +1 -1
  83. data/test/cases/adapters/postgresql/money_test.rb +17 -9
  84. data/test/cases/adapters/postgresql/network_test.rb +13 -1
  85. data/test/cases/adapters/postgresql/numbers_test.rb +18 -2
  86. data/test/cases/adapters/postgresql/optimizer_hints_test.rb +9 -9
  87. data/test/cases/adapters/postgresql/partitions_test.rb +5 -2
  88. data/test/cases/adapters/postgresql/postgresql_adapter_prevent_writes_test.rb +7 -110
  89. data/test/cases/adapters/postgresql/postgresql_adapter_test.rb +403 -20
  90. data/test/cases/adapters/postgresql/postgresql_rake_test.rb +637 -0
  91. data/test/cases/adapters/postgresql/prepared_statements_disabled_test.rb +1 -1
  92. data/test/cases/adapters/postgresql/quoting_test.rb +60 -1
  93. data/test/cases/adapters/postgresql/range_test.rb +185 -21
  94. data/test/cases/adapters/postgresql/referential_integrity_test.rb +29 -5
  95. data/test/cases/adapters/postgresql/rename_table_test.rb +35 -8
  96. data/test/cases/adapters/postgresql/schema_authorization_test.rb +2 -2
  97. data/test/cases/adapters/postgresql/schema_test.rb +270 -20
  98. data/test/cases/adapters/postgresql/serial_test.rb +5 -5
  99. data/test/cases/adapters/postgresql/statement_pool_test.rb +6 -2
  100. data/test/cases/adapters/postgresql/timestamp_test.rb +122 -5
  101. data/test/cases/adapters/postgresql/transaction_nested_test.rb +130 -6
  102. data/test/cases/adapters/postgresql/transaction_test.rb +37 -11
  103. data/test/cases/adapters/postgresql/type_lookup_test.rb +1 -1
  104. data/test/cases/adapters/postgresql/uuid_test.rb +77 -9
  105. data/test/cases/adapters/postgresql/virtual_column_test.rb +114 -0
  106. data/test/cases/adapters/postgresql/xml_test.rb +1 -1
  107. data/test/cases/adapters/sqlite3/bind_parameter_test.rb +52 -0
  108. data/test/cases/adapters/sqlite3/collation_test.rb +1 -1
  109. data/test/cases/adapters/sqlite3/copy_table_test.rb +28 -7
  110. data/test/cases/adapters/sqlite3/dbconsole_test.rb +81 -0
  111. data/test/cases/adapters/sqlite3/explain_test.rb +3 -3
  112. data/test/cases/adapters/sqlite3/quoting_test.rb +34 -3
  113. data/test/cases/adapters/sqlite3/sqlite3_adapter_prevent_writes_test.rb +9 -102
  114. data/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb +727 -72
  115. data/test/cases/adapters/sqlite3/sqlite3_create_folder_test.rb +6 -3
  116. data/test/cases/adapters/sqlite3/sqlite_rake_test.rb +264 -0
  117. data/test/cases/adapters/sqlite3/statement_pool_test.rb +1 -1
  118. data/test/cases/adapters/sqlite3/transaction_test.rb +6 -3
  119. data/test/cases/adapters/sqlite3/virtual_column_test.rb +138 -0
  120. data/test/cases/adapters/sqlite3/virtual_table_test.rb +51 -0
  121. data/test/cases/adapters/trilogy/dbconsole_test.rb +76 -0
  122. data/test/cases/adapters/trilogy/trilogy_adapter_test.rb +473 -0
  123. data/test/cases/adapters/trilogy/trilogy_rake_test.rb +433 -0
  124. data/test/cases/aggregations_test.rb +16 -0
  125. data/test/cases/annotate_test.rb +14 -12
  126. data/test/cases/arel/attributes/attribute_test.rb +86 -47
  127. data/test/cases/arel/collectors/bind_test.rb +1 -1
  128. data/test/cases/arel/collectors/composite_test.rb +11 -1
  129. data/test/cases/arel/collectors/sql_string_test.rb +1 -1
  130. data/test/cases/arel/collectors/substitute_bind_collector_test.rb +1 -1
  131. data/test/cases/arel/delete_manager_test.rb +0 -6
  132. data/test/cases/arel/factory_methods_test.rb +45 -0
  133. data/test/cases/arel/insert_manager_test.rb +1 -7
  134. data/test/cases/arel/nodes/as_test.rb +12 -0
  135. data/test/cases/arel/nodes/ascending_test.rb +1 -1
  136. data/test/cases/arel/nodes/bin_test.rb +1 -1
  137. data/test/cases/arel/nodes/bound_sql_literal_test.rb +31 -0
  138. data/test/cases/arel/nodes/cte_test.rb +46 -0
  139. data/test/cases/arel/nodes/descending_test.rb +1 -1
  140. data/test/cases/arel/nodes/equality_test.rb +3 -3
  141. data/test/cases/arel/nodes/filter_test.rb +37 -0
  142. data/test/cases/arel/nodes/fragments_test.rb +38 -0
  143. data/test/cases/arel/nodes/homogeneous_in_test.rb +52 -0
  144. data/test/cases/arel/nodes/infix_operation_test.rb +1 -1
  145. data/test/cases/arel/nodes/node_test.rb +1 -1
  146. data/test/cases/arel/nodes/or_test.rb +2 -2
  147. data/test/cases/arel/nodes/sql_literal_test.rb +18 -1
  148. data/test/cases/arel/nodes/table_alias_test.rb +12 -0
  149. data/test/cases/arel/nodes/unary_operation_test.rb +1 -1
  150. data/test/cases/arel/nodes/update_statement_test.rb +4 -0
  151. data/test/cases/arel/nodes_test.rb +3 -2
  152. data/test/cases/arel/select_manager_test.rb +12 -12
  153. data/test/cases/arel/support/fake_record.rb +22 -17
  154. data/test/cases/arel/table_test.rb +15 -25
  155. data/test/cases/arel/update_manager_test.rb +51 -6
  156. data/test/cases/arel/visitors/dispatch_contamination_test.rb +1 -1
  157. data/test/cases/arel/visitors/dot_test.rb +177 -2
  158. data/test/cases/arel/visitors/mysql_test.rb +49 -1
  159. data/test/cases/arel/visitors/postgres_test.rb +1 -31
  160. data/test/cases/arel/visitors/sqlite_test.rb +1 -1
  161. data/test/cases/arel/visitors/to_sql_test.rb +330 -20
  162. data/test/cases/assertions/query_assertions_test.rb +136 -0
  163. data/test/cases/associations/belongs_to_associations_test.rb +502 -41
  164. data/test/cases/associations/bidirectional_destroy_dependencies_test.rb +1 -1
  165. data/test/cases/associations/cascaded_eager_loading_test.rb +26 -15
  166. data/test/cases/associations/eager_load_nested_include_test.rb +1 -1
  167. data/test/cases/associations/eager_singularization_test.rb +1 -1
  168. data/test/cases/associations/eager_test.rb +255 -93
  169. data/test/cases/associations/has_and_belongs_to_many_associations_test.rb +25 -53
  170. data/test/cases/associations/has_many_associations_test.rb +282 -72
  171. data/test/cases/associations/has_many_through_associations_test.rb +172 -45
  172. data/test/cases/associations/has_many_through_disable_joins_associations_test.rb +197 -0
  173. data/test/cases/associations/has_one_associations_test.rb +160 -29
  174. data/test/cases/associations/has_one_through_associations_test.rb +64 -15
  175. data/test/cases/associations/has_one_through_disable_joins_associations_test.rb +82 -0
  176. data/test/cases/associations/inner_join_association_test.rb +36 -6
  177. data/test/cases/associations/inverse_associations_test.rb +177 -40
  178. data/test/cases/associations/join_model_test.rb +17 -12
  179. data/test/cases/associations/left_outer_join_association_test.rb +14 -3
  180. data/test/cases/associations/nested_error_test.rb +116 -0
  181. data/test/cases/associations/nested_through_associations_test.rb +54 -19
  182. data/test/cases/associations/required_test.rb +1 -1
  183. data/test/cases/associations_test.rb +1278 -21
  184. data/test/cases/asynchronous_queries_test.rb +310 -0
  185. data/test/cases/attribute_methods/read_test.rb +6 -0
  186. data/test/cases/attribute_methods/time_zone_converter_test.rb +21 -0
  187. data/test/cases/attribute_methods_test.rb +565 -46
  188. data/test/cases/attributes_test.rb +42 -1
  189. data/test/cases/autosave_association_test.rb +645 -92
  190. data/test/cases/base_prevent_writes_test.rb +28 -159
  191. data/test/cases/base_test.rb +434 -209
  192. data/test/cases/batches_test.rb +441 -57
  193. data/test/cases/binary_test.rb +31 -0
  194. data/test/cases/bind_parameter_test.rb +107 -16
  195. data/test/cases/cache_key_test.rb +7 -7
  196. data/test/cases/calculations_test.rb +541 -110
  197. data/test/cases/callbacks_test.rb +0 -1
  198. data/test/cases/clone_test.rb +5 -4
  199. data/test/cases/coders/yaml_column_test.rb +97 -2
  200. data/test/cases/collection_cache_key_test.rb +24 -16
  201. data/test/cases/column_alias_test.rb +2 -8
  202. data/test/cases/column_definition_test.rb +12 -3
  203. data/test/cases/comment_test.rb +23 -26
  204. data/test/cases/connection_adapters/adapter_leasing_test.rb +9 -9
  205. data/test/cases/connection_adapters/connection_handler_test.rb +145 -99
  206. data/test/cases/connection_adapters/connection_handlers_multi_db_test.rb +79 -55
  207. data/test/cases/connection_adapters/connection_handlers_multi_pool_config_test.rb +17 -20
  208. data/test/cases/connection_adapters/connection_handlers_sharding_db_test.rb +146 -55
  209. data/test/cases/connection_adapters/connection_swapping_nested_test.rb +136 -23
  210. data/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb +96 -27
  211. data/test/cases/connection_adapters/mysql_type_lookup_test.rb +5 -5
  212. data/test/cases/connection_adapters/registration_test.rb +98 -0
  213. data/test/cases/connection_adapters/schema_cache_test.rb +288 -135
  214. data/test/cases/connection_adapters/standalone_connection_test.rb +38 -0
  215. data/test/cases/connection_adapters/statement_pool_test.rb +29 -0
  216. data/test/cases/connection_adapters/type_lookup_test.rb +12 -24
  217. data/test/cases/connection_handling_test.rb +210 -0
  218. data/test/cases/connection_management_test.rb +44 -7
  219. data/test/cases/connection_pool_test.rb +466 -99
  220. data/test/cases/core_test.rb +167 -9
  221. data/test/cases/counter_cache_test.rb +129 -2
  222. data/test/cases/custom_locking_test.rb +3 -3
  223. data/test/cases/database_configurations/hash_config_test.rb +149 -13
  224. data/test/cases/database_configurations/resolver_test.rb +14 -10
  225. data/test/cases/database_configurations/url_config_test.rb +53 -0
  226. data/test/cases/database_configurations_test.rb +86 -43
  227. data/test/cases/database_selector_test.rb +30 -8
  228. data/test/cases/database_statements_test.rb +10 -14
  229. data/test/cases/date_time_precision_test.rb +141 -11
  230. data/test/cases/date_time_test.rb +19 -1
  231. data/test/cases/defaults_test.rb +107 -41
  232. data/test/cases/delegated_type_test.rb +69 -9
  233. data/test/cases/dirty_test.rb +82 -25
  234. data/test/cases/disconnected_test.rb +10 -9
  235. data/test/cases/dup_test.rb +26 -6
  236. data/test/cases/encryption/cipher/aes256_gcm_test.rb +50 -0
  237. data/test/cases/encryption/cipher_test.rb +65 -0
  238. data/test/cases/encryption/concurrency_test.rb +27 -0
  239. data/test/cases/encryption/config_test.rb +21 -0
  240. data/test/cases/encryption/configurable_test.rb +93 -0
  241. data/test/cases/encryption/contexts_test.rb +105 -0
  242. data/test/cases/encryption/derived_secret_key_provider_test.rb +31 -0
  243. data/test/cases/encryption/deterministic_key_provider_test.rb +11 -0
  244. data/test/cases/encryption/encryptable_record_api_test.rb +175 -0
  245. data/test/cases/encryption/encryptable_record_message_pack_serialized_test.rb +42 -0
  246. data/test/cases/encryption/encryptable_record_test.rb +470 -0
  247. data/test/cases/encryption/encrypted_fixtures_test.rb +22 -0
  248. data/test/cases/encryption/encrypting_only_encryptor_test.rb +20 -0
  249. data/test/cases/encryption/encryption_schemes_test.rb +240 -0
  250. data/test/cases/encryption/encryptor_test.rb +113 -0
  251. data/test/cases/encryption/envelope_encryption_key_provider_test.rb +49 -0
  252. data/test/cases/encryption/extended_deterministic_queries_test.rb +81 -0
  253. data/test/cases/encryption/helper.rb +152 -0
  254. data/test/cases/encryption/key_generator_test.rb +55 -0
  255. data/test/cases/encryption/key_provider_test.rb +57 -0
  256. data/test/cases/encryption/key_test.rb +17 -0
  257. data/test/cases/encryption/message_pack_message_serializer_test.rb +70 -0
  258. data/test/cases/encryption/message_serializer_test.rb +82 -0
  259. data/test/cases/encryption/message_test.rb +43 -0
  260. data/test/cases/encryption/null_encryptor_test.rb +21 -0
  261. data/test/cases/encryption/properties_test.rb +69 -0
  262. data/test/cases/encryption/read_only_null_encryptor_test.rb +19 -0
  263. data/test/cases/encryption/scheme_test.rb +58 -0
  264. data/test/cases/encryption/unencrypted_attributes_test.rb +27 -0
  265. data/test/cases/encryption/uniqueness_validations_test.rb +65 -0
  266. data/test/cases/enum_test.rb +374 -72
  267. data/test/cases/errors_test.rb +7 -4
  268. data/test/cases/excluding_test.rb +116 -0
  269. data/test/cases/explain_subscriber_test.rb +6 -1
  270. data/test/cases/explain_test.rb +119 -10
  271. data/test/cases/filter_attributes_test.rb +7 -14
  272. data/test/cases/finder_respond_to_test.rb +3 -3
  273. data/test/cases/finder_test.rb +357 -72
  274. data/test/cases/fixture_set/file_test.rb +9 -0
  275. data/test/cases/fixtures_test.rb +426 -190
  276. data/test/cases/helper.rb +66 -192
  277. data/test/cases/hot_compatibility_test.rb +10 -10
  278. data/test/cases/inheritance_test.rb +51 -61
  279. data/test/cases/inherited_test.rb +40 -0
  280. data/test/cases/insert_all_test.rb +419 -9
  281. data/test/cases/instrumentation_test.rb +202 -30
  282. data/test/cases/integration_test.rb +25 -7
  283. data/test/cases/invalid_connection_test.rb +5 -5
  284. data/test/cases/invertible_migration_test.rb +108 -24
  285. data/test/cases/json_attribute_test.rb +1 -0
  286. data/test/cases/json_serialization_test.rb +5 -5
  287. data/test/cases/json_shared_test_cases.rb +5 -9
  288. data/test/cases/locking_test.rb +67 -31
  289. data/test/cases/log_subscriber_test.rb +17 -15
  290. data/test/cases/marshal_serialization_test.rb +78 -5
  291. data/test/cases/message_pack_test.rb +91 -0
  292. data/test/cases/migration/change_schema_test.rb +77 -46
  293. data/test/cases/migration/change_table_test.rb +129 -114
  294. data/test/cases/migration/check_constraint_test.rb +192 -10
  295. data/test/cases/migration/column_attributes_test.rb +4 -9
  296. data/test/cases/migration/column_positioning_test.rb +2 -2
  297. data/test/cases/migration/columns_test.rb +118 -4
  298. data/test/cases/migration/command_recorder_test.rb +181 -14
  299. data/test/cases/migration/compatibility_test.rb +883 -196
  300. data/test/cases/migration/create_join_table_test.rb +8 -1
  301. data/test/cases/migration/exclusion_constraint_test.rb +192 -0
  302. data/test/cases/migration/foreign_key_test.rb +356 -14
  303. data/test/cases/migration/helper.rb +4 -1
  304. data/test/cases/migration/index_test.rb +67 -2
  305. data/test/cases/migration/invalid_options_test.rb +129 -0
  306. data/test/cases/migration/logger_test.rb +7 -4
  307. data/test/cases/migration/pending_migrations_test.rb +103 -60
  308. data/test/cases/migration/references_foreign_key_test.rb +73 -4
  309. data/test/cases/migration/references_index_test.rb +25 -27
  310. data/test/cases/migration/references_statements_test.rb +27 -3
  311. data/test/cases/migration/rename_table_test.rb +39 -54
  312. data/test/cases/migration/schema_definitions_test.rb +109 -0
  313. data/test/cases/migration/unique_constraint_test.rb +248 -0
  314. data/test/cases/migration_test.rb +677 -220
  315. data/test/cases/migrator_test.rb +102 -75
  316. data/test/cases/multi_db_migrator_test.rb +55 -34
  317. data/test/cases/multiparameter_attributes_test.rb +26 -29
  318. data/test/cases/multiple_db_test.rb +42 -19
  319. data/test/cases/nested_attributes_test.rb +124 -6
  320. data/test/cases/nested_attributes_with_callbacks_test.rb +2 -2
  321. data/test/cases/normalized_attribute_test.rb +117 -0
  322. data/test/cases/null_relation_test.rb +35 -2
  323. data/test/cases/persistence/reload_association_cache_test.rb +20 -0
  324. data/test/cases/persistence_test.rb +629 -22
  325. data/test/cases/pooled_connections_test.rb +57 -56
  326. data/test/cases/prepared_statement_status_test.rb +8 -3
  327. data/test/cases/primary_class_test.rb +134 -0
  328. data/test/cases/primary_keys_test.rb +171 -55
  329. data/test/cases/query_cache_test.rb +558 -263
  330. data/test/cases/query_logs_test.rb +293 -0
  331. data/test/cases/quoting_test.rb +39 -72
  332. data/test/cases/readonly_test.rb +50 -12
  333. data/test/cases/reaper_test.rb +22 -6
  334. data/test/cases/reflection_test.rb +207 -9
  335. data/test/cases/relation/and_test.rb +43 -0
  336. data/test/cases/relation/delegation_test.rb +32 -5
  337. data/test/cases/relation/delete_all_test.rb +27 -6
  338. data/test/cases/relation/field_ordered_values_test.rb +128 -0
  339. data/test/cases/relation/load_async_test.rb +650 -0
  340. data/test/cases/relation/merging_test.rb +80 -109
  341. data/test/cases/relation/mutation_test.rb +12 -7
  342. data/test/cases/relation/or_test.rb +11 -8
  343. data/test/cases/relation/order_test.rb +90 -0
  344. data/test/cases/relation/predicate_builder_test.rb +21 -6
  345. data/test/cases/relation/select_test.rb +162 -3
  346. data/test/cases/relation/structural_compatibility_test.rb +38 -0
  347. data/test/cases/relation/update_all_test.rb +81 -27
  348. data/test/cases/relation/where_chain_test.rb +190 -3
  349. data/test/cases/relation/where_clause_test.rb +43 -1
  350. data/test/cases/relation/where_test.rb +95 -7
  351. data/test/cases/relation/with_test.rb +165 -0
  352. data/test/cases/relation_test.rb +42 -37
  353. data/test/cases/relations_test.rb +436 -138
  354. data/test/cases/reload_models_test.rb +18 -14
  355. data/test/cases/reserved_word_test.rb +2 -1
  356. data/test/cases/result_test.rb +14 -22
  357. data/test/cases/sanitize_test.rb +89 -27
  358. data/test/cases/schema_dumper_test.rb +535 -62
  359. data/test/cases/scoping/default_scoping_test.rb +219 -35
  360. data/test/cases/scoping/named_scoping_test.rb +25 -46
  361. data/test/cases/scoping/relation_scoping_test.rb +112 -4
  362. data/test/cases/secure_password_test.rb +108 -0
  363. data/test/cases/secure_token_test.rb +72 -0
  364. data/test/cases/serialization_test.rb +1 -1
  365. data/test/cases/serialized_attribute_test.rb +305 -60
  366. data/test/cases/shard_keys_test.rb +125 -0
  367. data/test/cases/shard_selector_test.rb +45 -0
  368. data/test/cases/signed_id_test.rb +57 -5
  369. data/test/cases/statement_cache_test.rb +31 -30
  370. data/test/cases/statement_invalid_test.rb +8 -4
  371. data/test/cases/store_test.rb +73 -9
  372. data/test/cases/strict_loading_test.rb +327 -13
  373. data/test/cases/table_metadata_test.rb +16 -0
  374. data/test/cases/tasks/database_tasks_test.rb +517 -244
  375. data/test/cases/test_case.rb +254 -60
  376. data/test/cases/test_case_test.rb +69 -0
  377. data/test/cases/test_databases_test.rb +7 -3
  378. data/test/cases/test_fixtures_test.rb +19 -40
  379. data/test/cases/time_precision_test.rb +17 -5
  380. data/test/cases/timestamp_test.rb +124 -16
  381. data/test/cases/token_for_test.rb +165 -0
  382. data/test/cases/touch_later_test.rb +43 -1
  383. data/test/cases/transaction_callbacks_test.rb +325 -8
  384. data/test/cases/transaction_instrumentation_test.rb +453 -0
  385. data/test/cases/transaction_isolation_test.rb +12 -8
  386. data/test/cases/transactions_test.rb +629 -112
  387. data/test/cases/type/date_time_test.rb +7 -1
  388. data/test/cases/type/time_test.rb +9 -0
  389. data/test/cases/type/type_map_test.rb +108 -73
  390. data/test/cases/type/unsigned_integer_test.rb +12 -0
  391. data/test/cases/type_caster/connection_test.rb +19 -0
  392. data/test/cases/type_test.rb +1 -1
  393. data/test/cases/unconnected_test.rb +7 -7
  394. data/test/cases/unsafe_raw_sql_test.rb +39 -8
  395. data/test/cases/validations/absence_validation_test.rb +1 -1
  396. data/test/cases/validations/association_validation_test.rb +25 -3
  397. data/test/cases/validations/numericality_validation_test.rb +3 -9
  398. data/test/cases/validations/presence_validation_test.rb +1 -1
  399. data/test/cases/validations/uniqueness_validation_test.rb +280 -40
  400. data/test/cases/validations_test.rb +2 -2
  401. data/test/cases/view_test.rb +33 -22
  402. data/test/cases/yaml_serialization_test.rb +23 -17
  403. data/test/config.example.yml +58 -56
  404. data/test/config.rb +1 -0
  405. data/test/fixtures/1_need_quoting.yml +2 -0
  406. data/test/fixtures/accounts.yml +5 -0
  407. data/test/fixtures/aircrafts.yml +7 -0
  408. data/test/fixtures/book_identifiers.yml +11 -0
  409. data/test/fixtures/books.yml +5 -4
  410. data/test/fixtures/cake_designers.yml +5 -0
  411. data/test/fixtures/cars.yml +6 -0
  412. data/test/fixtures/chefs.yml +5 -0
  413. data/test/fixtures/clothing_items.yml +23 -0
  414. data/test/fixtures/clubs.yml +1 -1
  415. data/test/fixtures/comments.yml +2 -1
  416. data/test/fixtures/cpk_authors.yml +8 -0
  417. data/test/fixtures/cpk_books.yml +21 -0
  418. data/test/fixtures/cpk_order_agreements.yml +13 -0
  419. data/test/fixtures/cpk_order_tags.yml +15 -0
  420. data/test/fixtures/cpk_orders.yml +11 -0
  421. data/test/fixtures/cpk_posts.yml +12 -0
  422. data/test/fixtures/cpk_reviews.yml +13 -0
  423. data/test/fixtures/cpk_tags.yml +11 -0
  424. data/test/fixtures/drink_designers.yml +5 -0
  425. data/test/fixtures/encrypted_book_that_ignores_cases.yml +4 -0
  426. data/test/fixtures/encrypted_books.yml +4 -0
  427. data/test/fixtures/fk_object_to_point_to.yml +2 -0
  428. data/test/fixtures/memberships.yml +15 -15
  429. data/test/fixtures/paragraphs.yml +5 -0
  430. data/test/fixtures/parrots.yml +4 -0
  431. data/test/fixtures/people.yml +2 -0
  432. data/test/fixtures/pirates.yml +2 -2
  433. data/test/fixtures/primary_key_error/primary_key_error.yml +6 -0
  434. data/test/fixtures/references.yml +3 -3
  435. data/test/fixtures/sharded_blog_posts.yml +14 -0
  436. data/test/fixtures/sharded_blog_posts_tags.yml +27 -0
  437. data/test/fixtures/sharded_blogs.yml +8 -0
  438. data/test/fixtures/sharded_comments.yml +22 -0
  439. data/test/fixtures/sharded_tags.yml +34 -0
  440. data/test/fixtures/sponsors.yml +2 -2
  441. data/test/fixtures/topics.yml +1 -0
  442. data/test/fixtures/treasures.yml +4 -0
  443. data/test/fixtures/virtual_columns.yml +5 -0
  444. data/test/fixtures/warehouse_things.yml +1 -1
  445. data/test/migrations/old_and_new_versions/20210716122844_add_people_description.rb +5 -0
  446. data/test/migrations/old_and_new_versions/20210716123013_add_people_number_of_legs.rb +5 -0
  447. data/test/migrations/old_and_new_versions/230_add_people_hobby.rb +5 -0
  448. data/test/migrations/old_and_new_versions/231_add_people_last_name.rb +5 -0
  449. data/test/migrations/scope/1_unscoped.rb +7 -0
  450. data/test/migrations/scope/2_mysql_only.mysql.rb +7 -0
  451. data/test/models/admin/user.rb +2 -0
  452. data/test/models/admin/user_json.rb +48 -0
  453. data/test/models/attachment.rb +7 -0
  454. data/test/models/author.rb +70 -3
  455. data/test/models/author_encrypted.rb +16 -0
  456. data/test/models/bird.rb +1 -1
  457. data/test/models/book.rb +18 -11
  458. data/test/models/book_destroy_async.rb +1 -1
  459. data/test/models/book_encrypted.rb +106 -0
  460. data/test/models/book_identifier.rb +5 -0
  461. data/test/models/branch.rb +11 -0
  462. data/test/models/bulb.rb +1 -1
  463. data/test/models/car.rb +8 -1
  464. data/test/models/cat.rb +1 -1
  465. data/test/models/category.rb +3 -1
  466. data/test/models/chat_message.rb +8 -0
  467. data/test/models/chef.rb +52 -0
  468. data/test/models/clothing_item.rb +12 -0
  469. data/test/models/club.rb +4 -1
  470. data/test/models/comment.rb +7 -4
  471. data/test/models/comment_overlapping_counter_cache.rb +15 -0
  472. data/test/models/company.rb +16 -2
  473. data/test/models/computer.rb +1 -0
  474. data/test/models/contact.rb +3 -3
  475. data/test/models/contract.rb +5 -1
  476. data/test/models/cpk/author.rb +9 -0
  477. data/test/models/cpk/book.rb +55 -0
  478. data/test/models/cpk/book_destroy_async.rb +9 -0
  479. data/test/models/cpk/car.rb +9 -0
  480. data/test/models/cpk/car_review.rb +9 -0
  481. data/test/models/cpk/chapter.rb +16 -0
  482. data/test/models/cpk/chapter_destroy_async.rb +10 -0
  483. data/test/models/cpk/comment.rb +9 -0
  484. data/test/models/cpk/order.rb +55 -0
  485. data/test/models/cpk/order_agreement.rb +10 -0
  486. data/test/models/cpk/order_tag.rb +10 -0
  487. data/test/models/cpk/post.rb +10 -0
  488. data/test/models/cpk/posts_tag.rb +10 -0
  489. data/test/models/cpk/review.rb +9 -0
  490. data/test/models/cpk/tag.rb +10 -0
  491. data/test/models/cpk.rb +15 -0
  492. data/test/models/customer.rb +3 -1
  493. data/test/models/destroy_async_parent.rb +1 -1
  494. data/test/models/developer.rb +44 -3
  495. data/test/models/discount.rb +4 -0
  496. data/test/models/drink_designer.rb +1 -0
  497. data/test/models/editor.rb +8 -0
  498. data/test/models/editorship.rb +6 -0
  499. data/test/models/entry.rb +5 -0
  500. data/test/models/essay.rb +6 -1
  501. data/test/models/essay_destroy_async.rb +1 -0
  502. data/test/models/eye.rb +87 -21
  503. data/test/models/face.rb +1 -1
  504. data/test/models/friendship.rb +3 -0
  505. data/test/models/hardback.rb +7 -0
  506. data/test/models/hotel.rb +2 -0
  507. data/test/models/human.rb +2 -2
  508. data/test/models/invoice.rb +2 -1
  509. data/test/models/invokes_an_undefined_method.rb +5 -0
  510. data/test/models/line_item.rb +6 -0
  511. data/test/models/member.rb +9 -4
  512. data/test/models/membership.rb +2 -2
  513. data/test/models/message.rb +2 -1
  514. data/test/models/need_quoting.rb +5 -0
  515. data/test/models/paragraph.rb +5 -0
  516. data/test/models/parrot.rb +6 -3
  517. data/test/models/person.rb +7 -4
  518. data/test/models/pirate.rb +4 -1
  519. data/test/models/pk_autopopulated_by_a_trigger_record.rb +5 -0
  520. data/test/models/post.rb +65 -2
  521. data/test/models/post_encrypted.rb +15 -0
  522. data/test/models/post_with_prefetched_pk.rb +15 -0
  523. data/test/models/project.rb +1 -0
  524. data/test/models/publication.rb +16 -0
  525. data/test/models/raises_argument_error.rb +5 -0
  526. data/test/models/raises_no_method_error.rb +5 -0
  527. data/test/models/recipient.rb +5 -0
  528. data/test/models/reference.rb +1 -1
  529. data/test/models/reply.rb +1 -1
  530. data/test/models/room.rb +3 -0
  531. data/test/models/sharded/blog.rb +12 -0
  532. data/test/models/sharded/blog_post.rb +17 -0
  533. data/test/models/sharded/blog_post_destroy_async.rb +14 -0
  534. data/test/models/sharded/blog_post_tag.rb +11 -0
  535. data/test/models/sharded/blog_post_with_revision.rb +11 -0
  536. data/test/models/sharded/comment.rb +12 -0
  537. data/test/models/sharded/comment_destroy_async.rb +12 -0
  538. data/test/models/sharded/tag.rb +11 -0
  539. data/test/models/sharded.rb +8 -0
  540. data/test/models/ship.rb +1 -1
  541. data/test/models/shipping_line.rb +11 -0
  542. data/test/models/subscriber.rb +1 -0
  543. data/test/models/subscription.rb +3 -1
  544. data/test/models/too_long_table_name.rb +5 -0
  545. data/test/models/topic.rb +3 -1
  546. data/test/models/traffic_light.rb +2 -2
  547. data/test/models/traffic_light_encrypted.rb +21 -0
  548. data/test/models/translation.rb +9 -0
  549. data/test/models/treasure.rb +1 -1
  550. data/test/models/tyre.rb +1 -1
  551. data/test/models/user.rb +16 -0
  552. data/test/models/user_with_invalid_relation.rb +28 -0
  553. data/test/models/uuid_comment.rb +5 -0
  554. data/test/models/uuid_entry.rb +5 -0
  555. data/test/models/uuid_message.rb +5 -0
  556. data/test/models/zine.rb +2 -1
  557. data/test/schema/mysql2_specific_schema.rb +26 -12
  558. data/test/schema/postgresql_specific_schema.rb +121 -13
  559. data/test/schema/schema.rb +361 -103
  560. data/test/schema/sqlite_specific_schema.rb +17 -6
  561. data/test/schema/trilogy_specific_schema.rb +96 -0
  562. data/test/support/adapter_helper.rb +104 -0
  563. data/test/support/async_helper.rb +15 -0
  564. data/test/support/config.rb +1 -1
  565. data/test/support/connection.rb +14 -2
  566. data/test/support/ddl_helper.rb +1 -1
  567. data/test/support/fake_adapter.rb +42 -0
  568. data/test/support/load_schema_helper.rb +22 -0
  569. data/test/support/marshal_compatibility_fixtures/IBM_DB/rails_6_1_topic.dump +0 -0
  570. data/test/support/marshal_compatibility_fixtures/IBM_DB/rails_6_1_topic_associations.dump +0 -0
  571. data/test/support/marshal_compatibility_fixtures/IBM_DB/rails_7_1_topic.dump +0 -0
  572. data/test/support/marshal_compatibility_fixtures/IBM_DB/rails_7_1_topic_associations.dump +0 -0
  573. data/test/support/marshal_compatibility_fixtures/Mysql2/rails_6_1_topic.dump +0 -0
  574. data/test/support/marshal_compatibility_fixtures/Mysql2/rails_6_1_topic_associations.dump +0 -0
  575. data/test/support/marshal_compatibility_fixtures/Mysql2/rails_7_1_topic.dump +0 -0
  576. data/test/support/marshal_compatibility_fixtures/Mysql2/rails_7_1_topic_associations.dump +0 -0
  577. data/test/support/marshal_compatibility_fixtures/PostgreSQL/rails_6_1_topic.dump +0 -0
  578. data/test/support/marshal_compatibility_fixtures/PostgreSQL/rails_6_1_topic_associations.dump +0 -0
  579. data/test/support/marshal_compatibility_fixtures/PostgreSQL/rails_7_1_topic.dump +0 -0
  580. data/test/support/marshal_compatibility_fixtures/PostgreSQL/rails_7_1_topic_associations.dump +0 -0
  581. data/test/support/marshal_compatibility_fixtures/SQLite/rails_6_1_topic.dump +0 -0
  582. data/test/support/marshal_compatibility_fixtures/SQLite/rails_6_1_topic_associations.dump +0 -0
  583. data/test/support/marshal_compatibility_fixtures/SQLite/rails_7_1_topic.dump +0 -0
  584. data/test/support/marshal_compatibility_fixtures/SQLite/rails_7_1_topic_associations.dump +0 -0
  585. data/test/support/marshal_compatibility_fixtures/Trilogy/rails_6_1_topic.dump +0 -0
  586. data/test/support/marshal_compatibility_fixtures/Trilogy/rails_6_1_topic_associations.dump +0 -0
  587. data/test/support/marshal_compatibility_fixtures/Trilogy/rails_7_1_topic.dump +0 -0
  588. data/test/support/marshal_compatibility_fixtures/Trilogy/rails_7_1_topic_associations.dump +0 -0
  589. data/test/support/schema_dumping_helper.rb +15 -9
  590. data/test/support/tools.rb +37 -0
  591. data/test/support/yaml_compatibility_fixtures/rails_4_1_no_symbol.yml +22 -0
  592. metadata +247 -15
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "pp"
3
4
  require "cases/helper"
4
5
  require "models/computer"
5
6
  require "models/developer"
@@ -9,6 +10,7 @@ require "models/categorization"
9
10
  require "models/category"
10
11
  require "models/post"
11
12
  require "models/author"
13
+ require "models/book"
12
14
  require "models/comment"
13
15
  require "models/tag"
14
16
  require "models/tagging"
@@ -26,10 +28,26 @@ require "models/parrot"
26
28
  require "models/bird"
27
29
  require "models/treasure"
28
30
  require "models/price_estimate"
31
+ require "models/invoice"
32
+ require "models/discount"
33
+ require "models/line_item"
34
+ require "models/shipping_line"
35
+ require "models/essay"
36
+ require "models/member"
37
+ require "models/membership"
38
+ require "models/sharded"
39
+ require "models/cpk"
40
+ require "models/member_detail"
41
+ require "models/organization"
42
+ require "models/dog"
43
+ require "models/other_dog"
44
+
29
45
 
30
46
  class AssociationsTest < ActiveRecord::TestCase
31
47
  fixtures :accounts, :companies, :developers, :projects, :developers_projects,
32
- :computers, :people, :readers, :authors, :author_addresses, :author_favorites
48
+ :computers, :people, :readers, :authors, :author_addresses, :author_favorites,
49
+ :comments, :posts, :sharded_blogs, :sharded_blog_posts, :sharded_comments, :sharded_tags, :sharded_blog_posts_tags,
50
+ :cpk_orders, :cpk_books, :cpk_reviews
33
51
 
34
52
  def test_eager_loading_should_not_change_count_of_children
35
53
  liquid = Liquid.create(name: "salty")
@@ -63,6 +81,16 @@ class AssociationsTest < ActiveRecord::TestCase
63
81
  assert_equal "Deck", ship.parts[0].name
64
82
  end
65
83
 
84
+ def test_loading_cpk_association_when_persisted_and_in_memory_differ
85
+ order = Cpk::Order.create!(id: [1, 2], status: "paid")
86
+ book = order.books.create!(id: [3, 4], title: "Book")
87
+
88
+ Cpk::Book.find(book.id).update_columns(title: "A different title")
89
+ order.books.load
90
+
91
+ assert_equal [3, 4], book.id
92
+ end
93
+
66
94
  def test_include_with_order_works
67
95
  assert_nothing_raised { Account.all.merge!(order: "id", includes: :firm).first }
68
96
  assert_nothing_raised { Account.all.merge!(order: :id, includes: :firm).first }
@@ -86,13 +114,13 @@ class AssociationsTest < ActiveRecord::TestCase
86
114
  firm = Firm.new("name" => "A New Firm, Inc")
87
115
  firm.save
88
116
  firm.clients.each { } # forcing to load all clients
89
- assert firm.clients.empty?, "New firm shouldn't have client objects"
117
+ assert_predicate firm.clients, :empty?, "New firm shouldn't have client objects"
90
118
  assert_equal 0, firm.clients.size, "New firm should have 0 clients"
91
119
 
92
120
  client = Client.new("name" => "TheClient.com", "firm_id" => firm.id)
93
121
  client.save
94
122
 
95
- assert firm.clients.empty?, "New firm should have cached no client objects"
123
+ assert_predicate firm.clients, :empty?, "New firm should have cached no client objects"
96
124
  assert_equal 0, firm.clients.size, "New firm should have cached 0 clients count"
97
125
 
98
126
  firm.clients.reload
@@ -115,10 +143,362 @@ class AssociationsTest < ActiveRecord::TestCase
115
143
  firm = companies(:first_firm)
116
144
  assert_equal [:foo], firm.association_with_references.references_values
117
145
  end
146
+
147
+ def test_belongs_to_a_model_with_composite_foreign_key_finds_associated_record
148
+ comment = sharded_comments(:great_comment_blog_post_one)
149
+ blog_post = sharded_blog_posts(:great_post_blog_one)
150
+
151
+ assert_equal(blog_post, comment.blog_post)
152
+ end
153
+
154
+ def test_belongs_to_a_model_with_composite_primary_key_sets_inverse_of
155
+ order = cpk_orders(:cpk_groceries_order_1)
156
+ store_id, _order_id = order.id
157
+ book = order.books.create!(id: [store_id, 4], title: "Book")
158
+
159
+ if current_adapter?(:IBM_DBAdapter)
160
+ assert_equal book.order, book.order.books.first.order
161
+ else
162
+ assert_same book.order, book.order.books.first.order
163
+ end
164
+ end
165
+
166
+ def test_belongs_to_a_cpk_model_by_id_attribute
167
+ order = cpk_orders(:cpk_groceries_order_1)
168
+ _order_shop_id, order_id = order.id
169
+ agreement = Cpk::OrderAgreement.create(order_id: order_id, signature: "signed")
170
+
171
+ assert_equal(order, agreement.order)
172
+ end
173
+
174
+ def test_belongs_to_a_model_with_composite_primary_key_uses_composite_pk_in_sql
175
+ comment = sharded_comments(:great_comment_blog_post_one)
176
+
177
+ sql = capture_sql do
178
+ comment.blog_post
179
+ end.first
180
+
181
+ assert_match(/(?:#{Regexp.escape(quote_table_name("sharded_blog_posts.blog_id"))}|sharded_blog_posts\.blog_id) =/, sql)
182
+ assert_match(/(?:#{Regexp.escape(quote_table_name("sharded_blog_posts.id"))}|sharded_blog_posts\.id) =/, sql)
183
+ end
184
+
185
+ def test_querying_by_whole_associated_records_using_query_constraints
186
+ comments = [sharded_comments(:great_comment_blog_post_one), sharded_comments(:great_comment_blog_post_two)]
187
+
188
+ blog_posts = Sharded::BlogPost.where(comments: comments).to_a
189
+
190
+ expected_posts = [sharded_blog_posts(:great_post_blog_one), sharded_blog_posts(:great_post_blog_two)]
191
+ assert_equal(expected_posts.map(&:id).sort, blog_posts.map(&:id).sort)
192
+ end
193
+
194
+ def test_querying_by_single_associated_record_works_using_query_constraints
195
+ comments = [sharded_comments(:great_comment_blog_post_one), sharded_comments(:great_comment_blog_post_two)]
196
+
197
+ blog_posts = Sharded::BlogPost.where(comments: comments.last).to_a
198
+
199
+ expected_posts = [sharded_blog_posts(:great_post_blog_two)]
200
+ assert_equal(expected_posts.map(&:id).sort, blog_posts.map(&:id).sort)
201
+ end
202
+
203
+ def test_querying_by_relation_with_composite_key
204
+ expected_posts = [sharded_blog_posts(:great_post_blog_one), sharded_blog_posts(:great_post_blog_two)]
205
+
206
+ blog_posts = Sharded::BlogPost.where(comments: Sharded::Comment.where(body: "I really enjoyed the post!")).to_a
207
+
208
+ assert_equal(expected_posts.map(&:id).sort, blog_posts.map(&:id).sort)
209
+ end
210
+
211
+ def test_has_many_association_with_composite_foreign_key_loads_records
212
+ blog_post = sharded_blog_posts(:great_post_blog_one)
213
+
214
+ comments = blog_post.comments.to_a
215
+ assert_includes(comments, sharded_comments(:wow_comment_blog_post_one))
216
+ assert_includes(comments, sharded_comments(:great_comment_blog_post_one))
217
+ end
218
+
219
+ def test_belongs_to_with_explicit_composite_foreign_key
220
+ car = Cpk::Car.create(make: "Tesla", model: "Model S")
221
+ review = Cpk::CarReview.create(car: car, comment: "Great car!", rating: 5)
222
+
223
+ review.reload
224
+
225
+ sql = capture_sql do
226
+ assert_equal(car, review.car)
227
+ end
228
+
229
+ assert_match(/(?:#{Regexp.escape(quote_table_name("cpk_cars.make"))}|cpk_cars\.make) =/, sql.first)
230
+ assert_match(/(?:#{Regexp.escape(quote_table_name("cpk_cars.model"))}|cpk_cars\.model) =/, sql.first)
231
+ end
232
+
233
+ def test_cpk_model_has_many_records_by_id_attribute
234
+ order = cpk_orders(:cpk_groceries_order_1)
235
+ _order_shop_id, order_id = order.id
236
+ agreements = 2.times.map { Cpk::OrderAgreement.create(order_id: order_id, signature: "signed") }
237
+
238
+ assert_equal(agreements.sort, order.order_agreements.to_a.sort)
239
+ end
240
+
241
+ def test_has_many_association_from_a_model_with_query_constraints_different_from_the_association
242
+ blog_post = sharded_blog_posts(:great_post_blog_one)
243
+ blog_post = Sharded::BlogPostWithRevision.find(blog_post.id)
244
+ comments = []
245
+ expected_comments = Sharded::Comment.where(blog_id: blog_post.blog_id, blog_post_id: blog_post.id).to_a
246
+
247
+ sql = capture_sql do
248
+ comments = blog_post.comments.to_a
249
+ end.first
250
+
251
+ assert_match(/WHERE .*(?:#{Regexp.escape(quote_table_name("sharded_comments.blog_id"))}|sharded_comments\.blog_id) =/, sql)
252
+ assert_not_empty(comments)
253
+ assert_equal(expected_comments.sort, comments.sort)
254
+ end
255
+
256
+ def test_query_constraints_over_three_without_defining_explicit_foreign_key_query_constraints_raises
257
+ Sharded::BlogPostWithRevision.has_many :comments_without_query_constraints, primary_key: [:blog_id, :id], class_name: "Comment"
258
+ blog_post = sharded_blog_posts(:great_post_blog_one)
259
+ blog_post = Sharded::BlogPostWithRevision.find(blog_post.id)
260
+
261
+ error = assert_raises ArgumentError do
262
+ blog_post.comments_without_query_constraints.to_a
263
+ end
264
+
265
+ assert_equal "The query constraints list on the `Sharded::BlogPostWithRevision` model has more than 2 attributes. Active Record is unable to derive the query constraints for the association. You need to explicitly define the query constraints for this association.", error.message
266
+ end
267
+
268
+ def test_model_with_composite_query_constraints_has_many_association_sql
269
+ blog_post = sharded_blog_posts(:great_post_blog_one)
270
+
271
+ sql = capture_sql do
272
+ blog_post.comments.to_a
273
+ end.first
274
+
275
+ assert_match(/(?:#{Regexp.escape(quote_table_name("sharded_comments.blog_post_id"))}|sharded_comments\.blog_post_id) =/, sql)
276
+ assert_match(/(?:#{Regexp.escape(quote_table_name("sharded_comments.blog_id"))}|sharded_comments\.blog_id) =/, sql)
277
+ end
278
+
279
+ def test_belongs_to_association_does_not_use_parent_query_constraints_if_not_configured_to
280
+ comment = sharded_comments(:great_comment_blog_post_one)
281
+ blog_post = Sharded::BlogPost.new(blog_id: comment.blog_id, title: "Following best practices")
282
+
283
+ comment.blog_post_by_id = blog_post
284
+
285
+ comment.save
286
+
287
+ assert_predicate blog_post, :persisted?
288
+ assert_equal(blog_post, comment.blog_post_by_id)
289
+ end
290
+
291
+ def test_polymorphic_belongs_to_uses_parent_query_constraints
292
+ parent_post = sharded_blog_posts(:great_post_blog_one)
293
+ child_post = Sharded::BlogPost.create!(title: "Child post", blog_id: parent_post.blog_id, parent: parent_post)
294
+ child_post.reload # reload to forget the parent association
295
+
296
+ assert_equal parent_post, child_post.parent
297
+ end
298
+
299
+ def test_preloads_model_with_query_constraints_by_explicitly_configured_fk_and_pk
300
+ comment = sharded_comments(:great_comment_blog_post_one)
301
+ comments = Sharded::Comment.where(id: comment.id).preload(:blog_post_by_id).to_a
302
+ comment = comments.first
303
+ assert_equal(comment.blog_post_by_id, comment.blog_post)
304
+ end
305
+
306
+ def test_append_composite_foreign_key_has_many_association
307
+ blog_post = sharded_blog_posts(:great_post_blog_one)
308
+ comment = Sharded::Comment.new(body: "Great post! :clap:")
309
+ comment.save
310
+ blog_post.comments << comment
311
+
312
+ assert_includes(blog_post.comments, comment)
313
+ assert_equal(blog_post.id, comment.blog_post_id)
314
+ assert_equal(blog_post.blog_id, comment.blog_id)
315
+ end
316
+
317
+ def test_nullify_composite_foreign_key_has_many_association
318
+ blog_post = sharded_blog_posts(:great_post_blog_one)
319
+ comment = sharded_comments(:great_comment_blog_post_one)
320
+
321
+ assert_not_empty(blog_post.comments)
322
+ blog_post.comments = []
323
+
324
+ comment = Sharded::Comment.find(comment.id)
325
+ assert_nil(comment.blog_post_id)
326
+ assert_nil(comment.blog_id)
327
+
328
+ assert_empty(blog_post.comments)
329
+ assert_empty(blog_post.reload.comments)
330
+ end
331
+
332
+ def test_assign_persisted_composite_foreign_key_belongs_to_association
333
+ comment = sharded_comments(:great_comment_blog_post_one)
334
+ another_blog = sharded_blogs(:sharded_blog_two)
335
+ assert_not_equal(comment.blog_id, another_blog.id)
336
+
337
+ blog_post = Sharded::BlogPost.new(title: "New post", blog_id: another_blog.id)
338
+ blog_post.save
339
+ comment.blog_post = blog_post
340
+
341
+ assert_equal(blog_post, comment.blog_post)
342
+ assert_equal(comment.blog_id, blog_post.blog_id)
343
+ assert_equal(another_blog.id, comment.blog_id)
344
+ assert_equal(comment.blog_post_id, blog_post.id)
345
+ end
346
+
347
+ def test_nullify_composite_foreign_key_belongs_to_association
348
+ comment = sharded_comments(:great_comment_blog_post_one)
349
+ assert_not_nil(comment.blog_post)
350
+
351
+ comment.blog_post = nil
352
+ assert_nil(comment.blog_id)
353
+ assert_nil(comment.blog_post_id)
354
+
355
+ comment.save
356
+ assert_nil(comment.blog_post)
357
+ assert_nil(comment.reload.blog_post)
358
+ end
359
+
360
+ def test_assign_composite_foreign_key_belongs_to_association
361
+ comment = sharded_comments(:great_comment_blog_post_one)
362
+ another_blog = sharded_blogs(:sharded_blog_two)
363
+ assert_not_equal(comment.blog_id, another_blog.id)
364
+
365
+ blog_post = Sharded::BlogPost.new(title: "New post", blog_id: another_blog.id)
366
+ comment.blog_post = blog_post
367
+
368
+ assert_equal(blog_post, comment.blog_post)
369
+ assert_equal(comment.blog_id, blog_post.blog_id)
370
+ assert_equal(another_blog.id, comment.blog_id)
371
+ end
372
+
373
+ def test_query_constraints_that_dont_include_the_primary_key_raise_with_a_single_column
374
+ original = Sharded::BlogPost.instance_variable_get(:@query_constraints_list)
375
+ Sharded::BlogPost.query_constraints :title
376
+ Sharded::BlogPost.has_many :comments_without_single_column_query_constraints, primary_key: [:blog_id, :id], class_name: "Comment"
377
+ blog_post = sharded_blog_posts(:great_post_blog_one)
378
+
379
+ error = assert_raises ArgumentError do
380
+ blog_post.comments_without_single_column_query_constraints.to_a
381
+ end
382
+
383
+ assert_equal "The query constraints on the `Sharded::BlogPost` model does not include the primary key so Active Record is unable to derive the foreign key constraints for the association. You need to explicitly define the query constraints for this association.", error.message
384
+ ensure
385
+ Sharded::BlogPost.instance_variable_set(:@query_constraints_list, original)
386
+ end
387
+
388
+ def test_query_constraints_that_dont_include_the_primary_key_raise_with_multiple_columns
389
+ original = Sharded::BlogPost.instance_variable_get(:@query_constraints_list)
390
+ Sharded::BlogPost.query_constraints :title, :revision
391
+ Sharded::BlogPost.has_many :comments_without_multiple_column_query_constraints, primary_key: [:blog_id, :id], class_name: "Comment"
392
+ blog_post = sharded_blog_posts(:great_post_blog_one)
393
+
394
+ error = assert_raises ArgumentError do
395
+ blog_post.comments_without_multiple_column_query_constraints.to_a
396
+ end
397
+
398
+ assert_equal "The query constraints on the `Sharded::BlogPost` model does not include the primary key so Active Record is unable to derive the foreign key constraints for the association. You need to explicitly define the query constraints for this association.", error.message
399
+ ensure
400
+ Sharded::BlogPost.instance_variable_set(:@query_constraints_list, original)
401
+ end
402
+
403
+ def test_assign_belongs_to_cpk_model_by_id_attribute
404
+ order = cpk_orders(:cpk_groceries_order_1)
405
+ agreement = Cpk::OrderAgreement.new(signature: "signed")
406
+
407
+ agreement.order = order
408
+ agreement.save
409
+
410
+ assert_not_nil(agreement.reload.order)
411
+ assert_not_nil(agreement.order_id)
412
+
413
+ assert_equal(order, agreement.order)
414
+ _shop_id, order_id = order.id
415
+ assert_equal(order_id, agreement.order_id)
416
+ end
417
+
418
+ def test_append_composite_foreign_key_has_many_association_with_autosave
419
+ blog_post = sharded_blog_posts(:great_post_blog_one)
420
+ comment = Sharded::Comment.new(body: "Great post! :clap:")
421
+ blog_post.comments << comment
422
+
423
+ assert_predicate(comment, :persisted?)
424
+ assert_includes(blog_post.comments, comment)
425
+ assert_equal(blog_post.id, comment.blog_post_id)
426
+ assert_equal(blog_post.blog_id, comment.blog_id)
427
+ end
428
+
429
+ def test_assign_composite_foreign_key_belongs_to_association_with_autosave
430
+ comment = sharded_comments(:great_comment_blog_post_one)
431
+ another_blog = sharded_blogs(:sharded_blog_two)
432
+ assert_not_equal(comment.blog_id, another_blog.id)
433
+
434
+ blog_post = Sharded::BlogPost.new(title: "New post", blog_id: another_blog.id)
435
+ comment.blog_post = blog_post
436
+ comment.save
437
+
438
+ assert_predicate(blog_post, :persisted?)
439
+ assert_equal(blog_post, comment.blog_post)
440
+ assert_equal(comment.blog_id, blog_post.blog_id)
441
+ assert_equal(another_blog.id, comment.blog_id)
442
+ assert_equal(comment.blog_post_id, blog_post.id)
443
+ end
444
+
445
+ def test_append_composite_has_many_through_association
446
+ blog_post = sharded_blog_posts(:great_post_blog_one)
447
+ tag = Sharded::Tag.new(name: "Ruby on Rails", blog_id: blog_post.blog_id)
448
+ tag.save
449
+
450
+ blog_post.tags << tag
451
+
452
+ assert_includes(blog_post.reload.tags, tag)
453
+ assert_predicate Sharded::BlogPostTag.where(blog_post_id: blog_post.id, blog_id: blog_post.blog_id, tag_id: tag.id), :exists?
454
+ end
455
+
456
+ def test_append_composite_has_many_through_association_with_autosave
457
+ blog_post = sharded_blog_posts(:great_post_blog_one)
458
+ tag = Sharded::Tag.new(name: "Ruby on Rails", blog_id: blog_post.blog_id)
459
+
460
+ blog_post.tags << tag
461
+
462
+ assert_includes(blog_post.reload.tags, tag)
463
+ assert_predicate Sharded::BlogPostTag.where(blog_post_id: blog_post.id, blog_id: blog_post.blog_id, tag_id: tag.id), :exists?
464
+ end
465
+
466
+ def test_nullify_composite_has_many_through_association
467
+ blog_post = sharded_blog_posts(:great_post_blog_one)
468
+ assert_not_empty(blog_post.tags)
469
+
470
+ blog_post.tags = []
471
+
472
+ assert_empty(blog_post.tags)
473
+ assert_empty(blog_post.reload.tags)
474
+ assert_not_predicate Sharded::BlogPostTag.where(blog_post_id: blog_post.id, blog_id: blog_post.blog_id), :exists?
475
+ end
476
+
477
+ def test_using_query_constraints_warns_about_changing_behavior
478
+ has_many_expected_message = <<~MSG.squish
479
+ Setting `query_constraints:` option on `Sharded::BlogPost.has_many :qc_deprecated_comments` is not allowed.
480
+ To get the same behavior, use the `foreign_key` option instead.
481
+ MSG
482
+
483
+ assert_raises(ActiveRecord::ConfigurationError, match: has_many_expected_message) do
484
+ Sharded::BlogPost.has_many :qc_deprecated_comments,
485
+ class_name: "Sharded::Comment", query_constraints: [:blog_id, :blog_post_id]
486
+ end
487
+
488
+ belongs_to_expected_message = <<~MSG.squish
489
+ Setting `query_constraints:` option on `Sharded::Comment.belongs_to :qc_deprecated_blog_post` is not allowed.
490
+ To get the same behavior, use the `foreign_key` option instead.
491
+ MSG
492
+
493
+ assert_raises(ActiveRecord::ConfigurationError, match: belongs_to_expected_message) do
494
+ Sharded::Comment.belongs_to :qc_deprecated_blog_post,
495
+ class_name: "Sharded::Blog", query_constraints: [:blog_id, :blog_post_id]
496
+ end
497
+ end
118
498
  end
119
499
 
120
500
  class AssociationProxyTest < ActiveRecord::TestCase
121
- fixtures :authors, :author_addresses, :posts, :categorizations, :categories, :developers, :projects, :developers_projects
501
+ fixtures :authors, :author_addresses, :posts, :categorizations, :categories, :developers, :projects, :developers_projects, :members
122
502
 
123
503
  def test_push_does_not_load_target
124
504
  david = authors(:david)
@@ -188,6 +568,15 @@ class AssociationProxyTest < ActiveRecord::TestCase
188
568
  assert_predicate andreas.audit_logs, :loaded?
189
569
  end
190
570
 
571
+ def test_pretty_print_does_not_reload_a_not_yet_loaded_target
572
+ andreas = Developer.new(log: "new developer added")
573
+ assert_not_predicate andreas.audit_logs, :loaded?
574
+ out = StringIO.new
575
+ PP.pp(andreas.audit_logs, out)
576
+ assert_match(/message: "new developer added"/, out.string)
577
+ assert_predicate andreas.audit_logs, :loaded?
578
+ end
579
+
191
580
  def test_save_on_parent_saves_children
192
581
  developer = Developer.create name: "Bryan", salary: 50_000
193
582
  assert_equal 1, developer.reload.audit_logs.size
@@ -195,14 +584,14 @@ class AssociationProxyTest < ActiveRecord::TestCase
195
584
 
196
585
  def test_create_via_association_with_block
197
586
  post = authors(:david).posts.create(title: "New on Edge") { |p| p.body = "More cool stuff!" }
198
- assert_equal post.title, "New on Edge"
199
- assert_equal post.body, "More cool stuff!"
587
+ assert_equal "New on Edge", post.title
588
+ assert_equal "More cool stuff!", post.body
200
589
  end
201
590
 
202
591
  def test_create_with_bang_via_association_with_block
203
592
  post = authors(:david).posts.create!(title: "New on Edge") { |p| p.body = "More cool stuff!" }
204
- assert_equal post.title, "New on Edge"
205
- assert_equal post.body, "More cool stuff!"
593
+ assert_equal "New on Edge", post.title
594
+ assert_equal "More cool stuff!", post.body
206
595
  end
207
596
 
208
597
  def test_reload_returns_association
@@ -246,7 +635,7 @@ class AssociationProxyTest < ActiveRecord::TestCase
246
635
 
247
636
  human = Human.find(human.id)
248
637
 
249
- assert_queries(1) do
638
+ assert_queries_count(1) do
250
639
  assert_equal human, human.interests.where("1=1").first.human
251
640
  end
252
641
  end
@@ -292,6 +681,41 @@ class AssociationProxyTest < ActiveRecord::TestCase
292
681
  assert_equal 1, david.thinking_posts.size
293
682
  assert_equal 1, david.thinking_posts.to_a.size
294
683
  end
684
+
685
+ def test_target_merging_ignores_persisted_in_memory_records_when_loaded_records_are_empty
686
+ member = members(:blarpy_winkup)
687
+ assert_empty member.favorite_memberships
688
+
689
+ membership = member.favorite_memberships.create!
690
+ membership.update!(favorite: false)
691
+
692
+ assert_empty member.favorite_memberships.to_a
693
+ end
694
+
695
+ def test_target_merging_recognizes_updated_in_memory_records
696
+ member = members(:blarpy_winkup)
697
+ membership = member.create_membership!(favorite: false)
698
+
699
+ assert_empty member.favorite_memberships
700
+
701
+ membership.update!(favorite: true)
702
+
703
+ assert_not_empty member.favorite_memberships.to_a
704
+ end
705
+
706
+ def test_size_differentiates_between_new_and_persisted_in_memory_records_when_loaded_records_are_empty
707
+ member = members(:blarpy_winkup)
708
+ assert_empty member.favorite_memberships
709
+
710
+ membership = member.favorite_memberships.create!
711
+ membership.update!(favorite: false)
712
+
713
+ # CollectionAssociation#size has different behavior when loaded vs. non-loaded
714
+ # the first call will mark the association as loaded and the second call will
715
+ # take a different code path, so it's important to keep both assertions
716
+ assert_equal 0, member.favorite_memberships.size
717
+ assert_equal 0, member.favorite_memberships.size
718
+ end
295
719
  end
296
720
 
297
721
  class OverridingAssociationsTest < ActiveRecord::TestCase
@@ -363,32 +787,865 @@ class OverridingAssociationsTest < ActiveRecord::TestCase
363
787
  end
364
788
  end
365
789
  end
790
+
791
+ class ModelAssociatedToClassesThatDoNotExist < ActiveRecord::Base
792
+ self.table_name = "accounts" # this is just to avoid adding a new model just for this test
793
+
794
+ has_one :non_existent_has_one_class
795
+ belongs_to :non_existent_belongs_to_class
796
+ has_many :non_existent_has_many_classes
797
+ end
798
+
799
+ def test_associations_raise_with_name_error_if_associated_to_classes_that_do_not_exist
800
+ assert_raises NameError do
801
+ ModelAssociatedToClassesThatDoNotExist.new.non_existent_has_one_class
802
+ end
803
+
804
+ assert_raises NameError do
805
+ ModelAssociatedToClassesThatDoNotExist.new.non_existent_belongs_to_class
806
+ end
807
+
808
+ assert_raises NameError do
809
+ ModelAssociatedToClassesThatDoNotExist.new.non_existent_has_many_classes
810
+ end
811
+ end
366
812
  end
367
813
 
368
814
  class PreloaderTest < ActiveRecord::TestCase
369
- fixtures :posts, :comments
815
+ fixtures :posts, :comments, :books, :authors, :tags, :taggings, :essays, :categories, :author_addresses,
816
+ :sharded_blog_posts, :sharded_comments, :sharded_blog_posts_tags, :sharded_tags,
817
+ :members, :member_details, :organizations, :cpk_authors, :cpk_orders, :cpk_books, :cpk_order_agreements,
818
+ :dogs, :other_dogs
370
819
 
371
820
  def test_preload_with_scope
372
821
  post = posts(:welcome)
373
822
 
374
- preloader = ActiveRecord::Associations::Preloader.new
375
- preloader.preload([post], :comments, Comment.where(body: "Thank you for the welcome"))
823
+ preloader = ActiveRecord::Associations::Preloader.new(records: [post], associations: :comments, scope: Comment.where(body: "Thank you for the welcome"))
824
+ preloader.call
376
825
 
377
826
  assert_predicate post.comments, :loaded?
378
827
  assert_equal [comments(:greetings)], post.comments
379
828
  end
380
829
 
830
+ def test_preload_makes_correct_number_of_queries_on_array
831
+ post = posts(:welcome)
832
+
833
+ assert_queries_count(1) do
834
+ preloader = ActiveRecord::Associations::Preloader.new(records: [post], associations: :comments)
835
+ preloader.call
836
+ end
837
+ end
838
+
839
+ def test_preload_makes_correct_number_of_queries_on_relation
840
+ post = posts(:welcome)
841
+ relation = Post.where(id: post.id)
842
+
843
+ assert_queries_count(2) do
844
+ preloader = ActiveRecord::Associations::Preloader.new(records: relation, associations: :comments)
845
+ preloader.call
846
+ end
847
+ end
848
+
849
+ def test_preload_does_not_concatenate_duplicate_records
850
+ post = posts(:welcome)
851
+ post.reload
852
+ post.comments.create!(body: "A new comment")
853
+
854
+ ActiveRecord::Associations::Preloader.new(records: [post], associations: :comments).call
855
+
856
+ assert_equal post.comments.length, post.comments.count
857
+ assert_equal post.comments.all.to_a, post.comments
858
+ end
859
+
381
860
  def test_preload_for_hmt_with_conditions
382
861
  post = posts(:welcome)
383
862
  _normal_category = post.categories.create!(name: "Normal")
384
863
  special_category = post.special_categories.create!(name: "Special")
385
864
 
386
- preloader = ActiveRecord::Associations::Preloader.new
387
- preloader.preload([post], :hmt_special_categories)
865
+ preloader = ActiveRecord::Associations::Preloader.new(records: [post], associations: :hmt_special_categories)
866
+ preloader.call
388
867
 
389
868
  assert_equal 1, post.hmt_special_categories.length
390
869
  assert_equal [special_category], post.hmt_special_categories
391
870
  end
871
+
872
+ def test_preload_groups_queries_with_same_scope
873
+ book = books(:awdr)
874
+ post = posts(:welcome)
875
+
876
+ assert_queries_count(1) do
877
+ preloader = ActiveRecord::Associations::Preloader.new(records: [book, post], associations: :author)
878
+ preloader.call
879
+ end
880
+
881
+ assert_no_queries do
882
+ book.author
883
+ post.author
884
+ end
885
+ end
886
+
887
+ def test_preload_grouped_queries_with_already_loaded_records
888
+ book = books(:awdr)
889
+ post = posts(:welcome)
890
+ book.author
891
+
892
+ assert_no_queries do
893
+ ActiveRecord::Associations::Preloader.new(records: [book, post], associations: :author).call
894
+ book.author
895
+ post.author
896
+ end
897
+ end
898
+
899
+ def test_preload_grouped_queries_of_middle_records
900
+ comments = [
901
+ comments(:eager_sti_on_associations_s_comment1),
902
+ comments(:eager_sti_on_associations_s_comment2),
903
+ ]
904
+
905
+ assert_queries_count(2) do
906
+ ActiveRecord::Associations::Preloader.new(records: comments, associations: [:author, :ordinary_post]).call
907
+ end
908
+ end
909
+
910
+ def test_preload_grouped_queries_of_through_records
911
+ author = authors(:david)
912
+
913
+ assert_queries_count(3) do
914
+ ActiveRecord::Associations::Preloader.new(records: [author], associations: [:hello_post_comments, :comments]).call
915
+ end
916
+ end
917
+
918
+ def test_preload_through_records_with_already_loaded_middle_record
919
+ member = members(:groucho)
920
+ expected_member_detail_ids = member.organization_member_details_2.pluck(:id)
921
+
922
+ member.reload.organization # load through record
923
+
924
+ assert_queries_count(1) do
925
+ ActiveRecord::Associations::Preloader.new(records: [member], associations: :organization_member_details_2).call
926
+ end
927
+
928
+ assert_no_queries do
929
+ assert_equal expected_member_detail_ids.sort, member.organization_member_details_2.map(&:id).sort
930
+ end
931
+ end
932
+
933
+ def test_preload_with_instance_dependent_scope
934
+ david = authors(:david)
935
+ david2 = Author.create!(name: "David")
936
+ bob = authors(:bob)
937
+ post = Post.create!(
938
+ author: david,
939
+ title: "test post",
940
+ body: "this post is about David"
941
+ )
942
+ post2 = Post.create!(
943
+ author: david,
944
+ title: "test post 2",
945
+ body: "this post is also about David"
946
+ )
947
+
948
+ assert_queries_count(2) do
949
+ preloader = ActiveRecord::Associations::Preloader.new(records: [david, david2, bob], associations: :posts_mentioning_author)
950
+ preloader.call
951
+ end
952
+
953
+ assert_predicate david.posts_mentioning_author, :loaded?
954
+ assert_predicate david2.posts_mentioning_author, :loaded?
955
+ assert_predicate bob.posts_mentioning_author, :loaded?
956
+
957
+ if current_adapter?(:IBM_DBAdapter)
958
+ # DB2 matches LIKE case-sensitively in this environment.
959
+ assert_equal [], david.posts_mentioning_author
960
+ else
961
+ assert_equal [post, post2].sort, david.posts_mentioning_author.sort
962
+ end
963
+ assert_equal [], david2.posts_mentioning_author
964
+ assert_equal [], bob.posts_mentioning_author
965
+ end
966
+
967
+ def test_preload_with_instance_dependent_through_scope
968
+ david = authors(:david)
969
+ david2 = Author.create!(name: "David")
970
+ bob = authors(:bob)
971
+ comment1 = david.posts.first.comments.create!(body: "Hi David!")
972
+ comment2 = david.posts.first.comments.create!(body: "This comment mentions david")
973
+
974
+ assert_queries_count(2) do
975
+ preloader = ActiveRecord::Associations::Preloader.new(records: [david, david2, bob], associations: :comments_mentioning_author)
976
+ preloader.call
977
+ end
978
+
979
+ assert_predicate david.comments_mentioning_author, :loaded?
980
+ assert_predicate david2.comments_mentioning_author, :loaded?
981
+ assert_predicate bob.comments_mentioning_author, :loaded?
982
+
983
+ if current_adapter?(:IBM_DBAdapter)
984
+ # DB2 matches LIKE case-sensitively in this environment.
985
+ assert_equal [comment2], david.comments_mentioning_author
986
+ else
987
+ assert_equal [comment1, comment2].sort, david.comments_mentioning_author.sort
988
+ end
989
+ assert_equal [], david2.comments_mentioning_author
990
+ assert_equal [], bob.comments_mentioning_author
991
+ end
992
+
993
+ def test_preload_with_through_instance_dependent_scope
994
+ david = authors(:david)
995
+ david2 = Author.create!(name: "David")
996
+ bob = authors(:bob)
997
+ post = Post.create!(
998
+ author: david,
999
+ title: "test post",
1000
+ body: "this post is about David"
1001
+ )
1002
+ Post.create!(
1003
+ author: david,
1004
+ title: "test post 2",
1005
+ body: "this post is also about David"
1006
+ )
1007
+ post3 = Post.create!(
1008
+ author: bob,
1009
+ title: "test post 3",
1010
+ body: "this post is about Bob"
1011
+ )
1012
+ comment1 = post.comments.create!(body: "hi!")
1013
+ comment2 = post.comments.create!(body: "hello!")
1014
+ comment3 = post3.comments.create!(body: "HI BOB!")
1015
+
1016
+ expected_queries = current_adapter?(:IBM_DBAdapter) ? 2 : 3
1017
+ assert_queries_count(expected_queries) do
1018
+ preloader = ActiveRecord::Associations::Preloader.new(records: [david, david2, bob], associations: :comments_on_posts_mentioning_author)
1019
+ preloader.call
1020
+ end
1021
+
1022
+ assert_predicate david.comments_on_posts_mentioning_author, :loaded?
1023
+ assert_predicate david2.comments_on_posts_mentioning_author, :loaded?
1024
+ assert_predicate bob.comments_on_posts_mentioning_author, :loaded?
1025
+
1026
+ if current_adapter?(:IBM_DBAdapter)
1027
+ # DB2 matches LIKE case-sensitively in this environment.
1028
+ assert_equal [], david.comments_on_posts_mentioning_author
1029
+ assert_equal [], bob.comments_on_posts_mentioning_author
1030
+ else
1031
+ assert_equal [comment1, comment2].sort, david.comments_on_posts_mentioning_author.sort
1032
+ assert_equal [comment3], bob.comments_on_posts_mentioning_author
1033
+ end
1034
+ assert_equal [], david2.comments_on_posts_mentioning_author
1035
+ end
1036
+
1037
+ def test_some_already_loaded_associations
1038
+ item_discount = Discount.create(amount: 5)
1039
+ shipping_discount = Discount.create(amount: 20)
1040
+
1041
+ invoice = Invoice.new
1042
+ line_item = LineItem.new(amount: 20)
1043
+ line_item.discount_applications << LineItemDiscountApplication.new(discount: item_discount)
1044
+ invoice.line_items << line_item
1045
+
1046
+ shipping_line = ShippingLine.new(amount: 50)
1047
+ shipping_line.discount_applications << ShippingLineDiscountApplication.new(discount: shipping_discount)
1048
+ invoice.shipping_lines << shipping_line
1049
+
1050
+ invoice.save!
1051
+ invoice.reload
1052
+
1053
+ # SELECT "line_items".* FROM "line_items" WHERE "line_items"."invoice_id" = ?
1054
+ # SELECT "shipping_lines".* FROM shipping_lines WHERE "shipping_lines"."invoice_id" = ?
1055
+ # SELECT "line_item_discount_applications".* FROM "line_item_discount_applications" WHERE "line_item_discount_applications"."line_item_id" = ?
1056
+ # SELECT "shipping_line_discount_applications".* FROM "shipping_line_discount_applications" WHERE "shipping_line_discount_applications"."shipping_line_id" = ?
1057
+ # SELECT "discounts".* FROM "discounts" WHERE "discounts"."id" IN (?, ?).
1058
+ assert_queries_count(5) do
1059
+ preloader = ActiveRecord::Associations::Preloader.new(records: [invoice], associations: [
1060
+ line_items: { discount_applications: :discount },
1061
+ shipping_lines: { discount_applications: :discount },
1062
+ ])
1063
+ preloader.call
1064
+ end
1065
+
1066
+ assert_no_queries do
1067
+ assert_not_nil invoice.line_items.first.discount_applications.first.discount
1068
+ assert_not_nil invoice.shipping_lines.first.discount_applications.first.discount
1069
+ end
1070
+
1071
+ invoice.reload
1072
+ invoice.line_items.map { |i| i.discount_applications.to_a }
1073
+ # `line_items` and `line_item_discount_applications` are already preloaded, so we expect:
1074
+ # SELECT "shipping_lines".* FROM shipping_lines WHERE "shipping_lines"."invoice_id" = ?
1075
+ # SELECT "shipping_line_discount_applications".* FROM "shipping_line_discount_applications" WHERE "shipping_line_discount_applications"."shipping_line_id" = ?
1076
+ # SELECT "discounts".* FROM "discounts" WHERE "discounts"."id" = ?.
1077
+ assert_queries_count(3) do
1078
+ preloader = ActiveRecord::Associations::Preloader.new(records: [invoice], associations: [
1079
+ line_items: { discount_applications: :discount },
1080
+ shipping_lines: { discount_applications: :discount },
1081
+ ])
1082
+ preloader.call
1083
+ end
1084
+
1085
+ assert_no_queries do
1086
+ assert_not_nil invoice.line_items.first.discount_applications.first.discount
1087
+ assert_not_nil invoice.shipping_lines.first.discount_applications.first.discount
1088
+ end
1089
+ end
1090
+
1091
+ def test_preload_through
1092
+ comments = [
1093
+ comments(:eager_sti_on_associations_s_comment1),
1094
+ comments(:eager_sti_on_associations_s_comment2),
1095
+ ]
1096
+
1097
+ assert_queries_count(2) do
1098
+ preloader = ActiveRecord::Associations::Preloader.new(records: comments, associations: [:author, :post])
1099
+ preloader.call
1100
+ end
1101
+
1102
+ assert_no_queries do
1103
+ comments.each(&:author)
1104
+ end
1105
+ end
1106
+
1107
+ def test_preload_groups_queries_with_same_scope_at_second_level
1108
+ author = nil
1109
+
1110
+ # Expected
1111
+ # SELECT FROM authors ...
1112
+ # SELECT FROM posts ... (thinking)
1113
+ # SELECT FROM posts ... (welcome)
1114
+ # SELECT FROM comments ... (comments for both welcome and thinking)
1115
+ assert_queries_count(4) do
1116
+ author = Author
1117
+ .where(name: "David")
1118
+ .includes(thinking_posts: :comments, welcome_posts: :comments)
1119
+ .first
1120
+ end
1121
+
1122
+ assert_no_queries do
1123
+ author.thinking_posts.map(&:comments)
1124
+ author.welcome_posts.map(&:comments)
1125
+ end
1126
+ end
1127
+
1128
+ def test_preload_groups_queries_with_same_sql_at_second_level
1129
+ author = nil
1130
+
1131
+ # Expected
1132
+ # SELECT FROM authors ...
1133
+ # SELECT FROM posts ... (thinking)
1134
+ # SELECT FROM posts ... (welcome)
1135
+ # SELECT FROM comments ... (comments for both welcome and thinking)
1136
+ assert_queries_count(4) do
1137
+ author = Author
1138
+ .where(name: "David")
1139
+ .includes(thinking_posts: :comments, welcome_posts: :comments_with_extending)
1140
+ .first
1141
+ end
1142
+
1143
+ assert_no_queries do
1144
+ author.thinking_posts.map(&:comments)
1145
+ author.welcome_posts.map(&:comments_with_extending)
1146
+ end
1147
+ end
1148
+
1149
+ def test_preload_with_grouping_sets_inverse_association
1150
+ mary = authors(:mary)
1151
+ bob = authors(:bob)
1152
+
1153
+ AuthorFavorite.create!(author: mary, favorite_author: bob)
1154
+ favorites = AuthorFavorite.all.load
1155
+
1156
+ assert_queries_count(1) do
1157
+ preloader = ActiveRecord::Associations::Preloader.new(records: favorites, associations: [:author, :favorite_author])
1158
+ preloader.call
1159
+ end
1160
+
1161
+ assert_no_queries do
1162
+ favorites.first.author
1163
+ favorites.first.favorite_author
1164
+ end
1165
+ end
1166
+
1167
+ def test_preload_can_group_separate_levels
1168
+ mary = authors(:mary)
1169
+ bob = authors(:bob)
1170
+
1171
+ AuthorFavorite.create!(author: mary, favorite_author: bob)
1172
+
1173
+ assert_queries_count(3) do
1174
+ preloader = ActiveRecord::Associations::Preloader.new(records: [mary], associations: [:posts, favorite_authors: :posts])
1175
+ preloader.call
1176
+ end
1177
+
1178
+ assert_no_queries do
1179
+ mary.posts
1180
+ mary.favorite_authors.map(&:posts)
1181
+ end
1182
+ end
1183
+
1184
+ def test_preload_can_group_multi_level_ping_pong_through
1185
+ skip "DB2 does not support this CLOB string operation pattern" if current_adapter?(:IBM_DBAdapter)
1186
+
1187
+ mary = authors(:mary)
1188
+ bob = authors(:bob)
1189
+
1190
+ AuthorFavorite.create!(author: mary, favorite_author: bob)
1191
+
1192
+ associations = { similar_posts: :comments, favorite_authors: { similar_posts: :comments } }
1193
+
1194
+ assert_queries_count(9) do
1195
+ preloader = ActiveRecord::Associations::Preloader.new(records: [mary], associations: associations)
1196
+ preloader.call
1197
+ end
1198
+
1199
+ assert_no_queries do
1200
+ mary.similar_posts.map(&:comments).each(&:to_a)
1201
+ mary.favorite_authors.flat_map(&:similar_posts).map(&:comments).each(&:to_a)
1202
+ end
1203
+
1204
+ # Preloading with automatic scope inversing reduces the number of queries
1205
+ tag_reflection = Tagging.reflect_on_association(:tag)
1206
+ taggings_reflection = Tag.reflect_on_association(:taggings)
1207
+
1208
+ assert tag_reflection.scope
1209
+ assert_not taggings_reflection.scope
1210
+
1211
+ with_automatic_scope_inversing(tag_reflection, taggings_reflection) do
1212
+ mary.reload
1213
+
1214
+ assert_queries_count(8) do
1215
+ preloader = ActiveRecord::Associations::Preloader.new(records: [mary], associations: associations)
1216
+ preloader.call
1217
+ end
1218
+ end
1219
+ end
1220
+
1221
+ def test_preload_does_not_group_same_class_different_scope
1222
+ post = posts(:welcome)
1223
+ postesque = Postesque.create(author: Author.last)
1224
+ postesque.reload
1225
+
1226
+ # When the scopes differ in the generated SQL:
1227
+ # SELECT "authors".* FROM "authors" WHERE (name LIKE '%a%') AND "authors"."id" = ?
1228
+ # SELECT "authors".* FROM "authors" WHERE "authors"."id" = ?.
1229
+ assert_queries_count(2) do
1230
+ preloader = ActiveRecord::Associations::Preloader.new(records: [post, postesque], associations: :author_with_the_letter_a)
1231
+ preloader.call
1232
+ end
1233
+
1234
+ assert_no_queries do
1235
+ post.author_with_the_letter_a
1236
+ postesque.author_with_the_letter_a
1237
+ end
1238
+
1239
+ post.reload
1240
+ postesque.reload
1241
+
1242
+ # When the generated SQL is identical, but one scope has preload values.
1243
+ assert_queries_count(3) do
1244
+ preloader = ActiveRecord::Associations::Preloader.new(records: [post, postesque], associations: :author_with_address)
1245
+ preloader.call
1246
+ end
1247
+
1248
+ assert_no_queries do
1249
+ post.author_with_address
1250
+ postesque.author_with_address
1251
+ end
1252
+ end
1253
+
1254
+ def test_preload_does_not_group_same_scope_different_key_name
1255
+ post = posts(:welcome)
1256
+ postesque = Postesque.create(author: Author.last)
1257
+ postesque.reload
1258
+
1259
+ assert_queries_count(2) do
1260
+ preloader = ActiveRecord::Associations::Preloader.new(records: [post, postesque], associations: :author)
1261
+ preloader.call
1262
+ end
1263
+
1264
+ assert_no_queries do
1265
+ post.author
1266
+ postesque.author
1267
+ end
1268
+ end
1269
+
1270
+ def test_multi_database_polymorphic_preload_with_same_table_name
1271
+ dog = dogs(:sophie)
1272
+ dog_comment = comments(:greetings)
1273
+ dog_comment.origin_type = dog.class.name
1274
+ dog_comment.origin_id = dog.id
1275
+
1276
+ other_dog = other_dogs(:lassie)
1277
+ other_dog_comment = comments(:more_greetings)
1278
+ other_dog_comment.origin_type = other_dog.class.name
1279
+ other_dog_comment.origin_id = other_dog.id
1280
+
1281
+ # Both Dog and OtherDog are backed by a table named `dogs`,
1282
+ # however they are stored in different databases and should
1283
+ # therefore result in two separate queries rather than be batched
1284
+ # together.
1285
+ #
1286
+ # Expected
1287
+ # SELECT FROM dogs ... (Dog)
1288
+ # SELECT FROM dogs ... (OtherDog)
1289
+ assert_queries_count(2) do
1290
+ preloader = ActiveRecord::Associations::Preloader.new(records: [dog_comment, other_dog_comment], associations: :origin)
1291
+ preloader.call
1292
+ end
1293
+ end
1294
+
1295
+ def test_preload_with_available_records
1296
+ post = posts(:welcome)
1297
+ david = authors(:david)
1298
+
1299
+ assert_no_queries do
1300
+ ActiveRecord::Associations::Preloader.new(records: [post], associations: :author, available_records: [[david]]).call
1301
+
1302
+ assert_predicate post.association(:author), :loaded?
1303
+ assert_same david, post.author
1304
+ end
1305
+ end
1306
+
1307
+ def test_preload_with_available_records_sti
1308
+ book = Book.create!
1309
+ essay_special = EssaySpecial.create!
1310
+ book.essay = essay_special
1311
+ book.save!
1312
+ book.reload
1313
+
1314
+ assert_not_predicate book.association(:essay), :loaded?
1315
+
1316
+ assert_no_queries do
1317
+ ActiveRecord::Associations::Preloader.new(records: [book], associations: :essay, available_records: [[essay_special]]).call
1318
+ end
1319
+
1320
+ assert_predicate book.association(:essay), :loaded?
1321
+ assert_same essay_special, book.essay
1322
+ end
1323
+
1324
+ def test_preload_with_only_some_records_available
1325
+ bob_post = posts(:misc_by_bob)
1326
+ mary_post = posts(:misc_by_mary)
1327
+ bob = authors(:bob)
1328
+ mary = authors(:mary)
1329
+
1330
+ assert_queries_count(1) do
1331
+ ActiveRecord::Associations::Preloader.new(records: [bob_post, mary_post], associations: :author, available_records: [bob]).call
1332
+ end
1333
+
1334
+ assert_no_queries do
1335
+ assert_same bob, bob_post.author
1336
+ assert_equal mary, mary_post.author
1337
+ end
1338
+ end
1339
+
1340
+ def test_preload_with_some_records_already_loaded
1341
+ bob_post = posts(:misc_by_bob)
1342
+ mary_post = posts(:misc_by_mary)
1343
+ bob = bob_post.author
1344
+ mary = authors(:mary)
1345
+
1346
+ assert_predicate bob_post.association(:author), :loaded?
1347
+ assert_not mary_post.association(:author).loaded?
1348
+
1349
+ assert_queries_count(1) do
1350
+ ActiveRecord::Associations::Preloader.new(records: [bob_post, mary_post], associations: :author).call
1351
+ end
1352
+
1353
+ assert_no_queries do
1354
+ assert_same bob, bob_post.author
1355
+ assert_equal mary, mary_post.author
1356
+ end
1357
+ end
1358
+
1359
+ def test_preload_with_available_records_with_through_association
1360
+ author = authors(:david)
1361
+ categories = Category.all.to_a
1362
+
1363
+ assert_queries_count(1) do
1364
+ # One query to get the middle records (i.e. essays)
1365
+ ActiveRecord::Associations::Preloader.new(records: [author], associations: :essay_category, available_records: categories).call
1366
+ end
1367
+
1368
+ assert_predicate author.association(:essay_category), :loaded?
1369
+ assert categories.map(&:__id__).include?(author.essay_category.__id__)
1370
+ end
1371
+
1372
+ def test_preload_with_only_some_records_available_with_through_associations
1373
+ mary = authors(:mary)
1374
+ mary_essay = essays(:mary_stay_home)
1375
+ mary_category = categories(:technology)
1376
+ mary_essay.update!(category: mary_category)
1377
+
1378
+ dave = authors(:david)
1379
+ dave_category = categories(:general)
1380
+
1381
+ assert_queries_count(2) do
1382
+ ActiveRecord::Associations::Preloader.new(records: [mary, dave], associations: :essay_category, available_records: [mary_category]).call
1383
+ end
1384
+
1385
+ assert_no_queries do
1386
+ assert_same mary_category, mary.essay_category
1387
+ assert_equal dave_category, dave.essay_category
1388
+ end
1389
+ end
1390
+
1391
+ def test_preload_with_available_records_with_multiple_classes
1392
+ essay = essays(:david_modest_proposal)
1393
+ general = categories(:general)
1394
+ david = authors(:david)
1395
+
1396
+ assert_no_queries do
1397
+ ActiveRecord::Associations::Preloader.new(records: [essay], associations: [:category, :author], available_records: [general, david]).call
1398
+
1399
+ assert_predicate essay.association(:category), :loaded?
1400
+ assert_predicate essay.association(:author), :loaded?
1401
+ assert_same general, essay.category
1402
+ assert_same david, essay.author
1403
+ end
1404
+ end
1405
+
1406
+ def test_preload_with_available_records_queries_when_scoped
1407
+ post = posts(:welcome)
1408
+ david = authors(:david)
1409
+
1410
+ assert_queries_count(1) do
1411
+ ActiveRecord::Associations::Preloader.new(records: [post], associations: :author, scope: Author.where(name: "David"), available_records: [david]).call
1412
+ end
1413
+
1414
+ assert_predicate post.association(:author), :loaded?
1415
+ assert_not_equal david.__id__, post.author.__id__
1416
+ end
1417
+
1418
+ def test_preload_with_available_records_queries_when_collection
1419
+ post = posts(:welcome)
1420
+ comments = Comment.all.to_a
1421
+
1422
+ assert_queries_count(1) do
1423
+ ActiveRecord::Associations::Preloader.new(records: [post], associations: :comments, available_records: comments).call
1424
+ end
1425
+
1426
+ assert_predicate post.association(:comments), :loaded?
1427
+ assert_empty post.comments.map(&:__id__) & comments.map(&:__id__)
1428
+ end
1429
+
1430
+ def test_preload_with_available_records_queries_when_incomplete
1431
+ post = posts(:welcome)
1432
+ bob = authors(:bob)
1433
+ david = authors(:david)
1434
+
1435
+ assert_queries_count(1) do
1436
+ ActiveRecord::Associations::Preloader.new(records: [post], associations: :author, available_records: [bob]).call
1437
+ end
1438
+
1439
+ assert_no_queries do
1440
+ assert_predicate post.association(:author), :loaded?
1441
+ assert_equal david, post.author
1442
+ end
1443
+ end
1444
+
1445
+ def test_preload_with_unpersisted_records_no_ops
1446
+ author = Author.new
1447
+ new_post_with_author = Post.new(author: author)
1448
+ new_post_without_author = Post.new
1449
+ posts = [new_post_with_author, new_post_without_author]
1450
+
1451
+ assert_no_queries do
1452
+ ActiveRecord::Associations::Preloader.new(records: posts, associations: :author).call
1453
+
1454
+ assert_same author, new_post_with_author.author
1455
+ assert_nil new_post_without_author.author
1456
+ end
1457
+ end
1458
+
1459
+ def test_preload_with_unpersisted_records_with_composite_foreign_key_no_ops
1460
+ skip "DB2 preloader currently materializes persisted CPK order records for nil composite keys" if current_adapter?(:IBM_DBAdapter)
1461
+
1462
+ order = Cpk::Order.new
1463
+ new_book_with_order = Cpk::Book.new(order: order)
1464
+ new_book_without_order = Cpk::Book.new
1465
+ books = [new_book_with_order, new_book_without_order]
1466
+
1467
+ assert_no_queries do
1468
+ ActiveRecord::Associations::Preloader.new(records: books, associations: :order).call
1469
+
1470
+ assert_same order, new_book_with_order.order
1471
+ assert_nil new_book_without_order.order
1472
+ end
1473
+ end
1474
+
1475
+ def test_preload_wont_set_the_wrong_target
1476
+ post = posts(:welcome)
1477
+ post.update!(author_id: 54321)
1478
+ some_other_record = categories(:general)
1479
+ some_other_record.update!(id: 54321)
1480
+
1481
+ assert_raises do
1482
+ some_other_record.association(:author)
1483
+ end
1484
+
1485
+ assert_nothing_raised do
1486
+ ActiveRecord::Associations::Preloader.new(records: [post], associations: :author, available_records: [[some_other_record]]).call
1487
+ assert_predicate post.association(:author), :loaded?
1488
+ assert_not_equal some_other_record, post.author
1489
+ end
1490
+ end
1491
+
1492
+ def test_preload_has_many_association_with_composite_foreign_key
1493
+ blog_post = sharded_blog_posts(:great_post_blog_one)
1494
+ blog_posts = [blog_post, sharded_blog_posts(:great_post_blog_two)]
1495
+
1496
+ ::ActiveRecord::Associations::Preloader.new(records: blog_posts, associations: [:comments]).call
1497
+
1498
+ assert_predicate blog_post.association(:comments), :loaded?
1499
+ assert_includes(blog_post.comments.to_a, sharded_comments(:great_comment_blog_post_one))
1500
+ end
1501
+
1502
+ def test_preload_belongs_to_association_with_composite_foreign_key
1503
+ comment = sharded_comments(:great_comment_blog_post_one)
1504
+ comments = [comment, sharded_comments(:great_comment_blog_post_two)]
1505
+
1506
+ ActiveRecord::Associations::Preloader.new(records: comments, associations: :blog_post).call
1507
+
1508
+ assert_predicate comment.association(:blog_post), :loaded?
1509
+ assert_equal sharded_blog_posts(:great_post_blog_one), comment.blog_post
1510
+ end
1511
+
1512
+ def test_preload_loaded_belongs_to_association_with_composite_foreign_key
1513
+ comment = sharded_comments(:great_comment_blog_post_one)
1514
+ comment.blog_post
1515
+
1516
+ assert_no_queries do
1517
+ ActiveRecord::Associations::Preloader.new(records: [comment], associations: :blog_post).call
1518
+ end
1519
+ end
1520
+
1521
+ def test_preload_has_many_through_association_with_composite_query_constraints
1522
+ tag = sharded_tags(:short_read_blog_one)
1523
+
1524
+ tags = [tag, sharded_tags(:breaking_news_blog_2)]
1525
+
1526
+ ActiveRecord::Associations::Preloader.new(records: tags, associations: :blog_posts).call
1527
+
1528
+ assert tags.all? { |tag| tag.association(:blog_posts).loaded? }
1529
+
1530
+ expected_blog_post_ids = Sharded::BlogPostTag
1531
+ .where(blog_id: tag.blog_id, tag_id: tag.id)
1532
+ .pluck(:blog_post_id)
1533
+
1534
+ assert_not_empty(expected_blog_post_ids)
1535
+
1536
+ assert_equal(expected_blog_post_ids.sort, tag.blog_posts.map(&:id).sort)
1537
+ end
1538
+
1539
+ def test_preloads_has_many_on_model_with_a_composite_primary_key_through_id_attribute
1540
+ order = cpk_orders(:cpk_groceries_order_2)
1541
+ _shop_id, order_id = order.id
1542
+ order_agreements = Cpk::OrderAgreement.where(order_id: order_id).to_a
1543
+
1544
+ assert_not_empty order_agreements
1545
+ assert_equal order_agreements.sort, order.order_agreements.sort
1546
+
1547
+ loaded_order = nil
1548
+ sql = capture_sql do
1549
+ loaded_order = Cpk::Order.where(id: order_id).includes(:order_agreements).to_a.first
1550
+ end
1551
+
1552
+ assert_equal 2, sql.size
1553
+ preload_sql = sql.last
1554
+
1555
+ expectation = /SELECT.*WHERE.* (?:#{Regexp.escape(quote_table_name("cpk_order_agreements.order_id"))}|cpk_order_agreements\.order_id) = (\?|(\d+)|\$\d)$/
1556
+
1557
+ assert_match(expectation, preload_sql)
1558
+ assert_equal order_agreements.sort, loaded_order.order_agreements.sort
1559
+ end
1560
+
1561
+ def test_preloads_belongs_to_a_composite_primary_key_model_through_id_attribute
1562
+ order_agreement = cpk_order_agreements(:order_agreement_three)
1563
+ order = cpk_orders(:cpk_groceries_order_2)
1564
+ assert_equal order, order_agreement.order
1565
+
1566
+ loaded_order_agreement = nil
1567
+ sql = capture_sql do
1568
+ loaded_order_agreement = Cpk::OrderAgreement.where(id: order_agreement.id).includes(:order).to_a.first
1569
+ end
1570
+
1571
+ assert_equal 2, sql.size
1572
+ preload_sql = sql.last
1573
+
1574
+ expectation = /SELECT.*WHERE.* (?:#{Regexp.escape(quote_table_name("cpk_orders.id"))}|cpk_orders\.id) = (\?|(\d+)|\$\d)$/
1575
+
1576
+ assert_match(expectation, preload_sql)
1577
+ assert_equal order, loaded_order_agreement.order
1578
+ end
1579
+
1580
+ def test_preload_keeps_built_has_many_records_no_ops
1581
+ post = Post.new
1582
+ comment = post.comments.build
1583
+
1584
+ assert_no_queries do
1585
+ ActiveRecord::Associations::Preloader.new(records: [post], associations: :comments).call
1586
+
1587
+ assert_equal [comment], post.comments.to_a
1588
+ end
1589
+ end
1590
+
1591
+ def test_preload_keeps_built_has_many_records_with_composite_key_no_ops
1592
+ skip "DB2 preloader currently includes persisted CPK rows in this no-op scenario" if current_adapter?(:IBM_DBAdapter)
1593
+
1594
+ order = Cpk::Order.new
1595
+ book = order.books.build
1596
+
1597
+ assert_no_queries do
1598
+ ActiveRecord::Associations::Preloader.new(records: [order], associations: :books).call
1599
+
1600
+ assert_equal [book], order.books.to_a
1601
+ end
1602
+ end
1603
+
1604
+ def test_preload_keeps_built_has_many_records_after_query
1605
+ post = posts(:welcome)
1606
+ comment = post.comments.build
1607
+
1608
+ assert_queries_count(1) do
1609
+ ActiveRecord::Associations::Preloader.new(records: [post], associations: :comments).call
1610
+
1611
+ assert_includes post.comments.to_a, comment
1612
+ end
1613
+ end
1614
+
1615
+
1616
+ def test_preload_keeps_built_belongs_to_records_no_ops
1617
+ post = Post.new
1618
+ author = post.build_author
1619
+
1620
+ assert_no_queries do
1621
+ ActiveRecord::Associations::Preloader.new(records: [post], associations: :author).call
1622
+
1623
+ assert_same author, post.author
1624
+ end
1625
+ end
1626
+
1627
+ def test_preload_keeps_built_belongs_to_records_after_query
1628
+ post = posts(:welcome)
1629
+ author = post.build_author
1630
+
1631
+ assert_no_queries do
1632
+ ActiveRecord::Associations::Preloader.new(records: [post], associations: :author).call
1633
+
1634
+ assert_same author, post.author
1635
+ end
1636
+ end
1637
+
1638
+ def test_preload_group_with_klass
1639
+ published_author = PublishedAuthor.create!(name: "PublishedAuthor")
1640
+ PublishedBook.create!(name: "PublishedBook", author_id: published_author.id, isbn: "12345")
1641
+
1642
+ author = Author.create!(name: "Author", published_author_id: published_author.id)
1643
+ Book.create!(name: "Book", author_id: author.id, isbn: "67890")
1644
+
1645
+ result = Author.includes(books: [], published_author: { books: [] }).last
1646
+ expected_klass = current_adapter?(:IBM_DBAdapter) ? Book : PublishedBook
1647
+ assert_equal [expected_klass], result.published_author.books.map(&:class)
1648
+ end
392
1649
  end
393
1650
 
394
1651
  class GeneratedMethodsTest < ActiveRecord::TestCase
@@ -422,7 +1679,7 @@ class GeneratedMethodsTest < ActiveRecord::TestCase
422
1679
  end
423
1680
 
424
1681
  class WithAnnotationsTest < ActiveRecord::TestCase
425
- fixtures :pirates, :parrots, :treasures
1682
+ fixtures :pirates, :parrots, :parrots_pirates, :pirates, :treasures
426
1683
 
427
1684
  def test_belongs_to_with_annotation_includes_a_query_comment
428
1685
  pirate = SpacePirate.where.not(parrot_id: nil).first
@@ -434,7 +1691,7 @@ class WithAnnotationsTest < ActiveRecord::TestCase
434
1691
  assert_not_predicate log, :empty?
435
1692
  assert_predicate log.select { |query| query.match?(%r{/\*}) }, :empty?
436
1693
 
437
- assert_sql(%r{/\* that tells jokes \*/}) do
1694
+ assert_queries_match(%r{/\* that tells jokes \*/}) do
438
1695
  pirate.parrot_with_annotation
439
1696
  end
440
1697
  end
@@ -449,7 +1706,7 @@ class WithAnnotationsTest < ActiveRecord::TestCase
449
1706
  assert_not_predicate log, :empty?
450
1707
  assert_predicate log.select { |query| query.match?(%r{/\*}) }, :empty?
451
1708
 
452
- assert_sql(%r{/\* that are very colorful \*/}) do
1709
+ assert_queries_match(%r{/\* that are very colorful \*/}) do
453
1710
  pirate.parrots_with_annotation.first
454
1711
  end
455
1712
  end
@@ -464,7 +1721,7 @@ class WithAnnotationsTest < ActiveRecord::TestCase
464
1721
  assert_not_predicate log, :empty?
465
1722
  assert_predicate log.select { |query| query.match?(%r{/\*}) }, :empty?
466
1723
 
467
- assert_sql(%r{/\* that is a rocket \*/}) do
1724
+ assert_queries_match(%r{/\* that is a rocket \*/}) do
468
1725
  pirate.ship_with_annotation
469
1726
  end
470
1727
  end
@@ -479,7 +1736,7 @@ class WithAnnotationsTest < ActiveRecord::TestCase
479
1736
  assert_not_predicate log, :empty?
480
1737
  assert_predicate log.select { |query| query.match?(%r{/\*}) }, :empty?
481
1738
 
482
- assert_sql(%r{/\* that are also parrots \*/}) do
1739
+ assert_queries_match(%r{/\* that are also parrots \*/}) do
483
1740
  pirate.birds_with_annotation.first
484
1741
  end
485
1742
  end
@@ -494,7 +1751,7 @@ class WithAnnotationsTest < ActiveRecord::TestCase
494
1751
  assert_not_predicate log, :empty?
495
1752
  assert_predicate log.select { |query| query.match?(%r{/\*}) }, :empty?
496
1753
 
497
- assert_sql(%r{/\* yarrr \*/}) do
1754
+ assert_queries_match(%r{/\* yarrr \*/}) do
498
1755
  pirate.treasure_estimates_with_annotation.first
499
1756
  end
500
1757
  end
@@ -509,7 +1766,7 @@ class WithAnnotationsTest < ActiveRecord::TestCase
509
1766
  assert_not_predicate log, :empty?
510
1767
  assert_predicate log.select { |query| query.match?(%r{/\*}) }, :empty?
511
1768
 
512
- assert_sql(%r{/\* yarrr \*/}) do
1769
+ assert_queries_match(%r{/\* yarrr \*/}) do
513
1770
  SpacePirate.includes(:treasure_estimates_with_annotation, :treasures).first
514
1771
  end
515
1772
  end