activerecord 5.2.5 → 6.0.4.6

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 (294) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +913 -549
  3. data/MIT-LICENSE +3 -1
  4. data/README.rdoc +5 -3
  5. data/examples/performance.rb +1 -1
  6. data/lib/active_record/advisory_lock_base.rb +18 -0
  7. data/lib/active_record/aggregations.rb +4 -3
  8. data/lib/active_record/association_relation.rb +10 -8
  9. data/lib/active_record/associations/alias_tracker.rb +0 -1
  10. data/lib/active_record/associations/association.rb +55 -19
  11. data/lib/active_record/associations/association_scope.rb +11 -7
  12. data/lib/active_record/associations/belongs_to_association.rb +36 -42
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +0 -4
  14. data/lib/active_record/associations/builder/association.rb +14 -18
  15. data/lib/active_record/associations/builder/belongs_to.rb +19 -52
  16. data/lib/active_record/associations/builder/collection_association.rb +3 -13
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +17 -40
  18. data/lib/active_record/associations/builder/has_many.rb +2 -0
  19. data/lib/active_record/associations/builder/has_one.rb +35 -1
  20. data/lib/active_record/associations/builder/singular_association.rb +2 -0
  21. data/lib/active_record/associations/collection_association.rb +19 -23
  22. data/lib/active_record/associations/collection_proxy.rb +14 -17
  23. data/lib/active_record/associations/foreign_association.rb +7 -0
  24. data/lib/active_record/associations/has_many_association.rb +2 -11
  25. data/lib/active_record/associations/has_many_through_association.rb +14 -14
  26. data/lib/active_record/associations/has_one_association.rb +28 -30
  27. data/lib/active_record/associations/has_one_through_association.rb +5 -5
  28. data/lib/active_record/associations/join_dependency/join_association.rb +16 -10
  29. data/lib/active_record/associations/join_dependency/join_part.rb +4 -4
  30. data/lib/active_record/associations/join_dependency.rb +47 -30
  31. data/lib/active_record/associations/preloader/association.rb +61 -41
  32. data/lib/active_record/associations/preloader/through_association.rb +48 -39
  33. data/lib/active_record/associations/preloader.rb +44 -33
  34. data/lib/active_record/associations/singular_association.rb +2 -16
  35. data/lib/active_record/associations/through_association.rb +1 -1
  36. data/lib/active_record/associations.rb +21 -16
  37. data/lib/active_record/attribute_assignment.rb +7 -11
  38. data/lib/active_record/attribute_decorators.rb +0 -2
  39. data/lib/active_record/attribute_methods/before_type_cast.rb +4 -2
  40. data/lib/active_record/attribute_methods/dirty.rb +111 -40
  41. data/lib/active_record/attribute_methods/primary_key.rb +15 -24
  42. data/lib/active_record/attribute_methods/query.rb +2 -3
  43. data/lib/active_record/attribute_methods/read.rb +15 -54
  44. data/lib/active_record/attribute_methods/serialization.rb +1 -2
  45. data/lib/active_record/attribute_methods/time_zone_conversion.rb +1 -3
  46. data/lib/active_record/attribute_methods/write.rb +17 -25
  47. data/lib/active_record/attribute_methods.rb +28 -100
  48. data/lib/active_record/attributes.rb +13 -1
  49. data/lib/active_record/autosave_association.rb +12 -14
  50. data/lib/active_record/base.rb +2 -3
  51. data/lib/active_record/callbacks.rb +6 -21
  52. data/lib/active_record/coders/yaml_column.rb +0 -1
  53. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +109 -18
  54. data/lib/active_record/connection_adapters/abstract/database_limits.rb +17 -4
  55. data/lib/active_record/connection_adapters/abstract/database_statements.rb +102 -124
  56. data/lib/active_record/connection_adapters/abstract/query_cache.rb +18 -9
  57. data/lib/active_record/connection_adapters/abstract/quoting.rb +68 -17
  58. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +20 -14
  59. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +105 -72
  60. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +1 -3
  61. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +175 -79
  62. data/lib/active_record/connection_adapters/abstract/transaction.rb +96 -57
  63. data/lib/active_record/connection_adapters/abstract_adapter.rb +197 -43
  64. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +149 -217
  65. data/lib/active_record/connection_adapters/column.rb +17 -13
  66. data/lib/active_record/connection_adapters/connection_specification.rb +54 -45
  67. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +6 -10
  68. data/lib/active_record/connection_adapters/mysql/column.rb +1 -1
  69. data/lib/active_record/connection_adapters/mysql/database_statements.rb +70 -14
  70. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +0 -1
  71. data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -7
  72. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +4 -6
  73. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +40 -32
  74. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +14 -6
  75. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +139 -19
  76. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +6 -10
  77. data/lib/active_record/connection_adapters/mysql2_adapter.rb +26 -10
  78. data/lib/active_record/connection_adapters/postgresql/column.rb +17 -31
  79. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +26 -1
  80. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -2
  81. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +1 -4
  82. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +8 -0
  83. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +0 -1
  84. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +1 -2
  85. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +1 -2
  86. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +1 -1
  87. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +1 -2
  88. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +25 -7
  89. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +1 -1
  90. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +9 -7
  91. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +5 -3
  92. data/lib/active_record/connection_adapters/postgresql/quoting.rb +44 -7
  93. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +1 -1
  94. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +14 -3
  95. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +107 -91
  96. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +0 -1
  97. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +63 -75
  98. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +24 -27
  99. data/lib/active_record/connection_adapters/postgresql/utils.rb +0 -1
  100. data/lib/active_record/connection_adapters/postgresql_adapter.rb +168 -75
  101. data/lib/active_record/connection_adapters/schema_cache.rb +37 -14
  102. data/lib/active_record/connection_adapters/sql_type_metadata.rb +11 -8
  103. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +119 -0
  104. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +42 -7
  105. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +43 -12
  106. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +137 -147
  107. data/lib/active_record/connection_adapters/statement_pool.rb +0 -1
  108. data/lib/active_record/connection_handling.rb +139 -26
  109. data/lib/active_record/core.rb +107 -66
  110. data/lib/active_record/counter_cache.rb +8 -30
  111. data/lib/active_record/database_configurations/database_config.rb +37 -0
  112. data/lib/active_record/database_configurations/hash_config.rb +50 -0
  113. data/lib/active_record/database_configurations/url_config.rb +78 -0
  114. data/lib/active_record/database_configurations.rb +233 -0
  115. data/lib/active_record/dynamic_matchers.rb +3 -4
  116. data/lib/active_record/enum.rb +44 -7
  117. data/lib/active_record/errors.rb +15 -7
  118. data/lib/active_record/explain.rb +1 -2
  119. data/lib/active_record/fixture_set/model_metadata.rb +33 -0
  120. data/lib/active_record/fixture_set/render_context.rb +17 -0
  121. data/lib/active_record/fixture_set/table_row.rb +152 -0
  122. data/lib/active_record/fixture_set/table_rows.rb +46 -0
  123. data/lib/active_record/fixtures.rb +144 -474
  124. data/lib/active_record/gem_version.rb +4 -4
  125. data/lib/active_record/inheritance.rb +13 -6
  126. data/lib/active_record/insert_all.rb +179 -0
  127. data/lib/active_record/integration.rb +68 -16
  128. data/lib/active_record/internal_metadata.rb +11 -3
  129. data/lib/active_record/locking/optimistic.rb +14 -7
  130. data/lib/active_record/locking/pessimistic.rb +3 -3
  131. data/lib/active_record/log_subscriber.rb +8 -27
  132. data/lib/active_record/middleware/database_selector/resolver/session.rb +45 -0
  133. data/lib/active_record/middleware/database_selector/resolver.rb +87 -0
  134. data/lib/active_record/middleware/database_selector.rb +74 -0
  135. data/lib/active_record/migration/command_recorder.rb +54 -22
  136. data/lib/active_record/migration/compatibility.rb +79 -52
  137. data/lib/active_record/migration/join_table.rb +0 -1
  138. data/lib/active_record/migration.rb +104 -85
  139. data/lib/active_record/model_schema.rb +62 -11
  140. data/lib/active_record/nested_attributes.rb +2 -4
  141. data/lib/active_record/no_touching.rb +9 -2
  142. data/lib/active_record/null_relation.rb +0 -1
  143. data/lib/active_record/persistence.rb +232 -29
  144. data/lib/active_record/query_cache.rb +11 -4
  145. data/lib/active_record/querying.rb +33 -21
  146. data/lib/active_record/railtie.rb +80 -43
  147. data/lib/active_record/railties/collection_cache_association_loading.rb +34 -0
  148. data/lib/active_record/railties/controller_runtime.rb +30 -35
  149. data/lib/active_record/railties/databases.rake +199 -46
  150. data/lib/active_record/reflection.rb +51 -51
  151. data/lib/active_record/relation/batches.rb +13 -11
  152. data/lib/active_record/relation/calculations.rb +55 -49
  153. data/lib/active_record/relation/delegation.rb +35 -50
  154. data/lib/active_record/relation/finder_methods.rb +23 -28
  155. data/lib/active_record/relation/from_clause.rb +4 -0
  156. data/lib/active_record/relation/merger.rb +12 -17
  157. data/lib/active_record/relation/predicate_builder/array_handler.rb +5 -4
  158. data/lib/active_record/relation/predicate_builder/association_query_value.rb +1 -4
  159. data/lib/active_record/relation/predicate_builder/base_handler.rb +1 -2
  160. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +1 -2
  161. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +1 -4
  162. data/lib/active_record/relation/predicate_builder/range_handler.rb +3 -23
  163. data/lib/active_record/relation/predicate_builder.rb +5 -11
  164. data/lib/active_record/relation/query_attribute.rb +13 -8
  165. data/lib/active_record/relation/query_methods.rb +232 -69
  166. data/lib/active_record/relation/spawn_methods.rb +1 -2
  167. data/lib/active_record/relation/where_clause.rb +14 -11
  168. data/lib/active_record/relation/where_clause_factory.rb +1 -2
  169. data/lib/active_record/relation.rb +326 -81
  170. data/lib/active_record/result.rb +30 -12
  171. data/lib/active_record/sanitization.rb +32 -40
  172. data/lib/active_record/schema.rb +2 -11
  173. data/lib/active_record/schema_dumper.rb +22 -7
  174. data/lib/active_record/schema_migration.rb +6 -2
  175. data/lib/active_record/scoping/default.rb +4 -6
  176. data/lib/active_record/scoping/named.rb +25 -16
  177. data/lib/active_record/scoping.rb +8 -9
  178. data/lib/active_record/statement_cache.rb +30 -3
  179. data/lib/active_record/store.rb +87 -8
  180. data/lib/active_record/suppressor.rb +2 -2
  181. data/lib/active_record/table_metadata.rb +23 -15
  182. data/lib/active_record/tasks/database_tasks.rb +194 -25
  183. data/lib/active_record/tasks/mysql_database_tasks.rb +5 -6
  184. data/lib/active_record/tasks/postgresql_database_tasks.rb +5 -8
  185. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -9
  186. data/lib/active_record/test_databases.rb +23 -0
  187. data/lib/active_record/test_fixtures.rb +243 -0
  188. data/lib/active_record/timestamp.rb +39 -26
  189. data/lib/active_record/touch_later.rb +5 -4
  190. data/lib/active_record/transactions.rb +64 -73
  191. data/lib/active_record/translation.rb +1 -1
  192. data/lib/active_record/type/adapter_specific_registry.rb +3 -13
  193. data/lib/active_record/type/hash_lookup_type_map.rb +0 -1
  194. data/lib/active_record/type/serialized.rb +0 -1
  195. data/lib/active_record/type/time.rb +10 -0
  196. data/lib/active_record/type/type_map.rb +0 -1
  197. data/lib/active_record/type/unsigned_integer.rb +0 -1
  198. data/lib/active_record/type.rb +3 -5
  199. data/lib/active_record/type_caster/connection.rb +15 -14
  200. data/lib/active_record/type_caster/map.rb +1 -4
  201. data/lib/active_record/validations/associated.rb +0 -1
  202. data/lib/active_record/validations/uniqueness.rb +15 -27
  203. data/lib/active_record/validations.rb +3 -3
  204. data/lib/active_record.rb +10 -2
  205. data/lib/arel/alias_predication.rb +9 -0
  206. data/lib/arel/attributes/attribute.rb +37 -0
  207. data/lib/arel/attributes.rb +22 -0
  208. data/lib/arel/collectors/bind.rb +24 -0
  209. data/lib/arel/collectors/composite.rb +31 -0
  210. data/lib/arel/collectors/plain_string.rb +20 -0
  211. data/lib/arel/collectors/sql_string.rb +20 -0
  212. data/lib/arel/collectors/substitute_binds.rb +28 -0
  213. data/lib/arel/crud.rb +42 -0
  214. data/lib/arel/delete_manager.rb +18 -0
  215. data/lib/arel/errors.rb +9 -0
  216. data/lib/arel/expressions.rb +29 -0
  217. data/lib/arel/factory_methods.rb +49 -0
  218. data/lib/arel/insert_manager.rb +49 -0
  219. data/lib/arel/math.rb +45 -0
  220. data/lib/arel/nodes/and.rb +32 -0
  221. data/lib/arel/nodes/ascending.rb +23 -0
  222. data/lib/arel/nodes/binary.rb +52 -0
  223. data/lib/arel/nodes/bind_param.rb +36 -0
  224. data/lib/arel/nodes/case.rb +55 -0
  225. data/lib/arel/nodes/casted.rb +50 -0
  226. data/lib/arel/nodes/comment.rb +29 -0
  227. data/lib/arel/nodes/count.rb +12 -0
  228. data/lib/arel/nodes/delete_statement.rb +45 -0
  229. data/lib/arel/nodes/descending.rb +23 -0
  230. data/lib/arel/nodes/equality.rb +18 -0
  231. data/lib/arel/nodes/extract.rb +24 -0
  232. data/lib/arel/nodes/false.rb +16 -0
  233. data/lib/arel/nodes/full_outer_join.rb +8 -0
  234. data/lib/arel/nodes/function.rb +44 -0
  235. data/lib/arel/nodes/grouping.rb +8 -0
  236. data/lib/arel/nodes/in.rb +8 -0
  237. data/lib/arel/nodes/infix_operation.rb +80 -0
  238. data/lib/arel/nodes/inner_join.rb +8 -0
  239. data/lib/arel/nodes/insert_statement.rb +37 -0
  240. data/lib/arel/nodes/join_source.rb +20 -0
  241. data/lib/arel/nodes/matches.rb +18 -0
  242. data/lib/arel/nodes/named_function.rb +23 -0
  243. data/lib/arel/nodes/node.rb +50 -0
  244. data/lib/arel/nodes/node_expression.rb +13 -0
  245. data/lib/arel/nodes/outer_join.rb +8 -0
  246. data/lib/arel/nodes/over.rb +15 -0
  247. data/lib/arel/nodes/regexp.rb +16 -0
  248. data/lib/arel/nodes/right_outer_join.rb +8 -0
  249. data/lib/arel/nodes/select_core.rb +67 -0
  250. data/lib/arel/nodes/select_statement.rb +41 -0
  251. data/lib/arel/nodes/sql_literal.rb +16 -0
  252. data/lib/arel/nodes/string_join.rb +11 -0
  253. data/lib/arel/nodes/table_alias.rb +27 -0
  254. data/lib/arel/nodes/terminal.rb +16 -0
  255. data/lib/arel/nodes/true.rb +16 -0
  256. data/lib/arel/nodes/unary.rb +45 -0
  257. data/lib/arel/nodes/unary_operation.rb +20 -0
  258. data/lib/arel/nodes/unqualified_column.rb +22 -0
  259. data/lib/arel/nodes/update_statement.rb +41 -0
  260. data/lib/arel/nodes/values_list.rb +9 -0
  261. data/lib/arel/nodes/window.rb +126 -0
  262. data/lib/arel/nodes/with.rb +11 -0
  263. data/lib/arel/nodes.rb +68 -0
  264. data/lib/arel/order_predications.rb +13 -0
  265. data/lib/arel/predications.rb +256 -0
  266. data/lib/arel/select_manager.rb +271 -0
  267. data/lib/arel/table.rb +110 -0
  268. data/lib/arel/tree_manager.rb +72 -0
  269. data/lib/arel/update_manager.rb +34 -0
  270. data/lib/arel/visitors/depth_first.rb +203 -0
  271. data/lib/arel/visitors/dot.rb +296 -0
  272. data/lib/arel/visitors/ibm_db.rb +34 -0
  273. data/lib/arel/visitors/informix.rb +62 -0
  274. data/lib/arel/visitors/mssql.rb +156 -0
  275. data/lib/arel/visitors/mysql.rb +83 -0
  276. data/lib/arel/visitors/oracle.rb +158 -0
  277. data/lib/arel/visitors/oracle12.rb +65 -0
  278. data/lib/arel/visitors/postgresql.rb +109 -0
  279. data/lib/arel/visitors/sqlite.rb +38 -0
  280. data/lib/arel/visitors/to_sql.rb +888 -0
  281. data/lib/arel/visitors/visitor.rb +45 -0
  282. data/lib/arel/visitors/where_sql.rb +22 -0
  283. data/lib/arel/visitors.rb +20 -0
  284. data/lib/arel/window_predications.rb +9 -0
  285. data/lib/arel.rb +62 -0
  286. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +0 -1
  287. data/lib/rails/generators/active_record/migration/migration_generator.rb +2 -5
  288. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +1 -1
  289. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -2
  290. data/lib/rails/generators/active_record/migration.rb +14 -2
  291. data/lib/rails/generators/active_record/model/model_generator.rb +1 -1
  292. data/lib/rails/generators/active_record/model/templates/model.rb.tt +10 -1
  293. metadata +115 -29
  294. data/lib/active_record/collection_cache_key.rb +0 -53
