activerecord 5.2.3 → 6.1.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 (316) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +898 -532
  3. data/MIT-LICENSE +3 -1
  4. data/README.rdoc +7 -5
  5. data/examples/performance.rb +1 -1
  6. data/lib/active_record/aggregations.rb +5 -4
  7. data/lib/active_record/association_relation.rb +22 -12
  8. data/lib/active_record/associations/alias_tracker.rb +19 -16
  9. data/lib/active_record/associations/association.rb +95 -42
  10. data/lib/active_record/associations/association_scope.rb +21 -21
  11. data/lib/active_record/associations/belongs_to_association.rb +50 -46
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +1 -5
  13. data/lib/active_record/associations/builder/association.rb +23 -21
  14. data/lib/active_record/associations/builder/belongs_to.rb +29 -59
  15. data/lib/active_record/associations/builder/collection_association.rb +10 -19
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +17 -41
  17. data/lib/active_record/associations/builder/has_many.rb +8 -2
  18. data/lib/active_record/associations/builder/has_one.rb +33 -2
  19. data/lib/active_record/associations/builder/singular_association.rb +3 -1
  20. data/lib/active_record/associations/collection_association.rb +31 -29
  21. data/lib/active_record/associations/collection_proxy.rb +25 -21
  22. data/lib/active_record/associations/foreign_association.rb +20 -0
  23. data/lib/active_record/associations/has_many_association.rb +26 -13
  24. data/lib/active_record/associations/has_many_through_association.rb +27 -28
  25. data/lib/active_record/associations/has_one_association.rb +43 -31
  26. data/lib/active_record/associations/has_one_through_association.rb +5 -5
  27. data/lib/active_record/associations/join_dependency/join_association.rb +54 -12
  28. data/lib/active_record/associations/join_dependency/join_part.rb +5 -5
  29. data/lib/active_record/associations/join_dependency.rb +91 -60
  30. data/lib/active_record/associations/preloader/association.rb +71 -43
  31. data/lib/active_record/associations/preloader/through_association.rb +49 -40
  32. data/lib/active_record/associations/preloader.rb +48 -35
  33. data/lib/active_record/associations/singular_association.rb +3 -17
  34. data/lib/active_record/associations/through_association.rb +1 -1
  35. data/lib/active_record/associations.rb +133 -25
  36. data/lib/active_record/attribute_assignment.rb +17 -19
  37. data/lib/active_record/attribute_methods/before_type_cast.rb +13 -7
  38. data/lib/active_record/attribute_methods/dirty.rb +101 -40
  39. data/lib/active_record/attribute_methods/primary_key.rb +20 -25
  40. data/lib/active_record/attribute_methods/query.rb +4 -8
  41. data/lib/active_record/attribute_methods/read.rb +14 -56
  42. data/lib/active_record/attribute_methods/serialization.rb +12 -7
  43. data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -15
  44. data/lib/active_record/attribute_methods/write.rb +18 -34
  45. data/lib/active_record/attribute_methods.rb +81 -143
  46. data/lib/active_record/attributes.rb +45 -8
  47. data/lib/active_record/autosave_association.rb +76 -47
  48. data/lib/active_record/base.rb +4 -17
  49. data/lib/active_record/callbacks.rb +158 -43
  50. data/lib/active_record/coders/yaml_column.rb +1 -2
  51. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +293 -132
  52. data/lib/active_record/connection_adapters/abstract/database_limits.rb +7 -36
  53. data/lib/active_record/connection_adapters/abstract/database_statements.rb +167 -146
  54. data/lib/active_record/connection_adapters/abstract/query_cache.rb +21 -17
  55. data/lib/active_record/connection_adapters/abstract/quoting.rb +98 -47
  56. data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
  57. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +153 -110
  58. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +203 -90
  59. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -4
  60. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +381 -146
  61. data/lib/active_record/connection_adapters/abstract/transaction.rb +155 -68
  62. data/lib/active_record/connection_adapters/abstract_adapter.rb +229 -98
  63. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +243 -275
  64. data/lib/active_record/connection_adapters/column.rb +30 -12
  65. data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
  66. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +31 -0
  67. data/lib/active_record/connection_adapters/mysql/column.rb +1 -1
  68. data/lib/active_record/connection_adapters/mysql/database_statements.rb +86 -32
  69. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -2
  70. data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -7
  71. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +34 -10
  72. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +48 -32
  73. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +14 -6
  74. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +139 -19
  75. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +14 -9
  76. data/lib/active_record/connection_adapters/mysql2_adapter.rb +53 -18
  77. data/lib/active_record/connection_adapters/pool_config.rb +63 -0
  78. data/lib/active_record/connection_adapters/pool_manager.rb +43 -0
  79. data/lib/active_record/connection_adapters/postgresql/column.rb +37 -28
  80. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +38 -54
  81. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -2
  82. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +1 -4
  83. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -5
  84. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +2 -2
  85. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +10 -2
  86. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +0 -1
  87. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +1 -2
  88. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +3 -4
  90. data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
  92. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +1 -1
  93. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +3 -4
  94. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +25 -7
  95. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +1 -1
  96. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +9 -7
  97. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +15 -3
  98. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  99. data/lib/active_record/connection_adapters/postgresql/quoting.rb +47 -10
  100. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -2
  101. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +19 -4
  102. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +107 -91
  103. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +0 -1
  104. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +120 -100
  105. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +31 -26
  106. data/lib/active_record/connection_adapters/postgresql/utils.rb +0 -1
  107. data/lib/active_record/connection_adapters/postgresql_adapter.rb +222 -112
  108. data/lib/active_record/connection_adapters/schema_cache.rb +127 -21
  109. data/lib/active_record/connection_adapters/sql_type_metadata.rb +19 -6
  110. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +144 -0
  111. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +42 -7
  112. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +5 -1
  113. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +77 -13
  114. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +175 -187
  115. data/lib/active_record/connection_adapters/statement_pool.rb +0 -1
  116. data/lib/active_record/connection_adapters.rb +50 -0
  117. data/lib/active_record/connection_handling.rb +285 -33
  118. data/lib/active_record/core.rb +308 -100
  119. data/lib/active_record/counter_cache.rb +8 -30
  120. data/lib/active_record/database_configurations/connection_url_resolver.rb +98 -0
  121. data/lib/active_record/database_configurations/database_config.rb +80 -0
  122. data/lib/active_record/database_configurations/hash_config.rb +96 -0
  123. data/lib/active_record/database_configurations/url_config.rb +53 -0
  124. data/lib/active_record/database_configurations.rb +272 -0
  125. data/lib/active_record/delegated_type.rb +209 -0
  126. data/lib/active_record/destroy_association_async_job.rb +36 -0
  127. data/lib/active_record/dynamic_matchers.rb +3 -4
  128. data/lib/active_record/enum.rb +71 -17
  129. data/lib/active_record/errors.rb +62 -19
  130. data/lib/active_record/explain.rb +10 -6
  131. data/lib/active_record/explain_subscriber.rb +1 -1
  132. data/lib/active_record/fixture_set/file.rb +10 -17
  133. data/lib/active_record/fixture_set/model_metadata.rb +32 -0
  134. data/lib/active_record/fixture_set/render_context.rb +17 -0
  135. data/lib/active_record/fixture_set/table_row.rb +152 -0
  136. data/lib/active_record/fixture_set/table_rows.rb +46 -0
  137. data/lib/active_record/fixtures.rb +197 -481
  138. data/lib/active_record/gem_version.rb +3 -3
  139. data/lib/active_record/inheritance.rb +53 -24
  140. data/lib/active_record/insert_all.rb +208 -0
  141. data/lib/active_record/integration.rb +67 -17
  142. data/lib/active_record/internal_metadata.rb +26 -9
  143. data/lib/active_record/legacy_yaml_adapter.rb +7 -3
  144. data/lib/active_record/locking/optimistic.rb +26 -22
  145. data/lib/active_record/locking/pessimistic.rb +9 -5
  146. data/lib/active_record/log_subscriber.rb +34 -35
  147. data/lib/active_record/middleware/database_selector/resolver/session.rb +48 -0
  148. data/lib/active_record/middleware/database_selector/resolver.rb +92 -0
  149. data/lib/active_record/middleware/database_selector.rb +77 -0
  150. data/lib/active_record/migration/command_recorder.rb +96 -44
  151. data/lib/active_record/migration/compatibility.rb +141 -64
  152. data/lib/active_record/migration/join_table.rb +0 -1
  153. data/lib/active_record/migration.rb +205 -156
  154. data/lib/active_record/model_schema.rb +148 -22
  155. data/lib/active_record/nested_attributes.rb +4 -7
  156. data/lib/active_record/no_touching.rb +8 -1
  157. data/lib/active_record/null_relation.rb +0 -1
  158. data/lib/active_record/persistence.rb +267 -59
  159. data/lib/active_record/query_cache.rb +21 -4
  160. data/lib/active_record/querying.rb +40 -23
  161. data/lib/active_record/railtie.rb +115 -58
  162. data/lib/active_record/railties/controller_runtime.rb +30 -35
  163. data/lib/active_record/railties/databases.rake +402 -78
  164. data/lib/active_record/readonly_attributes.rb +4 -0
  165. data/lib/active_record/reflection.rb +113 -101
  166. data/lib/active_record/relation/batches/batch_enumerator.rb +25 -9
  167. data/lib/active_record/relation/batches.rb +44 -35
  168. data/lib/active_record/relation/calculations.rb +157 -93
  169. data/lib/active_record/relation/delegation.rb +35 -50
  170. data/lib/active_record/relation/finder_methods.rb +65 -40
  171. data/lib/active_record/relation/from_clause.rb +5 -1
  172. data/lib/active_record/relation/merger.rb +32 -40
  173. data/lib/active_record/relation/predicate_builder/array_handler.rb +13 -13
  174. data/lib/active_record/relation/predicate_builder/association_query_value.rb +5 -9
  175. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +1 -2
  176. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +4 -7
  177. data/lib/active_record/relation/predicate_builder/range_handler.rb +3 -23
  178. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  179. data/lib/active_record/relation/predicate_builder.rb +58 -40
  180. data/lib/active_record/relation/query_attribute.rb +13 -8
  181. data/lib/active_record/relation/query_methods.rb +487 -199
  182. data/lib/active_record/relation/record_fetch_warning.rb +3 -3
  183. data/lib/active_record/relation/spawn_methods.rb +9 -9
  184. data/lib/active_record/relation/where_clause.rb +108 -58
  185. data/lib/active_record/relation.rb +375 -104
  186. data/lib/active_record/result.rb +64 -38
  187. data/lib/active_record/runtime_registry.rb +2 -2
  188. data/lib/active_record/sanitization.rb +22 -41
  189. data/lib/active_record/schema.rb +2 -11
  190. data/lib/active_record/schema_dumper.rb +54 -9
  191. data/lib/active_record/schema_migration.rb +7 -9
  192. data/lib/active_record/scoping/default.rb +6 -8
  193. data/lib/active_record/scoping/named.rb +17 -24
  194. data/lib/active_record/scoping.rb +8 -9
  195. data/lib/active_record/secure_token.rb +16 -8
  196. data/lib/active_record/serialization.rb +5 -3
  197. data/lib/active_record/signed_id.rb +116 -0
  198. data/lib/active_record/statement_cache.rb +51 -8
  199. data/lib/active_record/store.rb +88 -9
  200. data/lib/active_record/suppressor.rb +2 -2
  201. data/lib/active_record/table_metadata.rb +39 -43
  202. data/lib/active_record/tasks/database_tasks.rb +276 -81
  203. data/lib/active_record/tasks/mysql_database_tasks.rb +37 -39
  204. data/lib/active_record/tasks/postgresql_database_tasks.rb +27 -32
  205. data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -17
  206. data/lib/active_record/test_databases.rb +24 -0
  207. data/lib/active_record/test_fixtures.rb +246 -0
  208. data/lib/active_record/timestamp.rb +43 -32
  209. data/lib/active_record/touch_later.rb +23 -22
  210. data/lib/active_record/transactions.rb +59 -117
  211. data/lib/active_record/translation.rb +1 -1
  212. data/lib/active_record/type/adapter_specific_registry.rb +3 -13
  213. data/lib/active_record/type/hash_lookup_type_map.rb +0 -1
  214. data/lib/active_record/type/serialized.rb +6 -3
  215. data/lib/active_record/type/time.rb +10 -0
  216. data/lib/active_record/type/type_map.rb +0 -1
  217. data/lib/active_record/type/unsigned_integer.rb +0 -1
  218. data/lib/active_record/type.rb +10 -5
  219. data/lib/active_record/type_caster/connection.rb +15 -15
  220. data/lib/active_record/type_caster/map.rb +8 -8
  221. data/lib/active_record/validations/associated.rb +1 -2
  222. data/lib/active_record/validations/numericality.rb +35 -0
  223. data/lib/active_record/validations/uniqueness.rb +38 -30
  224. data/lib/active_record/validations.rb +4 -3
  225. data/lib/active_record.rb +13 -12
  226. data/lib/arel/alias_predication.rb +9 -0
  227. data/lib/arel/attributes/attribute.rb +41 -0
  228. data/lib/arel/collectors/bind.rb +29 -0
  229. data/lib/arel/collectors/composite.rb +39 -0
  230. data/lib/arel/collectors/plain_string.rb +20 -0
  231. data/lib/arel/collectors/sql_string.rb +27 -0
  232. data/lib/arel/collectors/substitute_binds.rb +35 -0
  233. data/lib/arel/crud.rb +42 -0
  234. data/lib/arel/delete_manager.rb +18 -0
  235. data/lib/arel/errors.rb +9 -0
  236. data/lib/arel/expressions.rb +29 -0
  237. data/lib/arel/factory_methods.rb +49 -0
  238. data/lib/arel/insert_manager.rb +49 -0
  239. data/lib/arel/math.rb +45 -0
  240. data/lib/arel/nodes/and.rb +32 -0
  241. data/lib/arel/nodes/ascending.rb +23 -0
  242. data/lib/arel/nodes/binary.rb +126 -0
  243. data/lib/arel/nodes/bind_param.rb +44 -0
  244. data/lib/arel/nodes/case.rb +55 -0
  245. data/lib/arel/nodes/casted.rb +62 -0
  246. data/lib/arel/nodes/comment.rb +29 -0
  247. data/lib/arel/nodes/count.rb +12 -0
  248. data/lib/arel/nodes/delete_statement.rb +45 -0
  249. data/lib/arel/nodes/descending.rb +23 -0
  250. data/lib/arel/nodes/equality.rb +15 -0
  251. data/lib/arel/nodes/extract.rb +24 -0
  252. data/lib/arel/nodes/false.rb +16 -0
  253. data/lib/arel/nodes/full_outer_join.rb +8 -0
  254. data/lib/arel/nodes/function.rb +44 -0
  255. data/lib/arel/nodes/grouping.rb +11 -0
  256. data/lib/arel/nodes/homogeneous_in.rb +72 -0
  257. data/lib/arel/nodes/in.rb +15 -0
  258. data/lib/arel/nodes/infix_operation.rb +92 -0
  259. data/lib/arel/nodes/inner_join.rb +8 -0
  260. data/lib/arel/nodes/insert_statement.rb +37 -0
  261. data/lib/arel/nodes/join_source.rb +20 -0
  262. data/lib/arel/nodes/matches.rb +18 -0
  263. data/lib/arel/nodes/named_function.rb +23 -0
  264. data/lib/arel/nodes/node.rb +51 -0
  265. data/lib/arel/nodes/node_expression.rb +13 -0
  266. data/lib/arel/nodes/ordering.rb +27 -0
  267. data/lib/arel/nodes/outer_join.rb +8 -0
  268. data/lib/arel/nodes/over.rb +15 -0
  269. data/lib/arel/nodes/regexp.rb +16 -0
  270. data/lib/arel/nodes/right_outer_join.rb +8 -0
  271. data/lib/arel/nodes/select_core.rb +67 -0
  272. data/lib/arel/nodes/select_statement.rb +41 -0
  273. data/lib/arel/nodes/sql_literal.rb +19 -0
  274. data/lib/arel/nodes/string_join.rb +11 -0
  275. data/lib/arel/nodes/table_alias.rb +31 -0
  276. data/lib/arel/nodes/terminal.rb +16 -0
  277. data/lib/arel/nodes/true.rb +16 -0
  278. data/lib/arel/nodes/unary.rb +44 -0
  279. data/lib/arel/nodes/unary_operation.rb +20 -0
  280. data/lib/arel/nodes/unqualified_column.rb +22 -0
  281. data/lib/arel/nodes/update_statement.rb +41 -0
  282. data/lib/arel/nodes/values_list.rb +9 -0
  283. data/lib/arel/nodes/window.rb +126 -0
  284. data/lib/arel/nodes/with.rb +11 -0
  285. data/lib/arel/nodes.rb +70 -0
  286. data/lib/arel/order_predications.rb +13 -0
  287. data/lib/arel/predications.rb +250 -0
  288. data/lib/arel/select_manager.rb +270 -0
  289. data/lib/arel/table.rb +118 -0
  290. data/lib/arel/tree_manager.rb +72 -0
  291. data/lib/arel/update_manager.rb +34 -0
  292. data/lib/arel/visitors/dot.rb +308 -0
  293. data/lib/arel/visitors/mysql.rb +93 -0
  294. data/lib/arel/visitors/postgresql.rb +120 -0
  295. data/lib/arel/visitors/sqlite.rb +38 -0
  296. data/lib/arel/visitors/to_sql.rb +899 -0
  297. data/lib/arel/visitors/visitor.rb +45 -0
  298. data/lib/arel/visitors.rb +13 -0
  299. data/lib/arel/window_predications.rb +9 -0
  300. data/lib/arel.rb +54 -0
  301. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +0 -1
  302. data/lib/rails/generators/active_record/migration/migration_generator.rb +3 -5
  303. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +3 -1
  304. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +7 -5
  305. data/lib/rails/generators/active_record/migration.rb +19 -2
  306. data/lib/rails/generators/active_record/model/model_generator.rb +39 -2
  307. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
  308. data/lib/rails/generators/active_record/model/templates/model.rb.tt +10 -1
  309. metadata +117 -32
  310. data/lib/active_record/attribute_decorators.rb +0 -90
  311. data/lib/active_record/collection_cache_key.rb +0 -53
  312. data/lib/active_record/connection_adapters/connection_specification.rb +0 -287
  313. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -33
  314. data/lib/active_record/define_callbacks.rb +0 -22
  315. data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -19
  316. data/lib/active_record/relation/where_clause_factory.rb +0 -34
