activerecord 4.2.9 → 5.2.8

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 (274) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +614 -1572
  3. data/MIT-LICENSE +2 -2
  4. data/README.rdoc +10 -11
  5. data/examples/performance.rb +32 -31
  6. data/examples/simple.rb +5 -4
  7. data/lib/active_record/aggregations.rb +263 -249
  8. data/lib/active_record/association_relation.rb +11 -6
  9. data/lib/active_record/associations/alias_tracker.rb +29 -35
  10. data/lib/active_record/associations/association.rb +77 -43
  11. data/lib/active_record/associations/association_scope.rb +106 -133
  12. data/lib/active_record/associations/belongs_to_association.rb +52 -41
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +8 -8
  14. data/lib/active_record/associations/builder/association.rb +29 -38
  15. data/lib/active_record/associations/builder/belongs_to.rb +77 -30
  16. data/lib/active_record/associations/builder/collection_association.rb +9 -22
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +42 -35
  18. data/lib/active_record/associations/builder/has_many.rb +6 -4
  19. data/lib/active_record/associations/builder/has_one.rb +13 -6
  20. data/lib/active_record/associations/builder/singular_association.rb +15 -11
  21. data/lib/active_record/associations/collection_association.rb +139 -280
  22. data/lib/active_record/associations/collection_proxy.rb +231 -133
  23. data/lib/active_record/associations/foreign_association.rb +3 -1
  24. data/lib/active_record/associations/has_many_association.rb +34 -89
  25. data/lib/active_record/associations/has_many_through_association.rb +49 -76
  26. data/lib/active_record/associations/has_one_association.rb +38 -24
  27. data/lib/active_record/associations/has_one_through_association.rb +18 -9
  28. data/lib/active_record/associations/join_dependency/join_association.rb +40 -89
  29. data/lib/active_record/associations/join_dependency/join_base.rb +10 -9
  30. data/lib/active_record/associations/join_dependency/join_part.rb +12 -12
  31. data/lib/active_record/associations/join_dependency.rb +133 -159
  32. data/lib/active_record/associations/preloader/association.rb +85 -120
  33. data/lib/active_record/associations/preloader/through_association.rb +85 -74
  34. data/lib/active_record/associations/preloader.rb +81 -91
  35. data/lib/active_record/associations/singular_association.rb +27 -34
  36. data/lib/active_record/associations/through_association.rb +38 -18
  37. data/lib/active_record/associations.rb +1732 -1597
  38. data/lib/active_record/attribute_assignment.rb +58 -182
  39. data/lib/active_record/attribute_decorators.rb +39 -15
  40. data/lib/active_record/attribute_methods/before_type_cast.rb +10 -8
  41. data/lib/active_record/attribute_methods/dirty.rb +94 -135
  42. data/lib/active_record/attribute_methods/primary_key.rb +86 -71
  43. data/lib/active_record/attribute_methods/query.rb +4 -2
  44. data/lib/active_record/attribute_methods/read.rb +45 -63
  45. data/lib/active_record/attribute_methods/serialization.rb +40 -20
  46. data/lib/active_record/attribute_methods/time_zone_conversion.rb +58 -36
  47. data/lib/active_record/attribute_methods/write.rb +30 -45
  48. data/lib/active_record/attribute_methods.rb +166 -109
  49. data/lib/active_record/attributes.rb +201 -82
  50. data/lib/active_record/autosave_association.rb +94 -36
  51. data/lib/active_record/base.rb +57 -44
  52. data/lib/active_record/callbacks.rb +97 -57
  53. data/lib/active_record/coders/json.rb +3 -1
  54. data/lib/active_record/coders/yaml_column.rb +24 -12
  55. data/lib/active_record/collection_cache_key.rb +53 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +712 -290
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +10 -5
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +237 -90
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +71 -21
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +118 -52
  61. data/lib/active_record/connection_adapters/abstract/savepoints.rb +5 -3
  62. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +67 -46
  63. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +318 -217
  64. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +81 -36
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +570 -228
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +138 -70
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +325 -202
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +542 -593
  69. data/lib/active_record/connection_adapters/column.rb +50 -41
  70. data/lib/active_record/connection_adapters/connection_specification.rb +147 -135
  71. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +33 -0
  72. data/lib/active_record/connection_adapters/mysql/column.rb +27 -0
  73. data/lib/active_record/connection_adapters/mysql/database_statements.rb +140 -0
  74. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +72 -0
  75. data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -0
  76. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +73 -0
  77. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +87 -0
  78. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +80 -0
  79. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +148 -0
  80. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +35 -0
  81. data/lib/active_record/connection_adapters/mysql2_adapter.rb +41 -188
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +35 -11
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +45 -114
  84. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +50 -58
  86. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +10 -6
  87. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +2 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +4 -2
  89. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +5 -1
  90. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +13 -1
  91. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +9 -22
  92. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +3 -1
  93. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +5 -3
  94. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +31 -19
  95. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +2 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +3 -11
  97. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +45 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +7 -9
  99. data/lib/active_record/connection_adapters/postgresql/oid/{integer.rb → oid.rb} +6 -2
  100. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +33 -11
  101. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +52 -34
  102. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +4 -5
  103. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +55 -53
  104. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +5 -3
  105. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +3 -1
  106. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +3 -1
  107. data/lib/active_record/connection_adapters/postgresql/oid.rb +23 -25
  108. data/lib/active_record/connection_adapters/postgresql/quoting.rb +107 -47
  109. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +27 -14
  110. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +65 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +144 -90
  112. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +50 -0
  113. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +462 -284
  114. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +39 -0
  115. data/lib/active_record/connection_adapters/postgresql/utils.rb +12 -8
  116. data/lib/active_record/connection_adapters/postgresql_adapter.rb +432 -323
  117. data/lib/active_record/connection_adapters/schema_cache.rb +48 -24
  118. data/lib/active_record/connection_adapters/sql_type_metadata.rb +34 -0
  119. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
  120. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +67 -0
  121. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +17 -0
  122. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +19 -0
  123. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +18 -0
  124. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +106 -0
  125. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +269 -308
  126. data/lib/active_record/connection_adapters/statement_pool.rb +34 -13
  127. data/lib/active_record/connection_handling.rb +40 -27
  128. data/lib/active_record/core.rb +178 -198
  129. data/lib/active_record/counter_cache.rb +79 -36
  130. data/lib/active_record/define_callbacks.rb +22 -0
  131. data/lib/active_record/dynamic_matchers.rb +87 -105
  132. data/lib/active_record/enum.rb +135 -88
  133. data/lib/active_record/errors.rb +179 -52
  134. data/lib/active_record/explain.rb +23 -11
  135. data/lib/active_record/explain_registry.rb +4 -2
  136. data/lib/active_record/explain_subscriber.rb +10 -5
  137. data/lib/active_record/fixture_set/file.rb +35 -9
  138. data/lib/active_record/fixtures.rb +188 -132
  139. data/lib/active_record/gem_version.rb +4 -2
  140. data/lib/active_record/inheritance.rb +148 -112
  141. data/lib/active_record/integration.rb +70 -28
  142. data/lib/active_record/internal_metadata.rb +45 -0
  143. data/lib/active_record/legacy_yaml_adapter.rb +21 -3
  144. data/lib/active_record/locale/en.yml +3 -2
  145. data/lib/active_record/locking/optimistic.rb +88 -96
  146. data/lib/active_record/locking/pessimistic.rb +15 -3
  147. data/lib/active_record/log_subscriber.rb +95 -33
  148. data/lib/active_record/migration/command_recorder.rb +133 -90
  149. data/lib/active_record/migration/compatibility.rb +217 -0
  150. data/lib/active_record/migration/join_table.rb +8 -6
  151. data/lib/active_record/migration.rb +581 -282
  152. data/lib/active_record/model_schema.rb +290 -111
  153. data/lib/active_record/nested_attributes.rb +264 -222
  154. data/lib/active_record/no_touching.rb +7 -1
  155. data/lib/active_record/null_relation.rb +24 -37
  156. data/lib/active_record/persistence.rb +347 -119
  157. data/lib/active_record/query_cache.rb +13 -24
  158. data/lib/active_record/querying.rb +19 -17
  159. data/lib/active_record/railtie.rb +94 -32
  160. data/lib/active_record/railties/console_sandbox.rb +2 -0
  161. data/lib/active_record/railties/controller_runtime.rb +9 -3
  162. data/lib/active_record/railties/databases.rake +149 -156
  163. data/lib/active_record/readonly_attributes.rb +5 -4
  164. data/lib/active_record/reflection.rb +414 -267
  165. data/lib/active_record/relation/batches/batch_enumerator.rb +69 -0
  166. data/lib/active_record/relation/batches.rb +204 -55
  167. data/lib/active_record/relation/calculations.rb +256 -248
  168. data/lib/active_record/relation/delegation.rb +67 -60
  169. data/lib/active_record/relation/finder_methods.rb +288 -239
  170. data/lib/active_record/relation/from_clause.rb +26 -0
  171. data/lib/active_record/relation/merger.rb +86 -86
  172. data/lib/active_record/relation/predicate_builder/array_handler.rb +24 -24
  173. data/lib/active_record/relation/predicate_builder/association_query_value.rb +46 -0
  174. data/lib/active_record/relation/predicate_builder/base_handler.rb +19 -0
  175. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +20 -0
  176. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +56 -0
  177. data/lib/active_record/relation/predicate_builder/range_handler.rb +42 -0
  178. data/lib/active_record/relation/predicate_builder/relation_handler.rb +7 -1
  179. data/lib/active_record/relation/predicate_builder.rb +116 -119
  180. data/lib/active_record/relation/query_attribute.rb +45 -0
  181. data/lib/active_record/relation/query_methods.rb +448 -393
  182. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  183. data/lib/active_record/relation/spawn_methods.rb +11 -13
  184. data/lib/active_record/relation/where_clause.rb +186 -0
  185. data/lib/active_record/relation/where_clause_factory.rb +34 -0
  186. data/lib/active_record/relation.rb +287 -340
  187. data/lib/active_record/result.rb +54 -36
  188. data/lib/active_record/runtime_registry.rb +6 -4
  189. data/lib/active_record/sanitization.rb +155 -124
  190. data/lib/active_record/schema.rb +30 -24
  191. data/lib/active_record/schema_dumper.rb +91 -87
  192. data/lib/active_record/schema_migration.rb +19 -16
  193. data/lib/active_record/scoping/default.rb +102 -85
  194. data/lib/active_record/scoping/named.rb +81 -32
  195. data/lib/active_record/scoping.rb +45 -26
  196. data/lib/active_record/secure_token.rb +40 -0
  197. data/lib/active_record/serialization.rb +5 -5
  198. data/lib/active_record/statement_cache.rb +45 -35
  199. data/lib/active_record/store.rb +42 -36
  200. data/lib/active_record/suppressor.rb +61 -0
  201. data/lib/active_record/table_metadata.rb +82 -0
  202. data/lib/active_record/tasks/database_tasks.rb +134 -96
  203. data/lib/active_record/tasks/mysql_database_tasks.rb +56 -100
  204. data/lib/active_record/tasks/postgresql_database_tasks.rb +83 -41
  205. data/lib/active_record/tasks/sqlite_database_tasks.rb +44 -16
  206. data/lib/active_record/timestamp.rb +70 -38
  207. data/lib/active_record/touch_later.rb +64 -0
  208. data/lib/active_record/transactions.rb +199 -124
  209. data/lib/active_record/translation.rb +2 -0
  210. data/lib/active_record/type/adapter_specific_registry.rb +136 -0
  211. data/lib/active_record/type/date.rb +4 -45
  212. data/lib/active_record/type/date_time.rb +4 -49
  213. data/lib/active_record/type/decimal_without_scale.rb +6 -2
  214. data/lib/active_record/type/hash_lookup_type_map.rb +5 -3
  215. data/lib/active_record/type/internal/timezone.rb +17 -0
  216. data/lib/active_record/type/json.rb +30 -0
  217. data/lib/active_record/type/serialized.rb +24 -15
  218. data/lib/active_record/type/text.rb +2 -2
  219. data/lib/active_record/type/time.rb +11 -16
  220. data/lib/active_record/type/type_map.rb +15 -17
  221. data/lib/active_record/type/unsigned_integer.rb +9 -7
  222. data/lib/active_record/type.rb +79 -23
  223. data/lib/active_record/type_caster/connection.rb +33 -0
  224. data/lib/active_record/type_caster/map.rb +23 -0
  225. data/lib/active_record/type_caster.rb +9 -0
  226. data/lib/active_record/validations/absence.rb +25 -0
  227. data/lib/active_record/validations/associated.rb +13 -4
  228. data/lib/active_record/validations/length.rb +26 -0
  229. data/lib/active_record/validations/presence.rb +14 -13
  230. data/lib/active_record/validations/uniqueness.rb +40 -41
  231. data/lib/active_record/validations.rb +38 -35
  232. data/lib/active_record/version.rb +3 -1
  233. data/lib/active_record.rb +34 -22
  234. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +27 -0
  235. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
  236. data/lib/rails/generators/active_record/migration/migration_generator.rb +43 -35
  237. data/lib/rails/generators/active_record/migration/templates/{create_table_migration.rb → create_table_migration.rb.tt} +8 -3
  238. data/lib/rails/generators/active_record/migration/templates/{migration.rb → migration.rb.tt} +8 -1
  239. data/lib/rails/generators/active_record/migration.rb +18 -1
  240. data/lib/rails/generators/active_record/model/model_generator.rb +18 -22
  241. data/lib/rails/generators/active_record/model/templates/{model.rb → model.rb.tt} +3 -0
  242. data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +0 -0
  243. data/lib/rails/generators/active_record.rb +7 -5
  244. metadata +72 -50
  245. data/lib/active_record/associations/preloader/belongs_to.rb +0 -17
  246. data/lib/active_record/associations/preloader/collection_association.rb +0 -24
  247. data/lib/active_record/associations/preloader/has_many.rb +0 -17
  248. data/lib/active_record/associations/preloader/has_many_through.rb +0 -19
  249. data/lib/active_record/associations/preloader/has_one.rb +0 -23
  250. data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
  251. data/lib/active_record/associations/preloader/singular_association.rb +0 -21
  252. data/lib/active_record/attribute.rb +0 -163
  253. data/lib/active_record/attribute_set/builder.rb +0 -106
  254. data/lib/active_record/attribute_set.rb +0 -81
  255. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -491
  256. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
  257. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
  258. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +0 -13
  259. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +0 -35
  260. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
  261. data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
  262. data/lib/active_record/serializers/xml_serializer.rb +0 -193
  263. data/lib/active_record/type/big_integer.rb +0 -13
  264. data/lib/active_record/type/binary.rb +0 -50
  265. data/lib/active_record/type/boolean.rb +0 -31
  266. data/lib/active_record/type/decimal.rb +0 -64
  267. data/lib/active_record/type/decorator.rb +0 -14
  268. data/lib/active_record/type/float.rb +0 -19
  269. data/lib/active_record/type/integer.rb +0 -59
  270. data/lib/active_record/type/mutable.rb +0 -16
  271. data/lib/active_record/type/numeric.rb +0 -36
  272. data/lib/active_record/type/string.rb +0 -40
  273. data/lib/active_record/type/time_value.rb +0 -38
  274. data/lib/active_record/type/value.rb +0 -110