@@ -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,7 +72,6 @@ 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}"
@@ -117,6 +82,18 @@ module ActiveRecord::Associations::Builder # :nodoc:
117
82
  middle_options
118
83
  end
119
84
 
85
+ def table_name
86
+ if options[:join_table]
87
+ options[:join_table].to_s
88
+ else
89
+ class_name = options.fetch(:class_name) {
90
+ association_name.to_s.camelize.singularize
91
+ }
92
+ klass = lhs_model.send(:compute_type, class_name.to_s)
93
+ [lhs_model.table_name, klass.table_name].sort.join("\0").gsub(/^(.*[._])(.+)\0\1(.+)/, '\1\2_\3').tr("\0", "_")
94
+ end
95
+ end
96
+
120
97
  def belongs_to_options(options)
121
98
  rhs_options = {}
122
99
 
@@ -13,5 +13,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
13
13
  def self.valid_dependent_options
14
14
  [:destroy, :delete_all, :nullify, :restrict_with_error, :restrict_with_exception]
15
15
  end
16
+
17
+ private_class_method :macro, :valid_options, :valid_dependent_options
16
18
  end
17
19
  end
@@ -7,7 +7,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
7
7
  end
8
8
 
9
9
  def self.valid_options(options)
10
- valid = super + [:as]
10
+ valid = super + [:as, :touch]
11
11
  valid += [:through, :source, :source_type] if options[:through]