@@ -6,7 +6,7 @@ module ActiveRecord
6
6
  class BelongsToPolymorphicAssociation < BelongsToAssociation #:nodoc:
7
7
  def klass
8
8
  type = owner[reflection.foreign_type]
9
- type.presence && type.constantize
9
+ type.presence && owner.class.polymorphic_class_for(type)
10
10
  end
11
11
 
12
12
  def target_changed?
@@ -19,10 +19,6 @@ module ActiveRecord
19
19
  owner[reflection.foreign_type] = record ? record.class.polymorphic_name : nil
20
20
  end
21
21
 
22
- def different_target?(record)
23
- super || record.class != klass
24
- end
25
-
26
22
  def inverse_reflection_for(record)
27
23
  reflection.polymorphic_inverse_of(record.class)
28
24
  end
@@ -18,7 +18,9 @@ module ActiveRecord::Associations::Builder # :nodoc:
18
18
  end
19
19
  self.extensions = []
20
20
 
21
- VALID_OPTIONS = [:class_name, :anonymous_class, :foreign_key, :validate] # :nodoc:
21
+ VALID_OPTIONS = [
22
+ :class_name, :anonymous_class, :primary_key, :foreign_key, :dependent, :validate, :inverse_of, :strict_loading
23
+ ].freeze # :nodoc:
22
24
 