@@ -1,6 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
- # = Active Record Has Many Association
3
4
  module Associations
5
+ # = Active Record Has Many Association
4
6
  # This is the proxy that handles a has many association.
5
7
  #
6
8
  # If the association has a <tt>:through</tt> option further specialization
@@ -15,35 +17,27 @@ module ActiveRecord
15
17
 
16
18
  when :restrict_with_error
17
19
  unless empty?
18
- record = klass.human_attribute_name(reflection.name).downcase
19
- owner.errors.add(:base, :"restrict_dependent_destroy.many", record: record)
20
- false
20
+ record = owner.class.human_attribute_name(reflection.name).downcase
21
+ owner.errors.add(:base, :'restrict_dependent_destroy.has_many', record: record)
22
+ throw(:abort)
21
23
  end
22
24
 
25
+ when :destroy
26
+ # No point in executing the counter update since we're going to destroy the parent anyway
27
+ load_target.each { |t| t.destroyed_by_association = reflection }
28
+ destroy_all
23
29
  else
24
- if options[:dependent] == :destroy
25
- # No point in executing the counter update since we're going to destroy the parent anyway
26
- load_target.each { |t| t.destroyed_by_association = reflection }
27
- destroy_all
28
- else
29
- delete_all
30
- end
30
+ delete_all
31
31
  end