12
12
  valid
13
13
  end
@@ -16,6 +16,11 @@ module ActiveRecord::Associations::Builder # :nodoc:
16
16
  [:destroy, :delete, :nullify, :restrict_with_error, :restrict_with_exception]
17
17
  end
18
18
 
19
+ def self.define_callbacks(model, reflection)
20
+ super
21
+ add_touch_callbacks(model, reflection) if reflection.options[:touch]
22
+ end
23
+
19
24
  def self.add_destroy_callbacks(model, reflection)
20
25
  super unless reflection.options[:through]
21
26
  end
@@ -26,5 +31,34 @@ module ActiveRecord::Associations::Builder # :nodoc:
26
31
  model.validates_presence_of reflection.name, message: :required
27
32
  end
28
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
29
63
  end
30
64
  end
@@ -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
@@ -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
@@ -230,10 +232,10 @@ module ActiveRecord
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
 
@@ -300,23 +302,6 @@ module ActiveRecord
300
302
  end
301
303
 
302
304
  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
305
  # We have some records loaded from the database (persisted) and some that are
321
306
  # in-memory (memory). The same record may be represented in the persisted array
322
307
  # and in the memory array.
@@ -347,6 +332,13 @@ module ActiveRecord
347
332
  persisted + memory
348
333
  end