23
25
  def self.build(model, name, scope, options, &block)
24
26
  if model.dangerous_attribute_method?(name)
@@ -27,40 +29,32 @@ module ActiveRecord::Associations::Builder # :nodoc:
27
29
  "Please choose a different association name."
28
30
  end
29
31
 
30
- extension = define_extensions model, name, &block
31
- reflection = create_reflection model, name, scope, options, extension
32
+ reflection = create_reflection(model, name, scope, options, &block)
32
33
  define_accessors model, reflection
33
34
  define_callbacks model, reflection
34
35
  define_validations model, reflection
35
36
  reflection
36
37
  end
37
38
 
38
- def self.create_reflection(model, name, scope, options, extension = nil)
39
+ def self.create_reflection(model, name, scope, options, &block)
39
40
  raise ArgumentError, "association names must be a Symbol" unless name.kind_of?(Symbol)
40
41
 
41
42
  validate_options(options)
42
43
 
43
- scope = build_scope(scope, extension)
44
+ extension = define_extensions(model, name, &block)
45
+ options[:extend] = [*options[:extend], extension] if extension
46
+
47
+ scope = build_scope(scope)
44
48
 
45
49
  ActiveRecord::Reflection.create(macro, name, scope, options, model)
46
50
  end
47
51
 
48
- def self.build_scope(scope, extension)
49
- new_scope = scope
50
-
52
+ def self.build_scope(scope)
51
53
  if scope && scope.arity == 0