32
32
  end
33
33
 
34
34
  def insert_record(record, validate = true, raise = false)
35
35
  set_owner_attributes(record)
36
- set_inverse_instance(record)
37
-
38
- if raise
39
- record.save!(:validate => validate)
40
- else
41
- record.save(:validate => validate)
42
- end
36
+ super
43
37
  end
44
38
 
45
39
  def empty?
46
- if has_cached_counter?
40
+ if reflection.has_cached_counter?
47
41
  size.zero?
48
42
  else
49
43
  super
@@ -66,92 +60,34 @@ module ActiveRecord
66
60
  # If the collection is empty the target is set to an empty array and
67
61
  # the loaded flag is set to true as well.
68
62
  def count_records
69
- count = if has_cached_counter?
70
- owner._read_attribute cached_counter_attribute_name
63
+ count = if reflection.has_cached_counter?
64
+ owner._read_attribute(reflection.counter_cache_column).to_i
71
65
  else
72
- scope.count
66
+ scope.count(:all)
73
67
  end
74
68
 
75
69
  # If there's nothing in the database and @target has no new records
76
70
  # we are certain the current target is an empty array. This is a
77
71
  # documented side-effect of the method that may avoid an extra SELECT.
78
- @target ||= [] and loaded! if count == 0
72
+ (@target ||= []) && loaded! if count == 0
79
73
 
