activerecord 5.2.6.2 → 6.0.0.beta1

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 (241) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +299 -768
  3. data/MIT-LICENSE +3 -1
  4. data/README.rdoc +1 -1
  5. data/examples/performance.rb +1 -1
  6. data/lib/active_record/aggregations.rb +4 -2
  7. data/lib/active_record/associations/association.rb +35 -19
  8. data/lib/active_record/associations/association_scope.rb +4 -6
  9. data/lib/active_record/associations/belongs_to_association.rb +36 -42
  10. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +0 -4
  11. data/lib/active_record/associations/builder/belongs_to.rb +14 -50
  12. data/lib/active_record/associations/builder/collection_association.rb +3 -3
  13. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +17 -38
  14. data/lib/active_record/associations/collection_association.rb +11 -25
  15. data/lib/active_record/associations/collection_proxy.rb +32 -6
  16. data/lib/active_record/associations/foreign_association.rb +7 -0
  17. data/lib/active_record/associations/has_many_association.rb +1 -1
  18. data/lib/active_record/associations/has_many_through_association.rb +25 -18
  19. data/lib/active_record/associations/has_one_association.rb +28 -30
  20. data/lib/active_record/associations/has_one_through_association.rb +5 -5
  21. data/lib/active_record/associations/join_dependency/join_association.rb +11 -26
  22. data/lib/active_record/associations/join_dependency/join_part.rb +2 -2
  23. data/lib/active_record/associations/join_dependency.rb +15 -20
  24. data/lib/active_record/associations/preloader/association.rb +1 -2
  25. data/lib/active_record/associations/preloader.rb +32 -29
  26. data/lib/active_record/associations/singular_association.rb +2 -16
  27. data/lib/active_record/associations.rb +16 -12
  28. data/lib/active_record/attribute_assignment.rb +7 -10
  29. data/lib/active_record/attribute_methods/dirty.rb +64 -26
  30. data/lib/active_record/attribute_methods/primary_key.rb +8 -7
  31. data/lib/active_record/attribute_methods/read.rb +16 -48
  32. data/lib/active_record/attribute_methods/serialization.rb +1 -1
  33. data/lib/active_record/attribute_methods/time_zone_conversion.rb +1 -1
  34. data/lib/active_record/attribute_methods/write.rb +15 -16
  35. data/lib/active_record/attribute_methods.rb +34 -56
  36. data/lib/active_record/autosave_association.rb +7 -21
  37. data/lib/active_record/base.rb +2 -2
  38. data/lib/active_record/callbacks.rb +3 -17
  39. data/lib/active_record/collection_cache_key.rb +1 -1
  40. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +13 -36
  41. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  42. data/lib/active_record/connection_adapters/abstract/database_statements.rb +25 -84
  43. data/lib/active_record/connection_adapters/abstract/query_cache.rb +17 -14
  44. data/lib/active_record/connection_adapters/abstract/quoting.rb +5 -11
  45. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +15 -11
  46. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +30 -13
  47. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +0 -2
  48. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +41 -27
  49. data/lib/active_record/connection_adapters/abstract/transaction.rb +81 -52
  50. data/lib/active_record/connection_adapters/abstract_adapter.rb +95 -31
  51. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +65 -90
  52. data/lib/active_record/connection_adapters/connection_specification.rb +52 -42
  53. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +5 -9
  54. data/lib/active_record/connection_adapters/mysql/database_statements.rb +29 -7
  55. data/lib/active_record/connection_adapters/mysql/quoting.rb +1 -1
  56. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +3 -4
  57. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +65 -10
  58. data/lib/active_record/connection_adapters/mysql2_adapter.rb +8 -4
  59. data/lib/active_record/connection_adapters/postgresql/column.rb +1 -2
  60. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +16 -1
  61. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  62. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +1 -4
  63. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +1 -1
  64. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +1 -1
  65. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
  66. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +1 -1
  67. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +9 -7
  68. data/lib/active_record/connection_adapters/postgresql/quoting.rb +4 -4
  69. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +11 -36
  70. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +9 -2
  71. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +38 -20
  72. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +2 -1
  73. data/lib/active_record/connection_adapters/postgresql_adapter.rb +75 -56
  74. data/lib/active_record/connection_adapters/schema_cache.rb +5 -0
  75. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +5 -5
  76. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +14 -9
  77. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +95 -62
  78. data/lib/active_record/connection_handling.rb +132 -26
  79. data/lib/active_record/core.rb +76 -43
  80. data/lib/active_record/counter_cache.rb +4 -29
  81. data/lib/active_record/database_configurations/database_config.rb +37 -0
  82. data/lib/active_record/database_configurations/hash_config.rb +50 -0
  83. data/lib/active_record/database_configurations/url_config.rb +74 -0
  84. data/lib/active_record/database_configurations.rb +184 -0
  85. data/lib/active_record/enum.rb +22 -7
  86. data/lib/active_record/errors.rb +24 -21
  87. data/lib/active_record/explain.rb +1 -1
  88. data/lib/active_record/fixture_set/model_metadata.rb +33 -0
  89. data/lib/active_record/fixture_set/render_context.rb +17 -0
  90. data/lib/active_record/fixture_set/table_row.rb +153 -0
  91. data/lib/active_record/fixture_set/table_rows.rb +47 -0
  92. data/lib/active_record/fixtures.rb +140 -472
  93. data/lib/active_record/gem_version.rb +4 -4
  94. data/lib/active_record/inheritance.rb +12 -2
  95. data/lib/active_record/integration.rb +56 -16
  96. data/lib/active_record/internal_metadata.rb +5 -1
  97. data/lib/active_record/locking/optimistic.rb +2 -2
  98. data/lib/active_record/locking/pessimistic.rb +3 -3
  99. data/lib/active_record/log_subscriber.rb +7 -26
  100. data/lib/active_record/migration/command_recorder.rb +35 -5
  101. data/lib/active_record/migration/compatibility.rb +34 -16
  102. data/lib/active_record/migration.rb +38 -37
  103. data/lib/active_record/model_schema.rb +30 -9
  104. data/lib/active_record/nested_attributes.rb +2 -2
  105. data/lib/active_record/no_touching.rb +7 -0
  106. data/lib/active_record/persistence.rb +18 -7
  107. data/lib/active_record/query_cache.rb +11 -4
  108. data/lib/active_record/querying.rb +19 -11
  109. data/lib/active_record/railtie.rb +71 -42
  110. data/lib/active_record/railties/collection_cache_association_loading.rb +34 -0
  111. data/lib/active_record/railties/controller_runtime.rb +30 -35
  112. data/lib/active_record/railties/databases.rake +94 -43
  113. data/lib/active_record/reflection.rb +60 -44
  114. data/lib/active_record/relation/batches.rb +13 -10
  115. data/lib/active_record/relation/calculations.rb +38 -28
  116. data/lib/active_record/relation/delegation.rb +4 -13
  117. data/lib/active_record/relation/finder_methods.rb +12 -25
  118. data/lib/active_record/relation/merger.rb +2 -6
  119. data/lib/active_record/relation/predicate_builder/array_handler.rb +5 -4
  120. data/lib/active_record/relation/predicate_builder/association_query_value.rb +1 -4
  121. data/lib/active_record/relation/predicate_builder/base_handler.rb +1 -2
  122. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +1 -2
  123. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +1 -4
  124. data/lib/active_record/relation/predicate_builder/range_handler.rb +3 -23
  125. data/lib/active_record/relation/predicate_builder.rb +4 -6
  126. data/lib/active_record/relation/query_attribute.rb +15 -12
  127. data/lib/active_record/relation/query_methods.rb +29 -52
  128. data/lib/active_record/relation/where_clause.rb +4 -0
  129. data/lib/active_record/relation/where_clause_factory.rb +1 -2
  130. data/lib/active_record/relation.rb +150 -69
  131. data/lib/active_record/result.rb +30 -11
  132. data/lib/active_record/sanitization.rb +2 -39
  133. data/lib/active_record/schema.rb +1 -10
  134. data/lib/active_record/schema_dumper.rb +12 -6
  135. data/lib/active_record/schema_migration.rb +4 -0
  136. data/lib/active_record/scoping/default.rb +10 -3
  137. data/lib/active_record/scoping/named.rb +10 -14
  138. data/lib/active_record/scoping.rb +9 -8
  139. data/lib/active_record/statement_cache.rb +32 -5
  140. data/lib/active_record/store.rb +39 -8
  141. data/lib/active_record/table_metadata.rb +1 -4
  142. data/lib/active_record/tasks/database_tasks.rb +89 -23
  143. data/lib/active_record/tasks/mysql_database_tasks.rb +2 -4
  144. data/lib/active_record/tasks/postgresql_database_tasks.rb +5 -7
  145. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -8
  146. data/lib/active_record/test_databases.rb +38 -0
  147. data/lib/active_record/test_fixtures.rb +224 -0
  148. data/lib/active_record/timestamp.rb +4 -6
  149. data/lib/active_record/transactions.rb +3 -22
  150. data/lib/active_record/translation.rb +1 -1
  151. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  152. data/lib/active_record/type.rb +3 -4
  153. data/lib/active_record/type_caster/connection.rb +1 -6
  154. data/lib/active_record/type_caster/map.rb +1 -4
  155. data/lib/active_record/validations/uniqueness.rb +13 -25
  156. data/lib/active_record.rb +2 -1
  157. data/lib/arel/alias_predication.rb +9 -0
  158. data/lib/arel/attributes/attribute.rb +37 -0
  159. data/lib/arel/attributes.rb +22 -0
  160. data/lib/arel/collectors/bind.rb +24 -0
  161. data/lib/arel/collectors/composite.rb +31 -0
  162. data/lib/arel/collectors/plain_string.rb +20 -0
  163. data/lib/arel/collectors/sql_string.rb +20 -0
  164. data/lib/arel/collectors/substitute_binds.rb +28 -0
  165. data/lib/arel/crud.rb +42 -0
  166. data/lib/arel/delete_manager.rb +18 -0
  167. data/lib/arel/errors.rb +9 -0
  168. data/lib/arel/expressions.rb +29 -0
  169. data/lib/arel/factory_methods.rb +49 -0
  170. data/lib/arel/insert_manager.rb +49 -0
  171. data/lib/arel/math.rb +45 -0
  172. data/lib/arel/nodes/and.rb +32 -0
  173. data/lib/arel/nodes/ascending.rb +23 -0
  174. data/lib/arel/nodes/binary.rb +52 -0
  175. data/lib/arel/nodes/bind_param.rb +36 -0
  176. data/lib/arel/nodes/case.rb +55 -0
  177. data/lib/arel/nodes/casted.rb +50 -0
  178. data/lib/arel/nodes/count.rb +12 -0
  179. data/lib/arel/nodes/delete_statement.rb +45 -0
  180. data/lib/arel/nodes/descending.rb +23 -0
  181. data/lib/arel/nodes/equality.rb +18 -0
  182. data/lib/arel/nodes/extract.rb +24 -0
  183. data/lib/arel/nodes/false.rb +16 -0
  184. data/lib/arel/nodes/full_outer_join.rb +8 -0
  185. data/lib/arel/nodes/function.rb +44 -0
  186. data/lib/arel/nodes/grouping.rb +8 -0
  187. data/lib/arel/nodes/in.rb +8 -0
  188. data/lib/arel/nodes/infix_operation.rb +80 -0
  189. data/lib/arel/nodes/inner_join.rb +8 -0
  190. data/lib/arel/nodes/insert_statement.rb +37 -0
  191. data/lib/arel/nodes/join_source.rb +20 -0
  192. data/lib/arel/nodes/matches.rb +18 -0
  193. data/lib/arel/nodes/named_function.rb +23 -0
  194. data/lib/arel/nodes/node.rb +50 -0
  195. data/lib/arel/nodes/node_expression.rb +13 -0
  196. data/lib/arel/nodes/outer_join.rb +8 -0
  197. data/lib/arel/nodes/over.rb +15 -0
  198. data/lib/arel/nodes/regexp.rb +16 -0
  199. data/lib/arel/nodes/right_outer_join.rb +8 -0
  200. data/lib/arel/nodes/select_core.rb +63 -0
  201. data/lib/arel/nodes/select_statement.rb +41 -0
  202. data/lib/arel/nodes/sql_literal.rb +16 -0
  203. data/lib/arel/nodes/string_join.rb +11 -0
  204. data/lib/arel/nodes/table_alias.rb +27 -0
  205. data/lib/arel/nodes/terminal.rb +16 -0
  206. data/lib/arel/nodes/true.rb +16 -0
  207. data/lib/arel/nodes/unary.rb +44 -0
  208. data/lib/arel/nodes/unary_operation.rb +20 -0
  209. data/lib/arel/nodes/unqualified_column.rb +22 -0
  210. data/lib/arel/nodes/update_statement.rb +41 -0
  211. data/lib/arel/nodes/values.rb +16 -0
  212. data/lib/arel/nodes/values_list.rb +24 -0
  213. data/lib/arel/nodes/window.rb +126 -0
  214. data/lib/arel/nodes/with.rb +11 -0
  215. data/lib/arel/nodes.rb +67 -0
  216. data/lib/arel/order_predications.rb +13 -0
  217. data/lib/arel/predications.rb +257 -0
  218. data/lib/arel/select_manager.rb +271 -0
  219. data/lib/arel/table.rb +110 -0
  220. data/lib/arel/tree_manager.rb +72 -0
  221. data/lib/arel/update_manager.rb +34 -0
  222. data/lib/arel/visitors/depth_first.rb +199 -0
  223. data/lib/arel/visitors/dot.rb +292 -0
  224. data/lib/arel/visitors/ibm_db.rb +21 -0
  225. data/lib/arel/visitors/informix.rb +56 -0
  226. data/lib/arel/visitors/mssql.rb +143 -0
  227. data/lib/arel/visitors/mysql.rb +83 -0
  228. data/lib/arel/visitors/oracle.rb +159 -0
  229. data/lib/arel/visitors/oracle12.rb +67 -0
  230. data/lib/arel/visitors/postgresql.rb +116 -0
  231. data/lib/arel/visitors/sqlite.rb +39 -0
  232. data/lib/arel/visitors/to_sql.rb +913 -0
  233. data/lib/arel/visitors/visitor.rb +42 -0
  234. data/lib/arel/visitors/where_sql.rb +23 -0
  235. data/lib/arel/visitors.rb +20 -0
  236. data/lib/arel/window_predications.rb +9 -0
  237. data/lib/arel.rb +44 -0
  238. data/lib/rails/generators/active_record/migration/migration_generator.rb +2 -5
  239. data/lib/rails/generators/active_record/migration.rb +14 -1
  240. data/lib/rails/generators/active_record/model/model_generator.rb +1 -0
  241. metadata +107 -29