52
- new_scope = proc { instance_exec(&scope) }
54
+ proc { instance_exec(&scope) }
55
+ else
56
+ scope
53
57
  end
54
-
55
- if extension
56
- new_scope = wrap_scope new_scope, extension
57
- end
58
-
59
- new_scope
60
- end
61
-
62
- def self.wrap_scope(scope, extension)
63
- scope
64
58
  end
65
59
 
66
60
  def self.macro
@@ -80,7 +74,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
80
74
 
81
75
  def self.define_callbacks(model, reflection)
82
76
  if dependent = reflection.options[:dependent]
83
- check_dependent_options(dependent)
77
+ check_dependent_options(dependent, model)
84
78
  add_destroy_callbacks(model, reflection)
85
79
  end
86
80
 
@@ -126,7 +120,11 @@ module ActiveRecord::Associations::Builder # :nodoc:
126
120
  raise NotImplementedError
127
121
  end
128
122
 
129
- def self.check_dependent_options(dependent)
123
+ def self.check_dependent_options(dependent, model)
124
+ if dependent == :destroy_async && !model.destroy_association_async_job
125
+ err_message = "ActiveJob is required to use destroy_async on associations"
126
+ raise ActiveRecord::ActiveJobRequiredError, err_message
127
+ end
130
128
  unless valid_dependent_options.include? dependent