80
74
  [association_scope.limit_value, count].compact.min
81
75
  end
82
76
 
83
-
84
- # Returns whether a counter cache should be used for this association.
85
- #
86
- # The counter_cache option must be given on either the owner or inverse
87
- # association, and the column must be present on the owner.
88
- def has_cached_counter?(reflection = reflection())
89
- if reflection.options[:counter_cache] || (inverse = inverse_which_updates_counter_cache(reflection)) && inverse.options[:counter_cache]
90
- owner.attribute_present?(cached_counter_attribute_name(reflection))
91
- end
92
- end
93
-
94
- def cached_counter_attribute_name(reflection = reflection())
95
- if reflection.options[:counter_cache]
96
- reflection.options[:counter_cache].to_s
97
- else
98
- "#{reflection.name}_count"
99
- end
100
- end
101
-
102
77
  def update_counter(difference, reflection = reflection())
103
- update_counter_in_database(difference, reflection)
104
- update_counter_in_memory(difference, reflection)
105
- end
106
-
107
- def update_counter_in_database(difference, reflection = reflection())
108
- if has_cached_counter?(reflection)
109
- counter = cached_counter_attribute_name(reflection)
110
- owner.class.update_counters(owner.id, counter => difference)
78
+ if reflection.has_cached_counter?
79
+ owner.increment!(reflection.counter_cache_column, difference)
111
80
  end