data/MIT-LICENSE CHANGED
@@ -1,4 +1,6 @@
1
- Copyright (c) 2004-2018 David Heinemeier Hansson
1
+ Copyright (c) 2004-2019 David Heinemeier Hansson
2
+
3
+ Arel originally copyright (c) 2007-2016 Nick Kallen, Bryan Helmkamp, Emilio Tagua, Aaron Patterson
2
4
 
3
5
  Permission is hereby granted, free of charge, to any person obtaining
4
6
  a copy of this software and associated documentation files (the
data/README.rdoc CHANGED
@@ -192,7 +192,7 @@ The latest version of Active Record can be installed with RubyGems:
192
192
 
193
193
  Source code can be downloaded as part of the Rails project on GitHub:
194
194
 
195
- * https://github.com/rails/rails/tree/5-2-stable/activerecord
195
+ * https://github.com/rails/rails/tree/master/activerecord
196
196
 
197
197
 
198
198
  == License
@@ -176,7 +176,7 @@ Benchmark.ips(TIME) do |x|
176
176
  end
177
177
 
178
178
  x.report "Model.log" do
179
- Exhibit.connection.send(:log, "hello", "world") {}
179
+ Exhibit.connection.send(:log, "hello", "world") { }
180
180
  end
181
181
 
182
182
  x.report "AR.execute(query)" do
@@ -3,8 +3,6 @@
3
3
  module ActiveRecord
4
4
  # See ActiveRecord::Aggregations::ClassMethods for documentation
5
5
  module Aggregations
6
- extend ActiveSupport::Concern
7
-
8
6
  def initialize_dup(*) # :nodoc:
9
7
  @aggregation_cache = {}
10
8
  super
@@ -225,6 +223,10 @@ module ActiveRecord
225
223
  def composed_of(part_id, options = {})
226
224
  options.assert_valid_keys(:class_name, :mapping, :allow_nil, :constructor, :converter)
227
225
 
226
+ unless self < Aggregations
227
+ include Aggregations
228
+ end
229
+
228
230
  name = part_id.id2name
229
231
  class_name = options[:class_name] || name.camelize
230
232
  mapping = options[:mapping] || [ name, name ]
@@ -40,7 +40,9 @@ module ActiveRecord
40
40
  end
41
41
 
42
42
  # Reloads the \target and returns +self+ on success.
43
- def reload
43
+ # The QueryCache is cleared if +force+ is true.
44
+ def reload(force = false)
45
+ klass.connection.clear_query_cache if force && klass
44
46
  reset
45
47
  reset_scope
46
48
  load_target
@@ -79,18 +81,6 @@ module ActiveRecord
79
81
  target_scope.merge!(association_scope)
80
82
  end
81
83
 
82
- # The scope for this association.
83
- #
84
- # Note that the association_scope is merged into the target_scope only when the
85
- # scope method is called. This is because at that point the call may be surrounded
86
- # by scope.scoping { ... } or with_scope { ... } etc, which affects the scope which
87
- # actually gets built.
88
- def association_scope
89
- if klass
90
- @association_scope ||= AssociationScope.scope(self)
91
- end
92
- end
93
-
94
84
  def reset_scope
95
85
  @association_scope = nil
96
86
  end
@@ -129,12 +119,6 @@ module ActiveRecord
129
119
  reflection.klass
130
120
  end
131
121
 
132
- # Can be overridden (i.e. in ThroughAssociation) to merge in other scopes (i.e. the
133
- # through association's scope)
134
- def target_scope
135
- AssociationRelation.create(klass, self).merge!(klass.all)
136
- end
137
-
138
122
  def extensions
139
123
  extensions = klass.default_extensions | reflection.extensions
140
124
 
@@ -195,6 +179,38 @@ module ActiveRecord
195
179
  end
196
180
 
197
181
  private
182
+ def find_target
183
+ scope = self.scope
184
+ return scope.to_a if skip_statement_cache?(scope)
185
+
186
+ conn = klass.connection
187
+ sc = reflection.association_scope_cache(conn, owner) do |params|
188
+ as = AssociationScope.create { params.bind }
189
+ target_scope.merge!(as.scope(self))
190
+ end
191
+
192
+ binds = AssociationScope.get_bind_values(owner, reflection.chain)
193
+ sc.execute(binds, conn) { |record| set_inverse_instance(record) } || []
194
+ end
195
+
196
+ # The scope for this association.
197
+ #
198
+ # Note that the association_scope is merged into the target_scope only when the
199
+ # scope method is called. This is because at that point the call may be surrounded
200
+ # by scope.scoping { ... } or unscoped { ... } etc, which affects the scope which
201
+ # actually gets built.
202
+ def association_scope
203
+ if klass
204
+ @association_scope ||= AssociationScope.scope(self)
205
+ end
206
+ end
207
+
208
+ # Can be overridden (i.e. in ThroughAssociation) to merge in other scopes (i.e. the
209
+ # through association's scope)
210
+ def target_scope
211
+ AssociationRelation.create(klass, self).merge!(klass.all)
212
+ end
213
+
198
214
  def scope_for_create
199
215
  scope.scope_for_create
200
216
  end
@@ -26,7 +26,9 @@ module ActiveRecord
26
26
  chain = get_chain(reflection, association, scope.alias_tracker)
27
27
 
28
28
  scope.extending! reflection.extensions
29
- add_constraints(scope, owner, chain)
29
+ scope = add_constraints(scope, owner, chain)
30
+ scope.limit!(1) unless reflection.collection?
31
+ scope
30
32
  end
31
33
 
32
34
  def self.get_bind_values(owner, chain)
@@ -46,13 +48,9 @@ module ActiveRecord
46
48
  binds
47
49
  end
48
50
 
49
- # TODO Change this to private once we've dropped Ruby 2.2 support.
50
- # Workaround for Ruby 2.2 "private attribute?" warning.
51
- protected
52
-
51
+ private
53
52
  attr_reader :value_transformation
54
53
 
55
- private
56
54
  def join(table, constraint)
57
55
  table.create_join(table, table.create_on(constraint))
58
56
  end
@@ -16,21 +16,6 @@ module ActiveRecord
16
16
  end
17
17
  end
18
18
 
19
- def replace(record)
20
- if record
21
- raise_on_type_mismatch!(record)
22
- update_counters_on_replace(record)
23
- set_inverse_instance(record)
24
- @updated = true
25
- else
26
- decrement_counters
27
- end
28
-
29
- replace_keys(record)
30
-
31
- self.target = record
32
- end
33
-
34
19
  def inversed_from(record)
35
20
  replace_keys(record)
36
21
  super
@@ -49,30 +34,60 @@ module ActiveRecord
49
34
  @updated
50
35
  end
51
36
 
52
- def decrement_counters # :nodoc:
37
+ def decrement_counters
53
38
  update_counters(-1)
54
39
  end
55
40
 
56
- def increment_counters # :nodoc:
41
+ def increment_counters
57
42
  update_counters(1)
58
43
  end
59
44
 
45
+ def decrement_counters_before_last_save
46
+ if reflection.polymorphic?
47
+ model_was = owner.attribute_before_last_save(reflection.foreign_type).try(:constantize)
48
+ else
49
+ model_was = klass
50
+ end
51
+
52
+ foreign_key_was = owner.attribute_before_last_save(reflection.foreign_key)
53
+
54
+ if foreign_key_was && model_was < ActiveRecord::Base
55
+ update_counters_via_scope(model_was, foreign_key_was, -1)
56
+ end
57
+ end
58
+
60
59
  def target_changed?
61
60
  owner.saved_change_to_attribute?(reflection.foreign_key)
62
61
  end
63
62
 
64
63
  private
64
+ def replace(record)
65
+ if record
66
+ raise_on_type_mismatch!(record)
67
+ set_inverse_instance(record)
68
+ @updated = true
69
+ end
70
+
71
+ replace_keys(record)
72
+
73
+ self.target = record
74
+ end
65
75
 
66
76
  def update_counters(by)
67
77
  if require_counter_update? && foreign_key_present?
68
78
  if target && !stale_target?
69
79
  target.increment!(reflection.counter_cache_column, by, touch: reflection.options[:touch])
70
80
  else
71
- klass.update_counters(target_id, reflection.counter_cache_column => by, touch: reflection.options[:touch])
81
+ update_counters_via_scope(klass, owner._read_attribute(reflection.foreign_key), by)
72
82
  end
73
83
  end
74
84
  end
75
85
 
86
+ def update_counters_via_scope(klass, foreign_key, by)
87
+ scope = klass.unscoped.where!(primary_key(klass) => foreign_key)
88
+ scope.update_counters(reflection.counter_cache_column => by, touch: reflection.options[:touch])
89
+ end
90
+
76
91
  def find_target?
77
92
  !loaded? && foreign_key_present? && klass
78
93
  end
@@ -81,25 +96,12 @@ module ActiveRecord
81
96
  reflection.counter_cache_column && owner.persisted?
82
97
  end
83
98
 
84
- def update_counters_on_replace(record)
85
- if require_counter_update? && different_target?(record)
86
- owner.instance_variable_set :@_after_replace_counter_called, true
87
- record.increment!(reflection.counter_cache_column, touch: reflection.options[:touch])
88
- decrement_counters
89
- end
90
- end
91
-
92
- # Checks whether record is different to the current target, without loading it
93
- def different_target?(record)
94
- record._read_attribute(primary_key(record)) != owner._read_attribute(reflection.foreign_key)
95
- end
96
-
97
99
  def replace_keys(record)
98
- owner[reflection.foreign_key] = record ? record._read_attribute(primary_key(record)) : nil
100
+ owner[reflection.foreign_key] = record ? record._read_attribute(primary_key(record.class)) : nil
99
101
  end
100
102
 
101
- def primary_key(record)
102
- reflection.association_primary_key(record.class)
103
+ def primary_key(klass)
104
+ reflection.association_primary_key(klass)
103
105
  end
104
106
 
105
107
  def foreign_key_present?
@@ -113,14 +115,6 @@ module ActiveRecord
113
115
  inverse && inverse.has_one?
114
116
  end
115
117
 
116
- def target_id
117
- if options[:primary_key]
118
- owner.send(reflection.name).try(:id)
119
- else
120
- owner._read_attribute(reflection.foreign_key)
121
- end
122
- end
123
-
124
118
  def stale_state
125
119
  result = owner._read_attribute(reflection.foreign_key) { |n| owner.send(:missing_attribute, n, caller) }
126
120
  result && result.to_s
@@ -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
@@ -21,58 +21,16 @@ module ActiveRecord::Associations::Builder # :nodoc:
21
21
  add_default_callbacks(model, reflection) if reflection.options[:default]
22
22
  end
23
23
 
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
24
  def self.add_counter_cache_callbacks(model, reflection)
72
25
  cache_column = reflection.counter_cache_column
73
26
 
74
27
  model.after_update lambda { |record|
75
- record.belongs_to_counter_cache_after_update(reflection)
28
+ association = association(reflection.name)
29
+
30
+ if association.target_changed?
31
+ association.increment_counters
32
+ association.decrement_counters_before_last_save
33
+ end
76
34
  }
77
35
 
78
36
  klass = reflection.class_name.safe_constantize
@@ -123,12 +81,18 @@ module ActiveRecord::Associations::Builder # :nodoc:
123
81
  BelongsTo.touch_record(record, record.send(changes_method), foreign_key, n, touch, belongs_to_touch_method)
124
82
  }}