131
129
  raise ArgumentError, "The :dependent option must be one of #{valid_dependent_options}, but is :#{dependent}"
132
130
  end
@@ -136,5 +134,9 @@ module ActiveRecord::Associations::Builder # :nodoc:
136
134
  name = reflection.name
137
135
  model.before_destroy lambda { |o| o.association(name).handle_dependency }
138
136
  end
137
+
138
+ private_class_method :build_scope, :macro, :valid_options, :validate_options, :define_extensions,
139
+ :define_callbacks, :define_accessors, :define_readers, :define_writers, :define_validations,
140
+ :valid_dependent_options, :check_dependent_options, :add_destroy_callbacks
139
141
  end
140
142
  end
@@ -7,11 +7,14 @@ module ActiveRecord::Associations::Builder # :nodoc:
7
7
  end
8
8
 
9
9
  def self.valid_options(options)
10
- super + [:polymorphic, :touch, :counter_cache, :optional, :default]
10
+ valid = super + [:counter_cache, :optional, :default]
11
+ valid += [:polymorphic, :foreign_type] if options[:polymorphic]
12
+ valid += [:ensuring_owner_was] if options[:dependent] == :destroy_async
13
+ valid
11
14
  end
12
15
 
13
16
  def self.valid_dependent_options
14
- [:destroy, :delete]
17
+ [:destroy, :delete, :destroy_async]
15
18
  end
16
19
 
17
20
  def self.define_callbacks(model, reflection)
@@ -21,58 +24,16 @@ module ActiveRecord::Associations::Builder # :nodoc:
21
24
  add_default_callbacks(model, reflection) if reflection.options[:default]
22
25
  end
23
26
 
24
- def self.define_accessors(mixin, reflection)
25
- super
26
- add_counter_cache_methods mixin
27
- end
28
-
29
- def self.add_counter_cache_methods(mixin)
30
- return if mixin.method_defined? :belongs_to_counter_cache_after_update
31
-
32
- mixin.class_eval do
33
- def belongs_to_counter_cache_after_update(reflection)
34
- foreign_key = reflection.foreign_key
35
- cache_column = reflection.counter_cache_column
36
-
37
- if (@_after_replace_counter_called ||= false)
38
- @_after_replace_counter_called = false
39
- elsif association(reflection.name).target_changed?
40
- if reflection.polymorphic?
41
- model = attribute_in_database(reflection.foreign_type).try(:constantize)
42
- model_was = attribute_before_last_save(reflection.foreign_type).try(:constantize)
43
- else
44
- model = reflection.klass
45
- model_was = reflection.klass
46
- end
47
-
48
- foreign_key_was = attribute_before_last_save foreign_key
49
- foreign_key = attribute_in_database foreign_key
50
-
51
- if foreign_key && model.respond_to?(:increment_counter)
52
- foreign_key = counter_cache_target(reflection, model, foreign_key)
53
- model.increment_counter(cache_column, foreign_key)
54
- end
55
-
56
- if foreign_key_was && model_was.respond_to?(:decrement_counter)
57
- foreign_key_was = counter_cache_target(reflection, model_was, foreign_key_was)
58
- model_was.decrement_counter(cache_column, foreign_key_was)
59
- end
60
- end
61
- end
62
-
63
- private
64
- def counter_cache_target(reflection, model, foreign_key)
65
- primary_key = reflection.association_primary_key(model)
66
- model.unscoped.where!(primary_key => foreign_key)
67
- end
68
- end
69
- end
70
-
71
27
  def self.add_counter_cache_callbacks(model, reflection)
72
28
  cache_column = reflection.counter_cache_column
73
29
 
74
30
  model.after_update lambda { |record|
75
- record.belongs_to_counter_cache_after_update(reflection)
31
+ association = association(reflection.name)
32
+
33
+ if association.target_changed?
34
+ association.increment_counters
35
+ association.decrement_counters_before_last_save
36
+ end
76
37
  }
77
38
 
78
39
  klass = reflection.class_name.safe_constantize
@@ -97,38 +58,44 @@ module ActiveRecord::Associations::Builder # :nodoc:
97
58
 
98
59
  if old_record
99
60
  if touch != true
100
- old_record.send(touch_method, touch)
61
+ old_record.public_send(touch_method, touch)
101
62
  else
102
- old_record.send(touch_method)
63
+ old_record.public_send(touch_method)
103
64
  end
104
65
  end
105
66
  end
106
67
 
107
- record = o.send name
68
+ record = o.public_send name
108
69
  if record && record.persisted?
109
70
  if touch != true
110
- record.send(touch_method, touch)
71
+ record.public_send(touch_method, touch)
111
72
  else
112
- record.send(touch_method)
73
+ record.public_send(touch_method)
113
74
  end
114
75
  end
115
76
  end
116
77
 
117
78
  def self.add_touch_callbacks(model, reflection)
118
79
  foreign_key = reflection.foreign_key
119
- n = reflection.name
80
+ name = reflection.name
120
81
  touch = reflection.options[:touch]
121
82
 
122
83
  callback = lambda { |changes_method| lambda { |record|
123
- BelongsTo.touch_record(record, record.send(changes_method), foreign_key, n, touch, belongs_to_touch_method)
84
+ BelongsTo.touch_record(record, record.send(changes_method), foreign_key, name, touch, belongs_to_touch_method)
124
85
  }}
125
86
 
