activerecord 6.0.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (340) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +1013 -0
  3. data/MIT-LICENSE +22 -0
  4. data/README.rdoc +219 -0
  5. data/examples/performance.rb +185 -0
  6. data/examples/simple.rb +15 -0
  7. data/lib/active_record.rb +195 -0
  8. data/lib/active_record/aggregations.rb +285 -0
  9. data/lib/active_record/association_relation.rb +40 -0
  10. data/lib/active_record/associations.rb +1865 -0
  11. data/lib/active_record/associations/alias_tracker.rb +81 -0
  12. data/lib/active_record/associations/association.rb +332 -0
  13. data/lib/active_record/associations/association_scope.rb +166 -0
  14. data/lib/active_record/associations/belongs_to_association.rb +124 -0
  15. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +36 -0
  16. data/lib/active_record/associations/builder/association.rb +136 -0
  17. data/lib/active_record/associations/builder/belongs_to.rb +130 -0
  18. data/lib/active_record/associations/builder/collection_association.rb +72 -0
  19. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +114 -0
  20. data/lib/active_record/associations/builder/has_many.rb +19 -0
  21. data/lib/active_record/associations/builder/has_one.rb +64 -0
  22. data/lib/active_record/associations/builder/singular_association.rb +44 -0
  23. data/lib/active_record/associations/collection_association.rb +498 -0
  24. data/lib/active_record/associations/collection_proxy.rb +1128 -0
  25. data/lib/active_record/associations/foreign_association.rb +20 -0
  26. data/lib/active_record/associations/has_many_association.rb +136 -0
  27. data/lib/active_record/associations/has_many_through_association.rb +220 -0
  28. data/lib/active_record/associations/has_one_association.rb +118 -0
  29. data/lib/active_record/associations/has_one_through_association.rb +45 -0
  30. data/lib/active_record/associations/join_dependency.rb +258 -0
  31. data/lib/active_record/associations/join_dependency/join_association.rb +80 -0
  32. data/lib/active_record/associations/join_dependency/join_base.rb +23 -0
  33. data/lib/active_record/associations/join_dependency/join_part.rb +71 -0
  34. data/lib/active_record/associations/preloader.rb +201 -0
  35. data/lib/active_record/associations/preloader/association.rb +133 -0
  36. data/lib/active_record/associations/preloader/through_association.rb +116 -0
  37. data/lib/active_record/associations/singular_association.rb +59 -0
  38. data/lib/active_record/associations/through_association.rb +121 -0
  39. data/lib/active_record/attribute_assignment.rb +85 -0
  40. data/lib/active_record/attribute_decorators.rb +90 -0
  41. data/lib/active_record/attribute_methods.rb +420 -0
  42. data/lib/active_record/attribute_methods/before_type_cast.rb +81 -0
  43. data/lib/active_record/attribute_methods/dirty.rb +221 -0
  44. data/lib/active_record/attribute_methods/primary_key.rb +136 -0
  45. data/lib/active_record/attribute_methods/query.rb +41 -0
  46. data/lib/active_record/attribute_methods/read.rb +47 -0
  47. data/lib/active_record/attribute_methods/serialization.rb +90 -0
  48. data/lib/active_record/attribute_methods/time_zone_conversion.rb +91 -0
  49. data/lib/active_record/attribute_methods/write.rb +61 -0
  50. data/lib/active_record/attributes.rb +279 -0
  51. data/lib/active_record/autosave_association.rb +508 -0
  52. data/lib/active_record/base.rb +328 -0
  53. data/lib/active_record/callbacks.rb +339 -0
  54. data/lib/active_record/coders/json.rb +15 -0
  55. data/lib/active_record/coders/yaml_column.rb +50 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +1165 -0
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +85 -0
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +512 -0
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +154 -0
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +251 -0
  61. data/lib/active_record/connection_adapters/abstract/savepoints.rb +23 -0
  62. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +153 -0
  63. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +713 -0
  64. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +93 -0
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +1475 -0
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +323 -0
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +761 -0
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +821 -0
  69. data/lib/active_record/connection_adapters/column.rb +95 -0
  70. data/lib/active_record/connection_adapters/connection_specification.rb +297 -0
  71. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +29 -0
  72. data/lib/active_record/connection_adapters/mysql/column.rb +27 -0
  73. data/lib/active_record/connection_adapters/mysql/database_statements.rb +200 -0
  74. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +72 -0
  75. data/lib/active_record/connection_adapters/mysql/quoting.rb +81 -0
  76. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +72 -0
  77. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +95 -0
  78. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +88 -0
  79. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +264 -0
  80. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +31 -0
  81. data/lib/active_record/connection_adapters/mysql2_adapter.rb +146 -0
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +30 -0
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +182 -0
  84. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid.rb +34 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +92 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +53 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +15 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +17 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +50 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +23 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +23 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +15 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +21 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +71 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +15 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +15 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +45 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +41 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +15 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +65 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +97 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +18 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +113 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +26 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +28 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +30 -0
  108. data/lib/active_record/connection_adapters/postgresql/quoting.rb +205 -0
  109. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +43 -0
  110. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +222 -0
  112. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +50 -0
  113. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +776 -0
  114. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +36 -0
  115. data/lib/active_record/connection_adapters/postgresql/utils.rb +81 -0
  116. data/lib/active_record/connection_adapters/postgresql_adapter.rb +949 -0
  117. data/lib/active_record/connection_adapters/schema_cache.rb +141 -0
  118. data/lib/active_record/connection_adapters/sql_type_metadata.rb +37 -0
  119. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +118 -0
  120. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
  121. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +103 -0
  122. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +17 -0
  123. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +19 -0
  124. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +18 -0
  125. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +137 -0
  126. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +557 -0
  127. data/lib/active_record/connection_adapters/statement_pool.rb +61 -0
  128. data/lib/active_record/connection_handling.rb +267 -0
  129. data/lib/active_record/core.rb +599 -0
  130. data/lib/active_record/counter_cache.rb +193 -0
  131. data/lib/active_record/database_configurations.rb +233 -0
  132. data/lib/active_record/database_configurations/database_config.rb +37 -0
  133. data/lib/active_record/database_configurations/hash_config.rb +50 -0
  134. data/lib/active_record/database_configurations/url_config.rb +79 -0
  135. data/lib/active_record/define_callbacks.rb +22 -0
  136. data/lib/active_record/dynamic_matchers.rb +122 -0
  137. data/lib/active_record/enum.rb +274 -0
  138. data/lib/active_record/errors.rb +388 -0
  139. data/lib/active_record/explain.rb +50 -0
  140. data/lib/active_record/explain_registry.rb +32 -0
  141. data/lib/active_record/explain_subscriber.rb +34 -0
  142. data/lib/active_record/fixture_set/file.rb +82 -0
  143. data/lib/active_record/fixture_set/model_metadata.rb +33 -0
  144. data/lib/active_record/fixture_set/render_context.rb +17 -0
  145. data/lib/active_record/fixture_set/table_row.rb +153 -0
  146. data/lib/active_record/fixture_set/table_rows.rb +47 -0
  147. data/lib/active_record/fixtures.rb +738 -0
  148. data/lib/active_record/gem_version.rb +17 -0
  149. data/lib/active_record/inheritance.rb +293 -0
  150. data/lib/active_record/insert_all.rb +179 -0
  151. data/lib/active_record/integration.rb +207 -0
  152. data/lib/active_record/internal_metadata.rb +53 -0
  153. data/lib/active_record/legacy_yaml_adapter.rb +48 -0
  154. data/lib/active_record/locale/en.yml +48 -0
  155. data/lib/active_record/locking/optimistic.rb +197 -0
  156. data/lib/active_record/locking/pessimistic.rb +89 -0
  157. data/lib/active_record/log_subscriber.rb +118 -0
  158. data/lib/active_record/middleware/database_selector.rb +75 -0
  159. data/lib/active_record/middleware/database_selector/resolver.rb +92 -0
  160. data/lib/active_record/middleware/database_selector/resolver/session.rb +45 -0
  161. data/lib/active_record/migration.rb +1397 -0
  162. data/lib/active_record/migration/command_recorder.rb +284 -0
  163. data/lib/active_record/migration/compatibility.rb +244 -0
  164. data/lib/active_record/migration/join_table.rb +17 -0
  165. data/lib/active_record/model_schema.rb +542 -0
  166. data/lib/active_record/nested_attributes.rb +600 -0
  167. data/lib/active_record/no_touching.rb +65 -0
  168. data/lib/active_record/null_relation.rb +68 -0
  169. data/lib/active_record/persistence.rb +967 -0
  170. data/lib/active_record/query_cache.rb +52 -0
  171. data/lib/active_record/querying.rb +82 -0
  172. data/lib/active_record/railtie.rb +263 -0
  173. data/lib/active_record/railties/collection_cache_association_loading.rb +34 -0
  174. data/lib/active_record/railties/console_sandbox.rb +7 -0
  175. data/lib/active_record/railties/controller_runtime.rb +51 -0
  176. data/lib/active_record/railties/databases.rake +527 -0
  177. data/lib/active_record/readonly_attributes.rb +24 -0
  178. data/lib/active_record/reflection.rb +1042 -0
  179. data/lib/active_record/relation.rb +859 -0
  180. data/lib/active_record/relation/batches.rb +290 -0
  181. data/lib/active_record/relation/batches/batch_enumerator.rb +69 -0
  182. data/lib/active_record/relation/calculations.rb +424 -0
  183. data/lib/active_record/relation/delegation.rb +130 -0
  184. data/lib/active_record/relation/finder_methods.rb +552 -0
  185. data/lib/active_record/relation/from_clause.rb +26 -0
  186. data/lib/active_record/relation/merger.rb +184 -0
  187. data/lib/active_record/relation/predicate_builder.rb +150 -0
  188. data/lib/active_record/relation/predicate_builder/array_handler.rb +49 -0
  189. data/lib/active_record/relation/predicate_builder/association_query_value.rb +43 -0
  190. data/lib/active_record/relation/predicate_builder/base_handler.rb +18 -0
  191. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +19 -0
  192. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +53 -0
  193. data/lib/active_record/relation/predicate_builder/range_handler.rb +22 -0
  194. data/lib/active_record/relation/predicate_builder/relation_handler.rb +19 -0
  195. data/lib/active_record/relation/query_attribute.rb +50 -0
  196. data/lib/active_record/relation/query_methods.rb +1359 -0
  197. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  198. data/lib/active_record/relation/spawn_methods.rb +77 -0
  199. data/lib/active_record/relation/where_clause.rb +190 -0
  200. data/lib/active_record/relation/where_clause_factory.rb +33 -0
  201. data/lib/active_record/result.rb +168 -0
  202. data/lib/active_record/runtime_registry.rb +24 -0
  203. data/lib/active_record/sanitization.rb +214 -0
  204. data/lib/active_record/schema.rb +61 -0
  205. data/lib/active_record/schema_dumper.rb +270 -0
  206. data/lib/active_record/schema_migration.rb +60 -0
  207. data/lib/active_record/scoping.rb +106 -0
  208. data/lib/active_record/scoping/default.rb +151 -0
  209. data/lib/active_record/scoping/named.rb +217 -0
  210. data/lib/active_record/secure_token.rb +40 -0
  211. data/lib/active_record/serialization.rb +22 -0
  212. data/lib/active_record/statement_cache.rb +148 -0
  213. data/lib/active_record/store.rb +290 -0
  214. data/lib/active_record/suppressor.rb +61 -0
  215. data/lib/active_record/table_metadata.rb +75 -0
  216. data/lib/active_record/tasks/database_tasks.rb +506 -0
  217. data/lib/active_record/tasks/mysql_database_tasks.rb +115 -0
  218. data/lib/active_record/tasks/postgresql_database_tasks.rb +141 -0
  219. data/lib/active_record/tasks/sqlite_database_tasks.rb +77 -0
  220. data/lib/active_record/test_databases.rb +23 -0
  221. data/lib/active_record/test_fixtures.rb +224 -0
  222. data/lib/active_record/timestamp.rb +167 -0
  223. data/lib/active_record/touch_later.rb +66 -0
  224. data/lib/active_record/transactions.rb +493 -0
  225. data/lib/active_record/translation.rb +24 -0
  226. data/lib/active_record/type.rb +78 -0
  227. data/lib/active_record/type/adapter_specific_registry.rb +129 -0
  228. data/lib/active_record/type/date.rb +9 -0
  229. data/lib/active_record/type/date_time.rb +9 -0
  230. data/lib/active_record/type/decimal_without_scale.rb +15 -0
  231. data/lib/active_record/type/hash_lookup_type_map.rb +25 -0
  232. data/lib/active_record/type/internal/timezone.rb +17 -0
  233. data/lib/active_record/type/json.rb +30 -0
  234. data/lib/active_record/type/serialized.rb +71 -0
  235. data/lib/active_record/type/text.rb +11 -0
  236. data/lib/active_record/type/time.rb +21 -0
  237. data/lib/active_record/type/type_map.rb +62 -0
  238. data/lib/active_record/type/unsigned_integer.rb +17 -0
  239. data/lib/active_record/type_caster.rb +9 -0
  240. data/lib/active_record/type_caster/connection.rb +34 -0
  241. data/lib/active_record/type_caster/map.rb +20 -0
  242. data/lib/active_record/validations.rb +94 -0
  243. data/lib/active_record/validations/absence.rb +25 -0
  244. data/lib/active_record/validations/associated.rb +60 -0
  245. data/lib/active_record/validations/length.rb +26 -0
  246. data/lib/active_record/validations/presence.rb +68 -0
  247. data/lib/active_record/validations/uniqueness.rb +226 -0
  248. data/lib/active_record/version.rb +10 -0
  249. data/lib/arel.rb +51 -0
  250. data/lib/arel/alias_predication.rb +9 -0
  251. data/lib/arel/attributes.rb +22 -0
  252. data/lib/arel/attributes/attribute.rb +37 -0
  253. data/lib/arel/collectors/bind.rb +24 -0
  254. data/lib/arel/collectors/composite.rb +31 -0
  255. data/lib/arel/collectors/plain_string.rb +20 -0
  256. data/lib/arel/collectors/sql_string.rb +20 -0
  257. data/lib/arel/collectors/substitute_binds.rb +28 -0
  258. data/lib/arel/crud.rb +42 -0
  259. data/lib/arel/delete_manager.rb +18 -0
  260. data/lib/arel/errors.rb +9 -0
  261. data/lib/arel/expressions.rb +29 -0
  262. data/lib/arel/factory_methods.rb +49 -0
  263. data/lib/arel/insert_manager.rb +49 -0
  264. data/lib/arel/math.rb +45 -0
  265. data/lib/arel/nodes.rb +68 -0
  266. data/lib/arel/nodes/and.rb +32 -0
  267. data/lib/arel/nodes/ascending.rb +23 -0
  268. data/lib/arel/nodes/binary.rb +52 -0
  269. data/lib/arel/nodes/bind_param.rb +36 -0
  270. data/lib/arel/nodes/case.rb +55 -0
  271. data/lib/arel/nodes/casted.rb +50 -0
  272. data/lib/arel/nodes/comment.rb +29 -0
  273. data/lib/arel/nodes/count.rb +12 -0
  274. data/lib/arel/nodes/delete_statement.rb +45 -0
  275. data/lib/arel/nodes/descending.rb +23 -0
  276. data/lib/arel/nodes/equality.rb +18 -0
  277. data/lib/arel/nodes/extract.rb +24 -0
  278. data/lib/arel/nodes/false.rb +16 -0
  279. data/lib/arel/nodes/full_outer_join.rb +8 -0
  280. data/lib/arel/nodes/function.rb +44 -0
  281. data/lib/arel/nodes/grouping.rb +8 -0
  282. data/lib/arel/nodes/in.rb +8 -0
  283. data/lib/arel/nodes/infix_operation.rb +80 -0
  284. data/lib/arel/nodes/inner_join.rb +8 -0
  285. data/lib/arel/nodes/insert_statement.rb +37 -0
  286. data/lib/arel/nodes/join_source.rb +20 -0
  287. data/lib/arel/nodes/matches.rb +18 -0
  288. data/lib/arel/nodes/named_function.rb +23 -0
  289. data/lib/arel/nodes/node.rb +50 -0
  290. data/lib/arel/nodes/node_expression.rb +13 -0
  291. data/lib/arel/nodes/outer_join.rb +8 -0
  292. data/lib/arel/nodes/over.rb +15 -0
  293. data/lib/arel/nodes/regexp.rb +16 -0
  294. data/lib/arel/nodes/right_outer_join.rb +8 -0
  295. data/lib/arel/nodes/select_core.rb +67 -0
  296. data/lib/arel/nodes/select_statement.rb +41 -0
  297. data/lib/arel/nodes/sql_literal.rb +16 -0
  298. data/lib/arel/nodes/string_join.rb +11 -0
  299. data/lib/arel/nodes/table_alias.rb +27 -0
  300. data/lib/arel/nodes/terminal.rb +16 -0
  301. data/lib/arel/nodes/true.rb +16 -0
  302. data/lib/arel/nodes/unary.rb +45 -0
  303. data/lib/arel/nodes/unary_operation.rb +20 -0
  304. data/lib/arel/nodes/unqualified_column.rb +22 -0
  305. data/lib/arel/nodes/update_statement.rb +41 -0
  306. data/lib/arel/nodes/values_list.rb +9 -0
  307. data/lib/arel/nodes/window.rb +126 -0
  308. data/lib/arel/nodes/with.rb +11 -0
  309. data/lib/arel/order_predications.rb +13 -0
  310. data/lib/arel/predications.rb +257 -0
  311. data/lib/arel/select_manager.rb +271 -0
  312. data/lib/arel/table.rb +110 -0
  313. data/lib/arel/tree_manager.rb +72 -0
  314. data/lib/arel/update_manager.rb +34 -0
  315. data/lib/arel/visitors.rb +20 -0
  316. data/lib/arel/visitors/depth_first.rb +204 -0
  317. data/lib/arel/visitors/dot.rb +297 -0
  318. data/lib/arel/visitors/ibm_db.rb +34 -0
  319. data/lib/arel/visitors/informix.rb +62 -0
  320. data/lib/arel/visitors/mssql.rb +157 -0
  321. data/lib/arel/visitors/mysql.rb +83 -0
  322. data/lib/arel/visitors/oracle.rb +159 -0
  323. data/lib/arel/visitors/oracle12.rb +66 -0
  324. data/lib/arel/visitors/postgresql.rb +110 -0
  325. data/lib/arel/visitors/sqlite.rb +39 -0
  326. data/lib/arel/visitors/to_sql.rb +889 -0
  327. data/lib/arel/visitors/visitor.rb +46 -0
  328. data/lib/arel/visitors/where_sql.rb +23 -0
  329. data/lib/arel/window_predications.rb +9 -0
  330. data/lib/rails/generators/active_record.rb +19 -0
  331. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +27 -0
  332. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
  333. data/lib/rails/generators/active_record/migration.rb +48 -0
  334. data/lib/rails/generators/active_record/migration/migration_generator.rb +75 -0
  335. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +24 -0
  336. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +48 -0
  337. data/lib/rails/generators/active_record/model/model_generator.rb +49 -0
  338. data/lib/rails/generators/active_record/model/templates/model.rb.tt +22 -0
  339. data/lib/rails/generators/active_record/model/templates/module.rb.tt +7 -0
  340. metadata +415 -0
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord::Associations::Builder # :nodoc:
4
+ class HasAndBelongsToMany # :nodoc:
5
+ attr_reader :lhs_model, :association_name, :options
6
+
7
+ def initialize(association_name, lhs_model, options)
8
+ @association_name = association_name
9
+ @lhs_model = lhs_model
10
+ @options = options
11
+ end
12
+
13
+ def through_model
14
+ join_model = Class.new(ActiveRecord::Base) {
15
+ class << self
16
+ attr_accessor :left_model
17
+ attr_accessor :name
18
+ attr_accessor :table_name_resolver
19
+ attr_accessor :left_reflection
20
+ attr_accessor :right_reflection
21
+ end
22
+
23
+ def self.table_name
24
+ # Table name needs to be resolved lazily
25
+ # because RHS class might not have been loaded
26
+ @table_name ||= table_name_resolver.call
27
+ end
28
+
29
+ def self.compute_type(class_name)
30
+ left_model.compute_type class_name
31
+ end
32
+
33
+ def self.add_left_association(name, options)
34
+ belongs_to name, required: false, **options
35
+ self.left_reflection = _reflect_on_association(name)
36
+ end
37
+
38
+ def self.add_right_association(name, options)
39
+ rhs_name = name.to_s.singularize.to_sym
40
+ belongs_to rhs_name, required: false, **options
41
+ self.right_reflection = _reflect_on_association(rhs_name)
42
+ end
43
+
44
+ def self.retrieve_connection
45
+ left_model.retrieve_connection
46
+ end
47
+
48
+ private
49
+
50
+ def self.suppress_composite_primary_key(pk)
51
+ pk unless pk.is_a?(Array)
52
+ end
53
+ }
54
+
55
+ join_model.name = "HABTM_#{association_name.to_s.camelize}"
56
+ join_model.table_name_resolver = -> { table_name }
57
+ join_model.left_model = lhs_model
58
+
59
+ join_model.add_left_association :left_side, anonymous_class: lhs_model
60
+ join_model.add_right_association association_name, belongs_to_options(options)
61
+ join_model
62
+ end
63
+
64
+ def middle_reflection(join_model)
65
+ middle_name = [lhs_model.name.downcase.pluralize,
66
+ association_name.to_s].sort.join("_").gsub("::", "_").to_sym
67
+ middle_options = middle_options join_model
68
+
69
+ HasMany.create_reflection(lhs_model,
70
+ middle_name,
71
+ nil,
72
+ middle_options)
73
+ end
74
+
75
+ private
76
+
77
+ def middle_options(join_model)
78
+ middle_options = {}
79
+ middle_options[:class_name] = "#{lhs_model.name}::#{join_model.name}"
80
+ middle_options[:source] = join_model.left_reflection.name
81
+ if options.key? :foreign_key
82
+ middle_options[:foreign_key] = options[:foreign_key]
83
+ end
84
+ middle_options
85
+ end
86
+
87
+ def table_name
88
+ if options[:join_table]
89
+ options[:join_table].to_s
90
+ else
91
+ class_name = options.fetch(:class_name) {
92
+ association_name.to_s.camelize.singularize
93
+ }
94
+ klass = lhs_model.send(:compute_type, class_name.to_s)
95
+ [lhs_model.table_name, klass.table_name].sort.join("\0").gsub(/^(.*[._])(.+)\0\1(.+)/, '\1\2_\3').tr("\0", "_")
96
+ end
97
+ end
98
+
99
+ def belongs_to_options(options)
100
+ rhs_options = {}
101
+
102
+ if options.key? :class_name
103
+ rhs_options[:foreign_key] = options[:class_name].to_s.foreign_key
104
+ rhs_options[:class_name] = options[:class_name]
105
+ end
106
+
107
+ if options.key? :association_foreign_key
108
+ rhs_options[:foreign_key] = options[:association_foreign_key]
109
+ end
110
+
111
+ rhs_options
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord::Associations::Builder # :nodoc:
4
+ class HasMany < CollectionAssociation #:nodoc:
5
+ def self.macro
6
+ :has_many
7
+ end
8
+
9
+ def self.valid_options(options)
10
+ super + [:primary_key, :dependent, :as, :through, :source, :source_type, :inverse_of, :counter_cache, :join_table, :foreign_type, :index_errors]
11
+ end
12
+
13
+ def self.valid_dependent_options
14
+ [:destroy, :delete_all, :nullify, :restrict_with_error, :restrict_with_exception]
15
+ end
16
+
17
+ private_class_method :macro, :valid_options, :valid_dependent_options
18
+ end
19
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord::Associations::Builder # :nodoc:
4
+ class HasOne < SingularAssociation #:nodoc:
5
+ def self.macro
6
+ :has_one
7
+ end
8
+
9
+ def self.valid_options(options)
10
+ valid = super + [:as, :touch]
11
+ valid += [:through, :source, :source_type] if options[:through]
12
+ valid
13
+ end
14
+
15
+ def self.valid_dependent_options
16
+ [:destroy, :delete, :nullify, :restrict_with_error, :restrict_with_exception]
17
+ end
18
+
19
+ def self.define_callbacks(model, reflection)
20
+ super
21
+ add_touch_callbacks(model, reflection) if reflection.options[:touch]
22
+ end
23
+
24
+ def self.add_destroy_callbacks(model, reflection)
25
+ super unless reflection.options[:through]
26
+ end
27
+
28
+ def self.define_validations(model, reflection)
29
+ super
30
+ if reflection.options[:required]
31
+ model.validates_presence_of reflection.name, message: :required
32
+ end
33
+ end
34
+
35
+ def self.touch_record(o, name, touch)
36
+ record = o.send name
37
+
38
+ return unless record && record.persisted?
39
+
40
+ if touch != true
41
+ record.touch(touch)
42
+ else
43
+ record.touch
44
+ end
45
+ end
46
+
47
+ def self.add_touch_callbacks(model, reflection)
48
+ name = reflection.name
49
+ touch = reflection.options[:touch]
50
+
51
+ callback = lambda { |record|
52
+ HasOne.touch_record(record, name, touch)
53
+ }
54
+
55
+ model.after_create callback, if: :saved_changes?
56
+ model.after_update callback, if: :saved_changes?
57
+ model.after_destroy callback
58
+ model.after_touch callback
59
+ end
60
+
61
+ private_class_method :macro, :valid_options, :valid_dependent_options, :add_destroy_callbacks,
62
+ :define_callbacks, :define_validations, :add_touch_callbacks
63
+ end
64
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This class is inherited by the has_one and belongs_to association classes
4
+
5
+ module ActiveRecord::Associations::Builder # :nodoc:
6
+ class SingularAssociation < Association #:nodoc:
7
+ def self.valid_options(options)
8
+ super + [:foreign_type, :dependent, :primary_key, :inverse_of, :required]
9
+ end
10
+
11
+ def self.define_accessors(model, reflection)
12
+ super
13
+ mixin = model.generated_association_methods
14
+ name = reflection.name
15
+
16
+ define_constructors(mixin, name) if reflection.constructable?
17
+
18
+ mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
19
+ def reload_#{name}
20
+ association(:#{name}).force_reload_reader
21
+ end
22
+ CODE
23
+ end
24
+
25
+ # Defines the (build|create)_association methods for belongs_to or has_one association
26
+ def self.define_constructors(mixin, name)
27
+ mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
28
+ def build_#{name}(*args, &block)
29
+ association(:#{name}).build(*args, &block)
30
+ end
31
+
32
+ def create_#{name}(*args, &block)
33
+ association(:#{name}).create(*args, &block)
34
+ end
35
+
36
+ def create_#{name}!(*args, &block)
37
+ association(:#{name}).create!(*args, &block)
38
+ end
39
+ CODE
40
+ end
41
+
42
+ private_class_method :valid_options, :define_accessors, :define_constructors
43
+ end
44
+ end
@@ -0,0 +1,498 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Associations
5
+ # = Active Record Association Collection
6
+ #
7
+ # CollectionAssociation is an abstract class that provides common stuff to
8
+ # ease the implementation of association proxies that represent
9
+ # collections. See the class hierarchy in Association.
10
+ #
11
+ # CollectionAssociation:
12
+ # HasManyAssociation => has_many
13
+ # HasManyThroughAssociation + ThroughAssociation => has_many :through
14
+ #
15
+ # The CollectionAssociation class provides common methods to the collections
16
+ # defined by +has_and_belongs_to_many+, +has_many+ or +has_many+ with
17
+ # the +:through association+ option.
18
+ #
19
+ # You need to be careful with assumptions regarding the target: The proxy
20
+ # does not fetch records from the database until it needs them, but new
21
+ # ones created with +build+ are added to the target. So, the target may be
22
+ # non-empty and still lack children waiting to be read from the database.
23
+ # If you look directly to the database you cannot assume that's the entire
24
+ # collection because new records may have been added to the target, etc.
25
+ #
26
+ # If you need to work on all current children, new and existing records,
27
+ # +load_target+ and the +loaded+ flag are your friends.
28
+ class CollectionAssociation < Association #:nodoc:
29
+ # Implements the reader method, e.g. foo.items for Foo.has_many :items
30
+ def reader
31
+ if stale_target?
32
+ reload
33
+ end
34
+
35
+ @proxy ||= CollectionProxy.create(klass, self)
36
+ @proxy.reset_scope
37
+ end
38
+
39
+ # Implements the writer method, e.g. foo.items= for Foo.has_many :items
40
+ def writer(records)
41
+ replace(records)
42
+ end
43
+
44
+ # Implements the ids reader method, e.g. foo.item_ids for Foo.has_many :items
45
+ def ids_reader
46
+ if loaded?
47
+ target.pluck(reflection.association_primary_key)
48
+ elsif !target.empty?
49
+ load_target.pluck(reflection.association_primary_key)
50
+ else
51
+ @association_ids ||= scope.pluck(reflection.association_primary_key)
52
+ end
53
+ end
54
+
55
+ # Implements the ids writer method, e.g. foo.item_ids= for Foo.has_many :items
56
+ def ids_writer(ids)
57
+ primary_key = reflection.association_primary_key
58
+ pk_type = klass.type_for_attribute(primary_key)
59
+ ids = Array(ids).reject(&:blank?)
60
+ ids.map! { |i| pk_type.cast(i) }
61
+
62
+ records = klass.where(primary_key => ids).index_by do |r|
63
+ r.public_send(primary_key)
64
+ end.values_at(*ids).compact
65
+
66
+ if records.size != ids.size
67
+ found_ids = records.map { |record| record.public_send(primary_key) }
68
+ not_found_ids = ids - found_ids
69
+ klass.all.raise_record_not_found_exception!(ids, records.size, ids.size, primary_key, not_found_ids)
70
+ else
71
+ replace(records)
72
+ end
73
+ end
74
+
75
+ def reset
76
+ super
77
+ @target = []
78
+ @association_ids = nil
79
+ end
80
+
81
+ def find(*args)
82
+ if options[:inverse_of] && loaded?
83
+ args_flatten = args.flatten
84
+ model = scope.klass
85
+
86
+ if args_flatten.blank?
87
+ error_message = "Couldn't find #{model.name} without an ID"
88
+ raise RecordNotFound.new(error_message, model.name, model.primary_key, args)
89
+ end
90
+
91
+ result = find_by_scan(*args)
92
+
93
+ result_size = Array(result).size
94
+ if !result || result_size != args_flatten.size
95
+ scope.raise_record_not_found_exception!(args_flatten, result_size, args_flatten.size)
96
+ else
97
+ result
98
+ end
99
+ else
100
+ scope.find(*args)
101
+ end
102
+ end
103
+
104
+ def build(attributes = {}, &block)
105
+ if attributes.is_a?(Array)
106
+ attributes.collect { |attr| build(attr, &block) }
107
+ else
108
+ add_to_target(build_record(attributes, &block))
109
+ end
110
+ end
111
+
112
+ # Add +records+ to this association. Since +<<+ flattens its argument list
113
+ # and inserts each record, +push+ and +concat+ behave identically.
114
+ def concat(*records)
115
+ records = records.flatten
116
+ if owner.new_record?
117
+ load_target
118
+ concat_records(records)
119
+ else
120
+ transaction { concat_records(records) }
121
+ end
122
+ end
123
+
124
+ # Starts a transaction in the association class's database connection.
125
+ #
126
+ # class Author < ActiveRecord::Base
127
+ # has_many :books
128
+ # end
129
+ #
130
+ # Author.first.books.transaction do
131
+ # # same effect as calling Book.transaction
132
+ # end
133
+ def transaction(*args)
134
+ reflection.klass.transaction(*args) do
135
+ yield
136
+ end
137
+ end
138
+
139
+ # Removes all records from the association without calling callbacks
140
+ # on the associated records. It honors the +:dependent+ option. However
141
+ # if the +:dependent+ value is +:destroy+ then in that case the +:delete_all+
142
+ # deletion strategy for the association is applied.
143
+ #
144
+ # You can force a particular deletion strategy by passing a parameter.
145
+ #
146
+ # Example:
147
+ #
148
+ # @author.books.delete_all(:nullify)
149
+ # @author.books.delete_all(:delete_all)
150
+ #
151
+ # See delete for more info.
152
+ def delete_all(dependent = nil)
153
+ if dependent && ![:nullify, :delete_all].include?(dependent)
154
+ raise ArgumentError, "Valid values are :nullify or :delete_all"
155
+ end
156
+
157
+ dependent = if dependent
158
+ dependent
159
+ elsif options[:dependent] == :destroy
160
+ :delete_all
161
+ else
162
+ options[:dependent]
163
+ end
164
+
165
+ delete_or_nullify_all_records(dependent).tap do
166
+ reset
167
+ loaded!
168
+ end
169
+ end
170
+
171
+ # Destroy all the records from this association.
172
+ #
173
+ # See destroy for more info.
174
+ def destroy_all
175
+ destroy(load_target).tap do
176
+ reset
177
+ loaded!
178
+ end
179
+ end
180
+
181
+ # Removes +records+ from this association calling +before_remove+ and
182
+ # +after_remove+ callbacks.
183
+ #
184
+ # This method is abstract in the sense that +delete_records+ has to be
185
+ # provided by descendants. Note this method does not imply the records
186
+ # are actually removed from the database, that depends precisely on
187
+ # +delete_records+. They are in any case removed from the collection.
188
+ def delete(*records)
189
+ delete_or_destroy(records, options[:dependent])
190
+ end
191
+
192
+ # Deletes the +records+ and removes them from this association calling
193
+ # +before_remove+ , +after_remove+ , +before_destroy+ and +after_destroy+ callbacks.
194
+ #
195
+ # Note that this method removes records from the database ignoring the
196
+ # +:dependent+ option.
197
+ def destroy(*records)
198
+ delete_or_destroy(records, :destroy)
199
+ end
200
+
201
+ # Returns the size of the collection by executing a SELECT COUNT(*)
202
+ # query if the collection hasn't been loaded, and calling
203
+ # <tt>collection.size</tt> if it has.
204
+ #
205
+ # If the collection has been already loaded +size+ and +length+ are
206
+ # equivalent. If not and you are going to need the records anyway
207
+ # +length+ will take one less query. Otherwise +size+ is more efficient.
208
+ #
209
+ # This method is abstract in the sense that it relies on
210
+ # +count_records+, which is a method descendants have to provide.
211
+ def size
212
+ if !find_target? || loaded?
213
+ target.size
214
+ elsif @association_ids
215
+ @association_ids.size
216
+ elsif !association_scope.group_values.empty?
217
+ load_target.size
218
+ elsif !association_scope.distinct_value && !target.empty?
219
+ unsaved_records = target.select(&:new_record?)
220
+ unsaved_records.size + count_records
221
+ else
222
+ count_records
223
+ end
224
+ end
225
+
226
+ # Returns true if the collection is empty.
227
+ #
228
+ # If the collection has been loaded
229
+ # it is equivalent to <tt>collection.size.zero?</tt>. If the
230
+ # collection has not been loaded, it is equivalent to
231
+ # <tt>collection.exists?</tt>. If the collection has not already been
232
+ # loaded and you are going to fetch the records anyway it is better to
233
+ # check <tt>collection.length.zero?</tt>.
234
+ def empty?
235
+ if loaded? || @association_ids || reflection.has_cached_counter?
236
+ size.zero?
237
+ else
238
+ target.empty? && !scope.exists?
239
+ end
240
+ end
241
+
242
+ # Replace this collection with +other_array+. This will perform a diff
243
+ # and delete/add only records that have changed.
244
+ def replace(other_array)
245
+ other_array.each { |val| raise_on_type_mismatch!(val) }
246
+ original_target = load_target.dup
247
+
248
+ if owner.new_record?
249
+ replace_records(other_array, original_target)
250
+ else
251
+ replace_common_records_in_memory(other_array, original_target)
252
+ if other_array != original_target
253
+ transaction { replace_records(other_array, original_target) }
254
+ else
255
+ other_array
256
+ end
257
+ end
258
+ end
259
+
260
+ def include?(record)
261
+ if record.is_a?(reflection.klass)
262
+ if record.new_record?
263
+ include_in_memory?(record)
264
+ else
265
+ loaded? ? target.include?(record) : scope.exists?(record.id)
266
+ end
267
+ else
268
+ false
269
+ end
270
+ end
271
+
272
+ def load_target
273
+ if find_target?
274
+ @target = merge_target_lists(find_target, target)
275
+ end
276
+
277
+ loaded!
278
+ target
279
+ end
280
+
281
+ def add_to_target(record, skip_callbacks = false, &block)
282
+ if association_scope.distinct_value
283
+ index = @target.index(record)
284
+ end
285
+ replace_on_target(record, index, skip_callbacks, &block)
286
+ end
287
+
288
+ def scope
289
+ scope = super
290
+ scope.none! if null_scope?
291
+ scope
292
+ end
293
+
294
+ def null_scope?
295
+ owner.new_record? && !foreign_key_present?
296
+ end
297
+
298
+ def find_from_target?
299
+ loaded? ||
300
+ owner.new_record? ||
301
+ target.any? { |record| record.new_record? || record.changed? }
302
+ end
303
+
304
+ private
305
+ # We have some records loaded from the database (persisted) and some that are
306
+ # in-memory (memory). The same record may be represented in the persisted array
307
+ # and in the memory array.
308
+ #
309
+ # So the task of this method is to merge them according to the following rules:
310
+ #
311
+ # * The final array must not have duplicates
312
+ # * The order of the persisted array is to be preserved
313
+ # * Any changes made to attributes on objects in the memory array are to be preserved
314
+ # * Otherwise, attributes should have the value found in the database
315
+ def merge_target_lists(persisted, memory)
316
+ return persisted if memory.empty?
317
+ return memory if persisted.empty?
318
+
319
+ persisted.map! do |record|
320
+ if mem_record = memory.delete(record)
321
+
322
+ ((record.attribute_names & mem_record.attribute_names) - mem_record.changed_attribute_names_to_save).each do |name|
323
+ mem_record[name] = record[name]
324
+ end
325
+
326
+ mem_record
327
+ else
328
+ record
329
+ end
330
+ end
331
+
332
+ persisted + memory
333
+ end
334
+
335
+ def _create_record(attributes, raise = false, &block)
336
+ unless owner.persisted?
337
+ raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"
338
+ end
339
+
340
+ if attributes.is_a?(Array)
341
+ attributes.collect { |attr| _create_record(attr, raise, &block) }
342
+ else
343
+ record = build_record(attributes, &block)
344
+ transaction do
345
+ result = nil
346
+ add_to_target(record) do
347
+ result = insert_record(record, true, raise) {
348
+ @_was_loaded = loaded?
349
+ }
350
+ end
351
+ raise ActiveRecord::Rollback unless result
352
+ end
353
+ record
354
+ end
355
+ end
356
+
357
+ # Do the relevant stuff to insert the given record into the association collection.
358
+ def insert_record(record, validate = true, raise = false, &block)
359
+ if raise
360
+ record.save!(validate: validate, &block)
361
+ else
362
+ record.save(validate: validate, &block)
363
+ end
364
+ end
365
+
366
+ def delete_or_destroy(records, method)
367
+ return if records.empty?
368
+ records = find(records) if records.any? { |record| record.kind_of?(Integer) || record.kind_of?(String) }
369
+ records = records.flatten
370
+ records.each { |record| raise_on_type_mismatch!(record) }
371
+ existing_records = records.reject(&:new_record?)
372
+
373
+ if existing_records.empty?
374
+ remove_records(existing_records, records, method)
375
+ else
376
+ transaction { remove_records(existing_records, records, method) }
377
+ end
378
+ end
379
+
380
+ def remove_records(existing_records, records, method)
381
+ records.each { |record| callback(:before_remove, record) }
382
+
383
+ delete_records(existing_records, method) if existing_records.any?
384
+ @target -= records
385
+ @association_ids = nil
386
+
387
+ records.each { |record| callback(:after_remove, record) }
388
+ end
389
+
390
+ # Delete the given records from the association,
391
+ # using one of the methods +:destroy+, +:delete_all+
392
+ # or +:nullify+ (or +nil+, in which case a default is used).
393
+ def delete_records(records, method)
394
+ raise NotImplementedError
395
+ end
396
+
397
+ def replace_records(new_target, original_target)
398
+ delete(difference(target, new_target))
399
+
400
+ unless concat(difference(new_target, target))
401
+ @target = original_target
402
+ raise RecordNotSaved, "Failed to replace #{reflection.name} because one or more of the " \
403
+ "new records could not be saved."
404
+ end
405
+
406
+ target
407
+ end
408
+
409
+ def replace_common_records_in_memory(new_target, original_target)
410
+ common_records = intersection(new_target, original_target)
411
+ common_records.each do |record|
412
+ skip_callbacks = true
413
+ replace_on_target(record, @target.index(record), skip_callbacks)
414
+ end
415
+ end
416
+
417
+ def concat_records(records, raise = false)
418
+ result = true
419
+
420
+ records.each do |record|
421
+ raise_on_type_mismatch!(record)
422
+ add_to_target(record) do
423
+ unless owner.new_record?
424
+ result &&= insert_record(record, true, raise) {
425
+ @_was_loaded = loaded?
426
+ }
427
+ end
428
+ end
429
+ end
430
+
431
+ raise ActiveRecord::Rollback unless result
432
+
433
+ records
434
+ end
435
+
436
+ def replace_on_target(record, index, skip_callbacks)
437
+ callback(:before_add, record) unless skip_callbacks
438
+
439
+ set_inverse_instance(record)
440
+
441
+ @_was_loaded = true
442
+
443
+ yield(record) if block_given?
444
+
445
+ if index
446
+ target[index] = record
447
+ elsif @_was_loaded || !loaded?
448
+ @association_ids = nil
449
+ target << record
450
+ end
451
+
452
+ callback(:after_add, record) unless skip_callbacks
453
+
454
+ record
455
+ ensure
456
+ @_was_loaded = nil
457
+ end
458
+
459
+ def callback(method, record)
460
+ callbacks_for(method).each do |callback|
461
+ callback.call(method, owner, record)
462
+ end
463
+ end
464
+
465
+ def callbacks_for(callback_name)
466
+ full_callback_name = "#{callback_name}_for_#{reflection.name}"
467
+ owner.class.send(full_callback_name)
468
+ end
469
+
470
+ def include_in_memory?(record)
471
+ if reflection.is_a?(ActiveRecord::Reflection::ThroughReflection)
472
+ assoc = owner.association(reflection.through_reflection.name)
473
+ assoc.reader.any? { |source|
474
+ target_reflection = source.send(reflection.source_reflection.name)
475
+ target_reflection.respond_to?(:include?) ? target_reflection.include?(record) : target_reflection == record
476
+ } || target.include?(record)
477
+ else
478
+ target.include?(record)
479
+ end
480
+ end
481
+
482
+ # If the :inverse_of option has been
483
+ # specified, then #find scans the entire collection.
484
+ def find_by_scan(*args)
485
+ expects_array = args.first.kind_of?(Array)
486
+ ids = args.flatten.compact.map(&:to_s).uniq
487
+
488
+ if ids.size == 1
489
+ id = ids.first
490
+ record = load_target.detect { |r| id == r.id.to_s }
491
+ expects_array ? [ record ] : record
492
+ else
493
+ load_target.select { |r| ids.include?(r.id.to_s) }
494
+ end
495
+ end
496
+ end
497
+ end
498
+ end