125
83
 
126
- unless reflection.counter_cache_column
84
+ if reflection.counter_cache_column
85
+ touch_callback = callback.(:saved_changes)
86
+ update_callback = lambda { |record|
87
+ instance_exec(record, &touch_callback) unless association(reflection.name).target_changed?
88
+ }
89
+ model.after_update update_callback, if: :saved_changes?
90
+ else
127
91
  model.after_create callback.(:saved_changes), if: :saved_changes?
92
+ model.after_update callback.(:saved_changes), if: :saved_changes?
128
93
  model.after_destroy callback.(:changes_to_save)
129
94
  end
130
95
 
131
- model.after_update callback.(:saved_changes), if: :saved_changes?
132
96
  model.after_touch callback.(:changes_to_save)
133
97
  end
134
98
 
@@ -20,11 +20,11 @@ module ActiveRecord::Associations::Builder # :nodoc:
20
20
  }
21
21
  end
22
22
 
23
- def self.define_extensions(model, name, &block)
23
+ def self.define_extensions(model, name)
24
24
  if block_given?
25
25
  extension_module_name = "#{model.name.demodulize}#{name.to_s.camelize}AssociationExtension"
26
- extension = Module.new(&block)
27
- model.parent.const_set(extension_module_name, extension)
26
+ extension = Module.new(&Proc.new)
27
+ model.module_parent.const_set(extension_module_name, extension)
28
28
  end