126
- unless reflection.counter_cache_column
87
+ if reflection.counter_cache_column
88
+ touch_callback = callback.(:saved_changes)
89
+ update_callback = lambda { |record|
90
+ instance_exec(record, &touch_callback) unless association(reflection.name).target_changed?
91
+ }
92
+ model.after_update update_callback, if: :saved_changes?
93
+ else
127
94
  model.after_create callback.(:saved_changes), if: :saved_changes?
95
+ model.after_update callback.(:saved_changes), if: :saved_changes?
128
96
  model.after_destroy callback.(:changes_to_save)
129
97
  end
130
98
 
131
- model.after_update callback.(:saved_changes), if: :saved_changes?
132
99
  model.after_touch callback.(:changes_to_save)
133
100
  end
134
101
 
@@ -159,5 +126,8 @@ module ActiveRecord::Associations::Builder # :nodoc:
159
126
  model.validates_presence_of reflection.name, message: :required
160
127
  end
161
128
  end
129
+
130
+ private_class_method :macro, :valid_options, :valid_dependent_options, :define_callbacks, :define_validations,
131
+ :add_counter_cache_callbacks, :add_touch_callbacks, :add_default_callbacks, :add_destroy_callbacks
162
132
  end
163
133
  end
@@ -7,8 +7,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
7
7
  CALLBACKS = [:before_add, :after_add, :before_remove, :after_remove]
8
8
 
9
9
  def self.valid_options(options)
10
- super + [:table_name, :before_add,
11
- :after_add, :before_remove, :after_remove, :extend]
10
+ super + [:before_add, :after_add, :before_remove, :after_remove, :extend]
12
11
  end
13
12
 
14
13
  def self.define_callbacks(model, reflection)
@@ -20,19 +19,21 @@ module ActiveRecord::Associations::Builder # :nodoc:
20
19
  }
21
20
  end
22
21
 
23
- def self.define_extensions(model, name)
22
+ def self.define_extensions(model, name, &block)
24
23
  if block_given?
25
- extension_module_name = "#{model.name.demodulize}#{name.to_s.camelize}AssociationExtension"
26
- extension = Module.new(&Proc.new)
27
- model.parent.const_set(extension_module_name, extension)
24
+ extension_module_name = "#{name.to_s.camelize}AssociationExtension"
25
+ extension = Module.new(&block)
26
+ model.const_set(extension_module_name, extension)
28
27
  end
29
28
  end
30
29
 
31
30
  def self.define_callback(model, callback_name, name, options)
32
31
  full_callback_name = "#{callback_name}_for_#{name}"
33
32
 
34
- # TODO : why do i need method_defined? I think its because of the inheritance chain
35
- model.class_attribute full_callback_name unless model.method_defined?(full_callback_name)
33
+ unless model.method_defined?(full_callback_name)
34
+ model.class_attribute(full_callback_name, instance_accessor: false, instance_predicate: false)
35
+ end
36
+
36
37
  callbacks = Array(options[callback_name.to_sym]).map do |callback|
37
38
  case callback
38
39
  when Symbol
@@ -67,16 +68,6 @@ module ActiveRecord::Associations::Builder # :nodoc:
67
68
  CODE
68
69
  end
69
70
 
70
- def self.wrap_scope(scope, mod)
71
- if scope
72
- if scope.arity > 0
73
- proc { |owner| instance_exec(owner, &scope).extending(mod) }
74
- else
75
- proc { instance_exec(&scope).extending(mod) }
76
- end
77
- else
78
- proc { extending(mod) }
79
- end
80
- end
71
+ private_class_method :valid_options, :define_callback, :define_extensions, :define_readers, :define_writers
81
72
  end
82
73
  end
@@ -2,39 +2,6 @@
2
2
 
3
3
  module ActiveRecord::Associations::Builder # :nodoc:
4
4
  class HasAndBelongsToMany # :nodoc:
5
- class JoinTableResolver # :nodoc:
6
- KnownTable = Struct.new :join_table
7
-
8
- class KnownClass # :nodoc:
9
- def initialize(lhs_class, rhs_class_name)
10
- @lhs_class = lhs_class
11
- @rhs_class_name = rhs_class_name
12
- @join_table = nil
13
- end
14
-
15
- def join_table
16
- @join_table ||= [@lhs_class.table_name, klass.table_name].sort.join("\0").gsub(/^(.*[._])(.+)\0\1(.+)/, '\1\2_\3').tr("\0", "_")
17
- end
18
-
19
- private
20
-
21
- def klass
22
- @lhs_class.send(:compute_type, @rhs_class_name)
23
- end
24
- end
25
-
26
- def self.build(lhs_class, name, options)
27
- if options[:join_table]
28
- KnownTable.new options[:join_table].to_s
29
- else
30
- class_name = options.fetch(:class_name) {
31
- name.to_s.camelize.singularize
32
- }
33
- KnownClass.new lhs_class, class_name.to_s
34
- end
35
- end
36
- end
37
-
38
5
  attr_reader :lhs_model, :association_name, :options
39
6
 
40
7
  def initialize(association_name, lhs_model, options)
@@ -44,8 +11,6 @@ module ActiveRecord::Associations::Builder # :nodoc:
44
11
  end
45
12
 
46
13
  def through_model
47
- habtm = JoinTableResolver.build lhs_model, association_name, options
48
-
49
14
  join_model = Class.new(ActiveRecord::Base) {
50
15
  class << self
51
16
  attr_accessor :left_model
@@ -56,7 +21,9 @@ module ActiveRecord::Associations::Builder # :nodoc:
56
21
  end
57
22
 
58
23
  def self.table_name
59
- table_name_resolver.join_table
24
+ # Table name needs to be resolved lazily
25
+ # because RHS class might not have been loaded
26
+ @table_name ||= table_name_resolver.call
60
27
  end
61
28
 
62
29
  def self.compute_type(class_name)
@@ -79,14 +46,13 @@ module ActiveRecord::Associations::Builder # :nodoc:
79
46
  end
80
47
 
81
48
  private
82
-
83
49
  def self.suppress_composite_primary_key(pk)
84
50
  pk unless pk.is_a?(Array)
85
51
  end
86
52
  }