349
334
 
335
+ def build_record(attributes)
336
+ previous = klass.current_scope(true) if block_given?
337
+ super
338
+ ensure
339
+ klass.current_scope = previous if previous
340
+ end
341
+
350
342
  def _create_record(attributes, raise = false, &block)
351
343
  unless owner.persisted?
352
344
  raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"
@@ -393,10 +385,12 @@ module ActiveRecord
393
385
  end
394
386
 
395
387
  def remove_records(existing_records, records, method)
396
- records.each { |record| callback(:before_remove, record) }
388
+ catch(:abort) do
389
+ records.each { |record| callback(:before_remove, record) }
390
+ end || return
397
391
 
398
392
  delete_records(existing_records, method) if existing_records.any?
399
- records.each { |record| target.delete(record) }
393
+ @target -= records
400
394
  @association_ids = nil
401
395
 
402
396
  records.each { |record| callback(:after_remove, record) }
@@ -449,7 +443,9 @@ module ActiveRecord
449
443
  end
450
444
 
451
445
  def replace_on_target(record, index, skip_callbacks)
452
- callback(:before_add, record) unless skip_callbacks
446
+ catch(:abort) do
447
+ callback(:before_add, record)
448
+ end || return unless skip_callbacks
453
449
 
454
450
  set_inverse_instance(record)