29
29
  end
30
30
 
@@ -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)
@@ -86,7 +53,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
86
53
  }
87
54
 
88
55
  join_model.name = "HABTM_#{association_name.to_s.camelize}"
89
- join_model.table_name_resolver = habtm
56
+ join_model.table_name_resolver = -> { table_name }
90
57
  join_model.left_model = lhs_model
91
58
 
92
59
  join_model.add_left_association :left_side, anonymous_class: lhs_model
@@ -96,7 +63,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
96
63
 
97
64
  def middle_reflection(join_model)
98
65
  middle_name = [lhs_model.name.downcase.pluralize,
99
- association_name].join("_".freeze).gsub("::".freeze, "_".freeze).to_sym
66
+ association_name].join("_").gsub("::", "_").to_sym
100
67
  middle_options = middle_options join_model
101
68
 
102
69
  HasMany.create_reflection(lhs_model,
@@ -117,6 +84,18 @@ module ActiveRecord::Associations::Builder # :nodoc:
117
84
  middle_options
118
85
  end
119
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
+
120
99
  def belongs_to_options(options)
121
100
  rhs_options = {}
122
101
 
@@ -109,8 +109,9 @@ module ActiveRecord
109
109
  end
110
110
  end
111
111
 
112
- # Add +records+ to this association. Since +<<+ flattens its argument list
113
- # and inserts each record, +push+ and +concat+ behave identically.
112
+ # Add +records+ to this association. Returns +self+ so method calls may
113
+ # be chained. Since << flattens its argument list and inserts each record,
114
+ # +push+ and +concat+ behave identically.
114
115
  def concat(*records)
115
116
  records = records.flatten
116
117
  if owner.new_record?
@@ -211,9 +212,11 @@ module ActiveRecord
211
212
  def size
212
213
  if !find_target? || loaded?
213
214
  target.size
215
+ elsif @association_ids
216
+ @association_ids.size
214
217
  elsif !association_scope.group_values.empty?
215
218
  load_target.size
216
- elsif !association_scope.distinct_value && target.is_a?(Array)
219
+ elsif !association_scope.distinct_value && !target.empty?
217
220
  unsaved_records = target.select(&:new_record?)
218
221
  unsaved_records.size + count_records
219
222
  else
@@ -230,10 +233,10 @@ module ActiveRecord
230
233
  # loaded and you are going to fetch the records anyway it is better to
231
234
  # check <tt>collection.length.zero?</tt>.
232
235
  def empty?
233
- if loaded?
236
+ if loaded? || @association_ids
234
237
  size.zero?
235
238
  else
236
- @target.blank? && !scope.exists?
239
+ target.empty? && !scope.exists?
237
240
  end
238
241
  end
239
242
 
@@ -300,23 +303,6 @@ module ActiveRecord
300
303
  end
301
304
 
302
305
  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
306
  # We have some records loaded from the database (persisted) and some that are
321
307
  # in-memory (memory). The same record may be represented in the persisted array
322
308
  # and in the memory array.
@@ -361,6 +347,7 @@ module ActiveRecord
361
347
  add_to_target(record) do
362
348
  result = insert_record(record, true, raise) {
363
349
  @_was_loaded = loaded?
350
+ @association_ids = nil
364
351
  }
365
352
  end
366
353
  raise ActiveRecord::Rollback unless result
@@ -396,8 +383,7 @@ module ActiveRecord
396
383
  records.each { |record| callback(:before_remove, record) }
397
384
 
398
385
  delete_records(existing_records, method) if existing_records.any?
399
- records.each { |record| target.delete(record) }
400
- @association_ids = nil
386
+ @target -= records
401
387
 
402
388
  records.each { |record| callback(:after_remove, record) }
403
389
  end
@@ -438,6 +424,7 @@ module ActiveRecord
438
424
  unless owner.new_record?
439
425
  result &&= insert_record(record, true, raise) {
440
426
  @_was_loaded = loaded?
427
+ @association_ids = nil
441
428
  }
442
429
  end
443
430
  end
@@ -460,7 +447,6 @@ module ActiveRecord
460
447
  if index
461
448
  target[index] = record
462
449
  elsif @_was_loaded || !loaded?
463
- @association_ids = nil
464
450
  target << record
465
451
  end
466
452
 
@@ -366,6 +366,34 @@ module ActiveRecord
366
366
  @association.create!(attributes, &block)
367
367
  end
368
368
 
369
+ # Add one or more records to the collection by setting their foreign keys
370
+ # to the association's primary key. Since #<< flattens its argument list and
371
+ # inserts each record, +push+ and #concat behave identically. Returns +self+
372
+ # so method calls may be chained.
373
+ #
374
+ # class Person < ActiveRecord::Base
375
+ # has_many :pets
376
+ # end
377
+ #
378
+ # person.pets.size # => 0
379
+ # person.pets.concat(Pet.new(name: 'Fancy-Fancy'))
380
+ # person.pets.concat(Pet.new(name: 'Spook'), Pet.new(name: 'Choo-Choo'))
381
+ # person.pets.size # => 3
382
+ #
383
+ # person.id # => 1
384
+ # person.pets
385
+ # # => [
386
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
387
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
388
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
389
+ # # ]
390
+ #
391
+ # person.pets.concat([Pet.new(name: 'Brain'), Pet.new(name: 'Benny')])
392
+ # person.pets.size # => 5
393
+ def concat(*records)
394
+ @association.concat(*records)
395
+ end
396
+
369
397
  # Replaces this collection with +other_array+. This will perform a diff
370
398
  # and delete/add only records that have changed.
371
399
  #
@@ -1005,9 +1033,8 @@ module ActiveRecord
1005
1033
  end
1006
1034
 
1007
1035
  # 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
1009
- # inserts each record, +push+ and +concat+ behave identically. Returns +self+
1010
- # so several appends may be chained together.
1036
+ # to the association's primary key. Returns +self+, so several appends may be
1037
+ # chained together.
1011
1038
  #
1012
1039
  # class Person < ActiveRecord::Base
1013
1040
  # has_many :pets
@@ -1030,7 +1057,6 @@ module ActiveRecord
1030
1057
  end
1031
1058
  alias_method :push, :<<
1032
1059
  alias_method :append, :<<
1033
- alias_method :concat, :<<
1034
1060
 
1035
1061
  def prepend(*args)
1036
1062
  raise NoMethodError, "prepend on association is not defined. Please use <<, push or append"
@@ -1062,7 +1088,7 @@ module ActiveRecord
1062
1088
  # person.pets.reload # fetches pets from the database
1063
1089
  # # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
1064
1090
  def reload
1065
- proxy_association.reload
1091
+ proxy_association.reload(true)
1066
1092
  reset_scope
1067
1093
  end
1068
1094
 
@@ -1099,7 +1125,7 @@ module ActiveRecord
1099
1125
  SpawnMethods,
1100
1126
  ].flat_map { |klass|
1101
1127
  klass.public_instance_methods(false)
1102
- } - self.public_instance_methods(false) - [:select] + [:scoping]
1128
+ } - self.public_instance_methods(false) - [:select] + [:scoping, :values]
1103
1129
 
1104
1130
  delegate(*delegate_methods, to: :scope)
1105
1131
 
@@ -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
@@ -92,7 +92,7 @@ module ActiveRecord
92
92
  if method == :delete_all
93
93
  scope.delete_all
94
94
  else
95
- scope.update_all(reflection.foreign_key => nil)
95
+ scope.update_all(nullified_owner_attributes)
96
96
  end
97
97
  end
98
98