87
53
 
88
54
  join_model.name = "HABTM_#{association_name.to_s.camelize}"
89
- join_model.table_name_resolver = habtm
55
+ join_model.table_name_resolver = -> { table_name }
90
56
  join_model.left_model = lhs_model
91
57
 
92
58
  join_model.add_left_association :left_side, anonymous_class: lhs_model
@@ -96,7 +62,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
96
62
 
97
63
  def middle_reflection(join_model)
98
64
  middle_name = [lhs_model.name.downcase.pluralize,
99
- association_name].join("_".freeze).gsub("::".freeze, "_".freeze).to_sym
65
+ association_name.to_s].sort.join("_").gsub("::", "_").to_sym
100
66
  middle_options = middle_options join_model
101
67
 
102
68
  HasMany.create_reflection(lhs_model,
@@ -106,17 +72,27 @@ module ActiveRecord::Associations::Builder # :nodoc:
106
72
  end
107
73
 
108
74
  private
109
-
110
75
  def middle_options(join_model)
111
76
  middle_options = {}
112
77
  middle_options[:class_name] = "#{lhs_model.name}::#{join_model.name}"
113
- middle_options[:source] = join_model.left_reflection.name
114
78
  if options.key? :foreign_key
115
79
  middle_options[:foreign_key] = options[:foreign_key]
116
80
  end
117
81
  middle_options
118
82
  end
119
83
 
84
+ def table_name
85
+ if options[:join_table]
86
+ options[:join_table].to_s
87
+ else
88
+ class_name = options.fetch(:class_name) {
89
+ association_name.to_s.camelize.singularize
90
+ }
91
+ klass = lhs_model.send(:compute_type, class_name.to_s)
92
+ [lhs_model.table_name, klass.table_name].sort.join("\0").gsub(/^(.*[._])(.+)\0\1(.+)/, '\1\2_\3').tr("\0", "_")
93
+ end
94
+ end
95
+
120
96
  def belongs_to_options(options)
121
97
  rhs_options = {}
122
98
 
@@ -7,11 +7,17 @@ module ActiveRecord::Associations::Builder # :nodoc:
7
7
  end
8
8
 
9
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]
10
+ valid = super + [:counter_cache, :join_table, :index_errors, :ensuring_owner_was]
11
+ valid += [:as, :foreign_type] if options[:as]
12
+ valid += [:through, :source, :source_type] if options[:through]
13
+ valid += [:ensuring_owner_was] if options[:dependent] == :destroy_async
14
+ valid
11
15
  end
12
16
 
13
17
  def self.valid_dependent_options
14
- [:destroy, :delete_all, :nullify, :restrict_with_error, :restrict_with_exception]
18
+ [:destroy, :delete_all, :nullify, :restrict_with_error, :restrict_with_exception, :destroy_async]
15
19
  end
20
+
21
+ private_class_method :macro, :valid_options, :valid_dependent_options
16
22
  end
17
23
  end
@@ -7,13 +7,20 @@ module ActiveRecord::Associations::Builder # :nodoc:
7
7
  end
8
8
 
9
9
  def self.valid_options(options)
10
- valid = super + [:as]
10
+ valid = super
11
+ valid += [:as, :foreign_type] if options[:as]
12
+ valid += [:ensuring_owner_was] if options[:dependent] == :destroy_async
11
13
  valid += [:through, :source, :source_type] if options[:through]
12
14
  valid
13
15
  end
14
16
 
15
17
  def self.valid_dependent_options
16
- [:destroy, :delete, :nullify, :restrict_with_error, :restrict_with_exception]
18
+ [:destroy, :destroy_async, :delete, :nullify, :restrict_with_error, :restrict_with_exception]
19
+ end
20
+
21
+ def self.define_callbacks(model, reflection)
22
+ super
23
+ add_touch_callbacks(model, reflection) if reflection.options[:touch]
17
24
  end
18
25
 
19
26
  def self.add_destroy_callbacks(model, reflection)
@@ -26,5 +33,29 @@ module ActiveRecord::Associations::Builder # :nodoc:
26
33
  model.validates_presence_of reflection.name, message: :required
27
34
  end
28
35
  end
36
+
37
+ def self.touch_record(record, name, touch)
38
+ instance = record.send(name)
39
+
40
+ if instance&.persisted?
41
+ touch != true ?
42
+ instance.touch(touch) : instance.touch
43
+ end
44
+ end
45
+
46
+ def self.add_touch_callbacks(model, reflection)
47
+ name = reflection.name
48
+ touch = reflection.options[:touch]
49
+
50
+ callback = -> (record) { HasOne.touch_record(record, name, touch) }
51
+ model.after_create callback, if: :saved_changes?
52
+ model.after_create_commit { association(name).reset_negative_cache }
53
+ model.after_update callback, if: :saved_changes?
54
+ model.after_destroy callback
55
+ model.after_touch callback
56
+ end
57
+
58
+ private_class_method :macro, :valid_options, :valid_dependent_options, :add_destroy_callbacks,
59
+ :define_callbacks, :define_validations, :add_touch_callbacks
29
60
  end
30
61
  end
@@ -5,7 +5,7 @@
5
5
  module ActiveRecord::Associations::Builder # :nodoc:
6
6
  class SingularAssociation < Association #:nodoc:
7
7
  def self.valid_options(options)