455
451
 
@@ -2,11 +2,8 @@
2
2
 
3
3
  module ActiveRecord
4
4
  module Associations
5
- # Association proxies in Active Record are middlemen between the object that
6
- # holds the association, known as the <tt>@owner</tt>, and the actual associated
7
- # object, known as the <tt>@target</tt>. The kind of association any proxy is
8
- # about is available in <tt>@reflection</tt>. That's an instance of the class
9
- # ActiveRecord::Reflection::AssociationReflection.
5
+ # Collection proxies in Active Record are middlemen between an
6
+ # <tt>association</tt>, and its <tt>target</tt> result set.
10
7
  #
11
8
  # For example, given
12
9
  #
@@ -16,21 +13,21 @@ module ActiveRecord
16
13
  #
17
14
  # blog = Blog.first
18
15
  #
19
- # the association proxy in <tt>blog.posts</tt> has the object in +blog+ as
20
- # <tt>@owner</tt>, the collection of its posts as <tt>@target</tt>, and
21
- # the <tt>@reflection</tt> object represents a <tt>:has_many</tt> macro.
16
+ # The collection proxy returned by <tt>blog.posts</tt> is built from a
17
+ # <tt>:has_many</tt> <tt>association</tt>, and delegates to a collection
18
+ # of posts as the <tt>target</tt>.
22
19
  #