112
81
  end
113
82
 
114
83
  def update_counter_in_memory(difference, reflection = reflection())
115
- if counter_must_be_updated_by_has_many?(reflection)
116
- counter = cached_counter_attribute_name(reflection)
117
- owner[counter] += difference
118
- owner.send(:clear_attribute_changes, counter) # eww
84
+ if reflection.counter_must_be_updated_by_has_many?
85
+ counter = reflection.counter_cache_column
86
+ owner.increment(counter, difference)
87
+ owner.send(:clear_attribute_change, counter) # eww
119
88
  end
120
89
  end
121
90
 
122
- # This shit is nasty. We need to avoid the following situation:
123
- #
124
- # * An associated record is deleted via record.destroy
125
- # * Hence the callbacks run, and they find a belongs_to on the record with a
126
- # :counter_cache options which points back at our owner. So they update the
127
- # counter cache.
128
- # * In which case, we must make sure to *not* update the counter cache, or else
129
- # it will be decremented twice.
130
- #
131
- # Hence this method.
132
- def inverse_which_updates_counter_cache(reflection = reflection())
133
- counter_name = cached_counter_attribute_name(reflection)
134
- inverse_which_updates_counter_named(counter_name, reflection)
135
- end
136
- alias inverse_updates_counter_cache? inverse_which_updates_counter_cache
137
-
138
- def inverse_which_updates_counter_named(counter_name, reflection)
139
- reflection.klass._reflections.values.find { |inverse_reflection|
140
- inverse_reflection.belongs_to? &&
141
- inverse_reflection.counter_cache_column == counter_name
142
- }
143
- end
144
- alias inverse_updates_counter_named? inverse_which_updates_counter_named
145
-
146
- def inverse_updates_counter_in_memory?(reflection)
147
- inverse = inverse_which_updates_counter_cache(reflection)
148
- inverse && inverse == reflection.inverse_of
149
- end
150
-
151
- def counter_must_be_updated_by_has_many?(reflection)
152
- !inverse_updates_counter_in_memory?(reflection) && has_cached_counter?(reflection)
153
- end
154
-
155
91
  def delete_count(method, scope)
156
92
  if method == :delete_all
157
93
  scope.delete_all
@@ -161,15 +97,16 @@ module ActiveRecord
161
97
  end
162
98
 
163
99
  def delete_or_nullify_all_records(method)
164
- count = delete_count(method, self.scope)
100
+ count = delete_count(method, scope)
165
101
  update_counter(-count)
102
+ count
166
103
  end
167
104
 