8
- super + [:foreign_type, :dependent, :primary_key, :inverse_of, :required]
8
+ super + [:required, :touch]
9
9
  end
10
10
 
11
11
  def self.define_accessors(model, reflection)
@@ -38,5 +38,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
38
38
  end
39
39
  CODE
40
40
  end
41
+
42
+ private_class_method :valid_options, :define_accessors, :define_constructors
41
43
  end
42
44
  end
@@ -56,7 +56,7 @@ module ActiveRecord
56
56
  def ids_writer(ids)
57
57
  primary_key = reflection.association_primary_key
58
58
  pk_type = klass.type_for_attribute(primary_key)
59
- ids = Array(ids).reject(&:blank?)
59
+ ids = Array(ids).compact_blank
60
60
  ids.map! { |i| pk_type.cast(i) }
61
61
 
62
62
  records = klass.where(primary_key => ids).index_by do |r|
@@ -101,11 +101,11 @@ module ActiveRecord
101
101
  end
102
102
  end
103
103
 
104
- def build(attributes = {}, &block)
104
+ def build(attributes = nil, &block)
105
105
  if attributes.is_a?(Array)
106
106
  attributes.collect { |attr| build(attr, &block) }
107
107
  else
108
- add_to_target(build_record(attributes, &block))
108
+ add_to_target(build_record(attributes, &block), replace: true)
109
109
  end
110
110
  end
111
111
 
@@ -211,9 +211,11 @@ module ActiveRecord
211
211
  def size
212
212
  if !find_target? || loaded?
213
213
  target.size
214
+ elsif @association_ids
215
+ @association_ids.size
214
216
  elsif !association_scope.group_values.empty?
215
217
  load_target.size
216
- elsif !association_scope.distinct_value && target.is_a?(Array)
218
+ elsif !association_scope.distinct_value && !target.empty?
217
219
  unsaved_records = target.select(&:new_record?)
218
220
  unsaved_records.size + count_records
219
221
  else
@@ -226,14 +228,14 @@ module ActiveRecord
226
228
  # If the collection has been loaded
227
229
  # it is equivalent to <tt>collection.size.zero?</tt>. If the
228
230
  # collection has not been loaded, it is equivalent to
229
- # <tt>collection.exists?</tt>. If the collection has not already been
231
+ # <tt>!collection.exists?</tt>. If the collection has not already been
230
232
  # loaded and you are going to fetch the records anyway it is better to
231
233
  # check <tt>collection.length.zero?</tt>.
232
234
  def empty?
233
- if loaded?
235
+ if loaded? || @association_ids || reflection.has_cached_counter?
234
236
  size.zero?
235
237
  else
236
- @target.blank? && !scope.exists?
238
+ target.empty? && !scope.exists?
237
239
  end
238
240
  end
239
241
 
@@ -276,13 +278,24 @@ module ActiveRecord
276
278
  target
277
279
  end
278
280
 
279
- def add_to_target(record, skip_callbacks = false, &block)
280
- if association_scope.distinct_value
281
+ def add_to_target(record, skip_callbacks: false, replace: false, &block)
282
+ if replace || association_scope.distinct_value
281
283
  index = @target.index(record)
282
284
  end
283
285
  replace_on_target(record, index, skip_callbacks, &block)
284
286
  end
285
287
 
288
+ def target=(record)
289
+ return super unless ActiveRecord::Base.has_many_inversing
290
+
291
+ case record
292
+ when Array
293
+ super
294
+ else
295
+ add_to_target(record, skip_callbacks: true, replace: true)
296
+ end
297
+ end
298
+
286
299
  def scope
287
300
  scope = super
288
301
  scope.none! if null_scope?
@@ -295,28 +308,13 @@ module ActiveRecord
295
308
 
296
309
  def find_from_target?
297
310
  loaded? ||
311
+ owner.strict_loading? ||
312
+ reflection.strict_loading? ||
298
313
  owner.new_record? ||
299
314
  target.any? { |record| record.new_record? || record.changed? }
300
315
  end
301
316
 
302
317
  private
303
-
304
- def find_target
305
- scope = self.scope
306
- return scope.to_a if skip_statement_cache?(scope)
307
-
308
- conn = klass.connection
309
- sc = reflection.association_scope_cache(conn, owner) do |params|
310
- as = AssociationScope.create { params.bind }
311
- target_scope.merge!(as.scope(self))
312
- end
313
-
314
- binds = AssociationScope.get_bind_values(owner, reflection.chain)
315
- sc.execute(binds, conn) do |record|
316
- set_inverse_instance(record)
317
- end
318
- end
319
-
320
318
  # We have some records loaded from the database (persisted) and some that are
321
319
  # in-memory (memory). The same record may be represented in the persisted array
322
320
  # and in the memory array.
@@ -393,10 +391,12 @@ module ActiveRecord
393
391
  end
394
392
 
395
393
  def remove_records(existing_records, records, method)
396
- records.each { |record| callback(:before_remove, record) }
394
+ catch(:abort) do
395
+ records.each { |record| callback(:before_remove, record) }
396
+ end || return
397
397
 
398
398
  delete_records(existing_records, method) if existing_records.any?
399
- records.each { |record| target.delete(record) }
399
+ @target -= records
400
400
  @association_ids = nil
401
401
 
402
402
  records.each { |record| callback(:after_remove, record) }
@@ -449,7 +449,9 @@ module ActiveRecord
449
449
  end
450
450
 
451
451
  def replace_on_target(record, index, skip_callbacks)
452
- callback(:before_add, record) unless skip_callbacks
452
+ catch(:abort) do
453
+ callback(:before_add, record)
454
+ end || return unless skip_callbacks
453
455
 
454
456
  set_inverse_instance(record)
455
457