23
- # This class delegates unknown methods to <tt>@target</tt> via
24
- # <tt>method_missing</tt>.
20
+ # This class delegates unknown methods to the <tt>association</tt>'s
21
+ # relation class via a delegate cache.
25
22
  #
26
- # The <tt>@target</tt> object is not \loaded until needed. For example,
23
+ # The <tt>target</tt> result set is not loaded until needed. For example,
27
24
  #
28
25
  # blog.posts.count
29
26
  #
30
27
  # is computed directly through SQL and does not trigger by itself the
31
28
  # instantiation of the actual post records.
32
29
  class CollectionProxy < Relation
33
- def initialize(klass, association) #:nodoc:
30
+ def initialize(klass, association, **) #:nodoc:
34
31
  @association = association
35
32
  super klass
36
33
 
@@ -54,6 +51,7 @@ module ActiveRecord
54
51
  def loaded?
55
52
  @association.loaded?
56
53
  end
54
+ alias :loaded :loaded?
57
55
 
58
56
  ##
59
57
  # :method: select
@@ -1005,7 +1003,7 @@ module ActiveRecord
1005
1003
  end
1006
1004
 
1007
1005
  # Adds one or more +records+ to the collection by setting their foreign keys
1008
- # to the association's primary key. Since +<<+ flattens its argument list and
1006
+ # to the association's primary key. Since <tt><<</tt> flattens its argument list and
1009
1007
  # inserts each record, +push+ and +concat+ behave identically. Returns +self+
1010
1008
  # so several appends may be chained together.
1011
1009
  #
@@ -1032,7 +1030,7 @@ module ActiveRecord
1032
1030
  alias_method :append, :<<
1033
1031
  alias_method :concat, :<<
1034
1032
 
1035
- def prepend(*args)
1033
+ def prepend(*args) # :nodoc:
1036
1034
  raise NoMethodError, "prepend on association is not defined. Please use <<, push or append"
1037
1035
  end
1038
1036
 
@@ -1062,7 +1060,7 @@ module ActiveRecord
1062
1060
  # person.pets.reload # fetches pets from the database
1063
1061
  # # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
1064
1062
  def reload
1065
- proxy_association.reload
1063
+ proxy_association.reload(true)
1066
1064
  reset_scope
1067
1065
  end
1068
1066
 
@@ -1099,12 +1097,11 @@ module ActiveRecord
1099
1097
  SpawnMethods,
1100
1098
  ].flat_map { |klass|
1101
1099
  klass.public_instance_methods(false)
1102
- } - self.public_instance_methods(false) - [:select] + [:scoping]
1100
+ } - self.public_instance_methods(false) - [:select] + [:scoping, :values]
1103
1101
 
1104
1102
  delegate(*delegate_methods, to: :scope)
1105
1103
 
1106
1104
  private