168
105
  # Deletes the records according to the <tt>:dependent</tt> option.
169
106
  def delete_records(records, method)
170
107
  if method == :destroy
171
108
  records.each(&:destroy!)
172
- update_counter(-records.length) unless inverse_updates_counter_cache?
109
+ update_counter(-records.length) unless reflection.inverse_updates_counter_cache?
173
110
  else
174
111
  scope = self.scope.where(reflection.klass.primary_key => records)
175
112
  update_counter(-delete_count(method, scope))
@@ -194,6 +131,14 @@ module ActiveRecord
194
131
  end
195
132
  saved_successfully
196
133
  end
134
+
135
+ def difference(a, b)
136
+ a - b
137
+ end
138
+
139
+ def intersection(a, b)
140
+ a & b
141
+ end
197
142
  end
198
143
  end
199
144
  end
@@ -1,31 +1,14 @@
1
- require 'active_support/core_ext/string/filters'
1
+ # frozen_string_literal: true
2
2
 
3
3
  module ActiveRecord
4
- # = Active Record Has Many Through Association
5
4
  module Associations
5
+ # = Active Record Has Many Through Association
6
6
  class HasManyThroughAssociation < HasManyAssociation #:nodoc:
7
7
  include ThroughAssociation
8
8
 
9
9
  def initialize(owner, reflection)
10
10
  super
11
-
12
- @through_records = {}
13
- @through_association = nil
14
- end
15
-
16
- # Returns the size of the collection by executing a SELECT COUNT(*) query
17
- # if the collection hasn't been loaded, and by calling collection.size if
18
- # it has. If the collection will likely have a size greater than zero,
19
- # and if fetching the collection will be needed afterwards, one less
20
- # SELECT query will be generated by using #length instead.
21
- def size
22
- if has_cached_counter?
23
- owner._read_attribute cached_counter_attribute_name(reflection)
24
- elsif loaded?
25
- target.size
26
- else
27
- super
28
- end
11
+ @through_records = {}
29
12
  end
30
13
 
31
14
  def concat(*records)
@@ -55,34 +38,16 @@ module ActiveRecord
55
38
  def insert_record(record, validate = true, raise = false)
56
39
  ensure_not_nested
57
40
 
58
- if record.new_record?
59
- if raise
60
- record.save!(:validate => validate)
61
- else
62
- return unless record.save(:validate => validate)
63
- end
41
+ if record.new_record? || record.has_changes_to_save?
42
+ return unless super
64
43
  end
65
44
 
66
45
  save_through_record(record)
67
- if has_cached_counter? && !through_reflection_updates_counter_cache?
68
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
69
- Automatic updating of counter caches on through associations has been
70
- deprecated, and will be removed in Rails 5. Instead, please set the
71
- appropriate `counter_cache` options on the `has_many` and `belongs_to`
72
- for your associations to #{through_reflection.name}.
73
- MSG
74
46
 
75
- update_counter_in_database(1)
76
- end
77
47
  record
78
48
  end
79
49
 
80
50
  private
81
-
82
- def through_association
83
- @through_association ||= owner.association(through_reflection.name)
84
- end
85
-
86
51
  # The through record (built with build_record) is temporarily cached
87
52
  # so that it may be reused if insert_record is subsequently called.
88
53
  #
@@ -92,21 +57,14 @@ module ActiveRecord
92
57
  @through_records[record.object_id] ||= begin
93
58
  ensure_mutable
94
59
 
95
- through_record = through_association.build(*options_for_through_record)
96
- through_record.send("#{source_reflection.name}=", record)
60
+ attributes = through_scope_attributes
61
+ attributes[source_reflection.name] = record
62
+ attributes[source_reflection.foreign_type] = options[:source_type] if options[:source_type]
97
63
 
98
- if options[:source_type]
99
- through_record.send("#{source_reflection.foreign_type}=", options[:source_type])
100
- end
101
-
102
- through_record
64
+ through_association.build(attributes)
103
65
  end
104
66
  end
105
67
 
106
- def options_for_through_record
107
- [through_scope_attributes]
108
- end
109
-
110
68
  def through_scope_attributes
111
69
  scope.where_values_hash(through_association.reflection.name.to_s).