1107
-
1108
1105
  def find_nth_with_limit(index, limit)
1109
1106
  load_target if find_from_target?
1110
1107
  super
@@ -9,5 +9,12 @@ module ActiveRecord::Associations
9
9
  false
10
10
  end
11
11
  end
12
+
13
+ def nullified_owner_attributes
14
+ Hash.new.tap do |attrs|
15
+ attrs[reflection.foreign_key] = nil
16
+ attrs[reflection.type] = nil if reflection.type.present?
17
+ end
18
+ end
12
19
  end
13
20
  end
@@ -36,16 +36,7 @@ module ActiveRecord
36
36
  super
37
37
  end
38
38
 
39
- def empty?
40
- if reflection.has_cached_counter?
41
- size.zero?
42
- else
43
- super
44
- end
45
- end
46
-
47
39
  private
48
-
49
40
  # Returns the number of records in this collection.
50
41
  #
51
42
  # If the association has a counter cache it gets that value. Otherwise
@@ -69,7 +60,7 @@ module ActiveRecord
69
60
  # If there's nothing in the database and @target has no new records
70
61
  # we are certain the current target is an empty array. This is a
71
62
  # documented side-effect of the method that may avoid an extra SELECT.
72
- (@target ||= []) && loaded! if count == 0
63
+ loaded! if count == 0
73
64
 
74
65
  [association_scope.limit_value, count].compact.min
75
66
  end
@@ -92,7 +83,7 @@ module ActiveRecord
92
83
  if method == :delete_all
93
84
  scope.delete_all
94
85
  else
95
- scope.update_all(reflection.foreign_key => nil)
86
+ scope.update_all(nullified_owner_attributes)
96
87
  end
97
88
  end
98
89
 
@@ -21,20 +21,6 @@ module ActiveRecord
21
21
  super
22
22
  end
23
23
 
24
- def concat_records(records)
25
- ensure_not_nested
26
-
27
- records = super(records, true)
28
-
29
- if owner.new_record? && records
30
- records.flatten.each do |record|
31
- build_through_record(record)
32
- end
33
- end
34
-
35
- records
36
- end
37
-
38
24
  def insert_record(record, validate = true, raise = false)
39
25
  ensure_not_nested
40
26
 
@@ -48,6 +34,20 @@ module ActiveRecord
48
34
  end
49
35
 
50
36
  private
37
+ def concat_records(records)
38
+ ensure_not_nested
39
+
40
+ records = super(records, true)
41
+
42
+ if owner.new_record? && records
43
+ records.flatten.each do |record|
44
+ build_through_record(record)
45
+ end
46
+ end
47
+
48
+ records
49
+ end
50
+
51
51
  # The through record (built with build_record) is temporarily cached
52
52
  # so that it may be reused if insert_record is subsequently called.
53
53
  #
@@ -23,35 +23,6 @@ module ActiveRecord
23
23
  end
24
24
  end
25
25
 
26
- def replace(record, save = true)
27
- raise_on_type_mismatch!(record) if record
28
- load_target
29
-
30
- return target unless target || record
31
-
32
- assigning_another_record = target != record
33
- if assigning_another_record || record.has_changes_to_save?
34
- save &&= owner.persisted?
35
-
36
- transaction_if(save) do
37
- remove_target!(options[:dependent]) if target && !target.destroyed? && assigning_another_record
38
-
39
- if record
40
- set_owner_attributes(record)
41
- set_inverse_instance(record)
42
-
43
- if save && !record.save
44
- nullify_owner_attributes(record)
45
- set_owner_attributes(target) if target
46
- raise RecordNotSaved, "Failed to save the new associated #{reflection.name}."
47
- end
48
- end
49
- end
50
- end
51
-
52
- self.target = record
53
- end
54
-
55
26
  def delete(method = options[:dependent])
56
27
  if load_target
57
28
  case method
@@ -62,12 +33,39 @@ module ActiveRecord
62
33
  target.destroy
63
34
  throw(:abort) unless target.destroyed?
64
35
  when :nullify
65
- target.update_columns(reflection.foreign_key => nil) if target.persisted?
36
+ target.update_columns(nullified_owner_attributes) if target.persisted?
66
37
  end
67
38
  end
68
39
  end
69
40
 
70
41
  private
42
+ def replace(record, save = true)
43
+ raise_on_type_mismatch!(record) if record
44
+
45
+ return target unless load_target || record
46
+
47
+ assigning_another_record = target != record
48
+ if assigning_another_record || record.has_changes_to_save?
49
+ save &&= owner.persisted?
50
+
51
+ transaction_if(save) do
52
+ remove_target!(options[:dependent]) if target && !target.destroyed? && assigning_another_record
53
+
54
+ if record
55
+ set_owner_attributes(record)
56
+ set_inverse_instance(record)
57
+
58
+ if save && !record.save
59
+ nullify_owner_attributes(record)
60
+ set_owner_attributes(target) if target
61
+ raise RecordNotSaved, "Failed to save the new associated #{reflection.name}."
62
+ end
63
+ end
64
+ end
65
+ end
66
+
67
+ self.target = record
68
+ end
71
69
 
72
70
  # The reason that the save param for replace is false, if for create (not just build),
73
71
  # is because the setting of the foreign keys is actually handled by the scoping when
@@ -6,12 +6,12 @@ module ActiveRecord
6
6
  class HasOneThroughAssociation < HasOneAssociation #:nodoc:
7
7
  include ThroughAssociation
8
8
 
9
- def replace(record, save = true)
10
- create_through_record(record, save)
11
- self.target = record
12
- end
13
-
14
9
  private
10
+ def replace(record, save = true)
11
+ create_through_record(record, save)
12
+ self.target = record
13
+ end
14
+
15
15
  def create_through_record(record, save)
16
16
  ensure_not_nested
17
17
 
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_record/associations/join_dependency/join_part"
4
+ require "active_support/core_ext/array/extract"
4
5
 
5
6
  module ActiveRecord
6
7
  module Associations
@@ -32,13 +33,19 @@ module ActiveRecord
32
33
 
33
34
  join_scope = reflection.join_scope(table, foreign_table, foreign_klass)
34
35
 
36
+ unless join_scope.references_values.empty?
37
+ join_dependency = join_scope.construct_join_dependency(
38
+ join_scope.eager_load_values | join_scope.includes_values, Arel::Nodes::OuterJoin
39
+ )
40
+ join_scope.joins!(join_dependency)
41
+ end
42
+
35
43
  arel = join_scope.arel(alias_tracker.aliases)
36
44
  nodes = arel.constraints.first
37
45
 
38
- others, children = nodes.children.partition do |node|
39
- !fetch_arel_attribute(node) { |attr| attr.relation.name == table.name }
46
+ others = nodes.children.extract! do |node|
47
+ !Arel.fetch_attribute(node) { |attr| attr.relation.name == table.name }
40
48
  end
41
- nodes = table.create_and(children)
42
49
 
43
50
  joins << table.create_join(table, table.create_on(nodes), join_type)
44
51
 
@@ -59,14 +66,13 @@ module ActiveRecord
59
66
  @table = tables.first
60
67
  end
61
68
 
62
- private
63
- def fetch_arel_attribute(value)
64
- case value
65
- when Arel::Nodes::Between, Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual, Arel::Nodes::LessThan, Arel::Nodes::LessThanOrEqual, Arel::Nodes::GreaterThan, Arel::Nodes::GreaterThanOrEqual
66
- yield value.left.is_a?(Arel::Attributes::Attribute) ? value.left : value.right
67
- end
68
- end
69
+ def readonly?
70
+ return @readonly if defined?(@readonly)
71
+
72
+ @readonly = reflection.scope && reflection.scope_for(base_klass.unscoped).readonly_value
73
+ end
69
74
 
75
+ private
70
76
  def append_constraints(join, constraints)
71
77
  if join.is_a?(Arel::Nodes::StringJoin)
72
78
  join_string = table.create_and(constraints.unshift(join.left))
@@ -54,16 +54,16 @@ module ActiveRecord
54
54
  length = column_names_with_alias.length
55
55
 
56
56
  while index < length
57
- column_name, alias_name = column_names_with_alias[index]
58
- hash[column_name] = row[alias_name]
57
+ column = column_names_with_alias[index]
58
+ hash[column.name] = row[column.alias]
59
59
  index += 1
60
60
  end
61
61
 
62
62
  hash
63
63
  end
64
64
 
65
- def instantiate(row, aliases, &block)
66
- base_klass.instantiate(extract_record(row, aliases), &block)
65
+ def instantiate(row, aliases, column_types = {}, &block)
66
+ base_klass.instantiate(extract_record(row, aliases), column_types, &block)
67
67
  end
68
68
  end
69
69
  end