112
70
  except!(through_association.reflection.foreign_key,
@@ -114,7 +72,10 @@ module ActiveRecord
114
72
  end
115
73
 
116
74
  def save_through_record(record)
117
- build_through_record(record).save!
75
+ association = build_through_record(record)
76
+ if association.changed?
77
+ association.save!
78
+ end
118
79
  ensure
119
80
  @through_records.delete(record.object_id)
120
81
  end
@@ -122,7 +83,7 @@ module ActiveRecord
122
83
  def build_record(attributes)
123
84
  ensure_not_nested
124
85
 
125
- record = super(attributes)
86
+ record = super
126
87
 
127
88
  inverse = source_reflection.inverse_of
128
89
  if inverse
@@ -136,6 +97,11 @@ module ActiveRecord
136
97
  record
137
98
  end
138
99
 
100
+ def remove_records(existing_records, records, method)
101
+ super
102
+ delete_through_records(records)
103
+ end
104
+
139
105
  def target_reflection_has_associated_record?
140
106
  !(through_reflection.belongs_to? && owner[through_reflection.foreign_key].blank?)
141
107
  end
@@ -143,7 +109,7 @@ module ActiveRecord
143
109
  def update_through_counter?(method)
144
110
  case method
145
111
  when :destroy
146
- !inverse_updates_counter_cache?(through_reflection)
112
+ !through_reflection.inverse_updates_counter_cache?
147
113
  when :nullify
148
114
  false
149
115
  else
@@ -160,23 +126,15 @@ module ActiveRecord
160
126
 
161
127
  scope = through_association.scope
162
128
  scope.where! construct_join_attributes(*records)
129
+ scope = scope.where(through_scope_attributes)
163
130
 
164
131
  case method
165
132
  when :destroy
166
133
  if scope.klass.primary_key
167
- count = scope.destroy_all.length
134
+ count = scope.destroy_all.count(&:destroyed?)
168
135
  else
169
- scope.each do |record|
170
- record._run_destroy_callbacks
171
- end
172
-
173
- arel = scope.arel
174
-
175
- stmt = Arel::DeleteManager.new arel.engine
176
- stmt.from scope.klass.arel_table
177
- stmt.wheres = arel.constraints
178
-
179
- count = scope.klass.connection.delete(stmt, 'SQL', scope.bind_values)
136
+ scope.each(&:_run_destroy_callbacks)
137
+ count = scope.delete_all
180
138
  end
181
139
  when :nullify
182
140
  count = scope.update_all(source_reflection.foreign_key => nil)
@@ -196,6 +154,30 @@ module ActiveRecord
196
154
  else
197
155
  update_counter(-count)
198
156
  end
157
+
158
+ count
159
+ end
160
+
161
+ def difference(a, b)
162
+ distribution = distribution(b)
163
+
164
+ a.reject { |record| mark_occurrence(distribution, record) }
165
+ end
166
+
167
+ def intersection(a, b)
168
+ distribution = distribution(b)
169
+
170
+ a.select { |record| mark_occurrence(distribution, record) }
171
+ end
172
+
173
+ def mark_occurrence(distribution, record)
174
+ distribution[record] > 0 && distribution[record] -= 1
175
+ end
176
+
177
+ def distribution(array)
178
+ array.each_with_object(Hash.new(0)) do |record, distribution|
179
+ distribution[record] += 1
180
+ end
199
181
  end
200
182
 
201
183
  def through_records_for(record)
@@ -226,22 +208,13 @@ module ActiveRecord
226
208
 
227
209
  def find_target
228
210
  return [] unless target_reflection_has_associated_record?
229
- get_records
211
+ super
230
212
  end
231
213
 
232
214
  # NOTE - not sure that we can actually cope with inverses here
233
215
  def invertible_for?(record)
234
216
  false
235
217
  end
236
-
237
- def has_cached_counter?(reflection = reflection())
238
- owner.attribute_present?(cached_counter_attribute_name(reflection))
239
- end
240
-
241
- def through_reflection_updates_counter_cache?
242
- counter_name = cached_counter_attribute_name
243
- inverse_updates_counter_named?(counter_name, through_reflection)
244
- end
245
218
  end
246
219
  end
247
220
  end
@@ -1,6 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
- # = Active Record Belongs To Has One Association
3
4
  module Associations
5
+ # = Active Record Has One Association
4
6
  class HasOneAssociation < SingularAssociation #:nodoc:
5
7
  include ForeignAssociation
6
8
 
@@ -11,9 +13,9 @@ module ActiveRecord
11
13
 
12
14
  when :restrict_with_error
13
15
  if load_target
14
- record = klass.human_attribute_name(reflection.name).downcase
15
- owner.errors.add(:base, :"restrict_dependent_destroy.one", record: record)
16
- false
16
+ record = owner.class.human_attribute_name(reflection.name).downcase
17
+ owner.errors.add(:base, :'restrict_dependent_destroy.has_one', record: record)
18
+ throw(:abort)
17
19
  end
18
20
 
19
21
  else
@@ -25,10 +27,10 @@ module ActiveRecord
25
27
  raise_on_type_mismatch!(record) if record
26
28
  load_target
27
29
 
28
- return self.target if !(target || record)
30
+ return target unless target || record
29
31
 
30
32
  assigning_another_record = target != record
31
- if assigning_another_record || record.changed?
33
+ if assigning_another_record || record.has_changes_to_save?
32
34
  save &&= owner.persisted?
33
35
 
34
36
  transaction_if(save) do
@@ -53,12 +55,14 @@ module ActiveRecord
53
55
  def delete(method = options[:dependent])
54
56
  if load_target
55
57
  case method
56
- when :delete
57
- target.delete
58
- when :destroy
59
- target.destroy
60
- when :nullify
61
- target.update_columns(reflection.foreign_key => nil)
58
+ when :delete
59
+ target.delete
60
+ when :destroy
61
+ target.destroyed_by_association = reflection
62
+ target.destroy
63
+ throw(:abort) unless target.destroyed?
64
+ when :nullify
65
+ target.update_columns(reflection.foreign_key => nil) if target.persisted?
62
66
  end
63
67
  end
64
68
  end
@@ -75,18 +79,20 @@ module ActiveRecord
75
79
 
76
80
  def remove_target!(method)
77
81
  case method
78
- when :delete
79
- target.delete
80
- when :destroy
81
- target.destroy
82
- else
83
- nullify_owner_attributes(target)
84
-
85
- if target.persisted? && owner.persisted? && !target.save
86
- set_owner_attributes(target)
87
- raise RecordNotSaved, "Failed to remove the existing associated #{reflection.name}. " +
88
- "The record failed to save after its foreign key was set to nil."
89
- end
82
+ when :delete
83
+ target.delete
84
+ when :destroy
85
+ target.destroyed_by_association = reflection
86
+ target.destroy
87
+ else
88
+ nullify_owner_attributes(target)
89
+ remove_inverse_instance(target)
90
+
91
+ if target.persisted? && owner.persisted? && !target.save
92
+ set_owner_attributes(target)
93
+ raise RecordNotSaved, "Failed to remove the existing associated #{reflection.name}. " \
94
+ "The record failed to save after its foreign key was set to nil."
95
+ end
90
96
  end
91
97
  end
92
98
 
@@ -101,6 +107,14 @@ module ActiveRecord
101
107
  yield
102
108
  end
103
109
  end
110
+
111
+ def _create_record(attributes, raise_error = false, &block)
112
+ unless owner.persisted?
113
+ raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"
114
+ end
115
+
116
+ super
117
+ end
104
118
  end
105
119
  end
106
120
  end
@@ -1,30 +1,39 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
- # = Active Record Has One Through Association
3
4
  module Associations
5
+ # = Active Record Has One Through Association
4
6
  class HasOneThroughAssociation < HasOneAssociation #:nodoc:
5
7
  include ThroughAssociation
6
8
 
7
- def replace(record)
8
- create_through_record(record)
9
+ def replace(record, save = true)
10
+ create_through_record(record, save)
9
11
  self.target = record
10
12
  end
11
13
 
12
14
  private
13
-
14
- def create_through_record(record)
15
+ def create_through_record(record, save)
15
16
  ensure_not_nested
16
17
 
17
- through_proxy = owner.association(through_reflection.name)
18
- through_record = through_proxy.send(:load_target)
18
+ through_proxy = through_association
19
+ through_record = through_proxy.load_target
19
20
 
20
21
  if through_record && !record
21
22
  through_record.destroy
22
23
  elsif record
23
24
  attributes = construct_join_attributes(record)
24
25
 
26
+ if through_record && through_record.destroyed?
27
+ through_record = through_proxy.tap(&:reload).target
28
+ end
29
+
25
30
  if through_record
26
- through_record.update(attributes)
27
- elsif owner.new_record?
31
+ if through_record.new_record?
32
+ through_record.assign_attributes(attributes)
33
+ else
34
+ through_record.update(attributes)
35
+ end
36
+ elsif owner.new_record? || !save
28
37
  through_proxy.build(attributes)
29
38
  else
30
39
  through_proxy.create(attributes)