activerecord 4.2.0 → 5.2.8.1

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 +640 -928
  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 +264 -247
  8. data/lib/active_record/association_relation.rb +24 -6
  9. data/lib/active_record/associations/alias_tracker.rb +29 -35
  10. data/lib/active_record/associations/association.rb +87 -41
  11. data/lib/active_record/associations/association_scope.rb +106 -132
  12. data/lib/active_record/associations/belongs_to_association.rb +55 -36
  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 +14 -23
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +50 -39
  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 +145 -266
  22. data/lib/active_record/associations/collection_proxy.rb +242 -138
  23. data/lib/active_record/associations/foreign_association.rb +13 -0
  24. data/lib/active_record/associations/has_many_association.rb +35 -75
  25. data/lib/active_record/associations/has_many_through_association.rb +51 -69
  26. data/lib/active_record/associations/has_one_association.rb +39 -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 -81
  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 +134 -154
  32. data/lib/active_record/associations/preloader/association.rb +85 -116
  33. data/lib/active_record/associations/preloader/through_association.rb +85 -74
  34. data/lib/active_record/associations/preloader.rb +83 -93
  35. data/lib/active_record/associations/singular_association.rb +27 -40
  36. data/lib/active_record/associations/through_association.rb +48 -23
  37. data/lib/active_record/associations.rb +1732 -1596
  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 +12 -5
  41. data/lib/active_record/attribute_methods/dirty.rb +94 -125
  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 +62 -36
  47. data/lib/active_record/attribute_methods/write.rb +31 -46
  48. data/lib/active_record/attribute_methods.rb +170 -117
  49. data/lib/active_record/attributes.rb +201 -74
  50. data/lib/active_record/autosave_association.rb +118 -45
  51. data/lib/active_record/base.rb +60 -48
  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 +37 -13
  55. data/lib/active_record/collection_cache_key.rb +53 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +712 -284
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +10 -5
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +254 -87
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +72 -22
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +119 -52
  61. data/lib/active_record/connection_adapters/abstract/savepoints.rb +6 -4
  62. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +67 -46
  63. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +328 -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 +617 -212
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +139 -75
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +332 -191
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +567 -563
  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 +42 -195
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +35 -11
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +46 -115
  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 -57
  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 +5 -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 -13
  92. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +3 -1
  93. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +7 -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 -1
  103. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +65 -51
  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 +466 -280
  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 +439 -330
  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 -324
  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 +205 -202
  129. data/lib/active_record/counter_cache.rb +80 -37
  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 +136 -90
  133. data/lib/active_record/errors.rb +180 -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 +11 -6
  137. data/lib/active_record/fixture_set/file.rb +35 -9
  138. data/lib/active_record/fixtures.rb +193 -135
  139. data/lib/active_record/gem_version.rb +5 -3
  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 +48 -0
  144. data/lib/active_record/locale/en.yml +3 -2
  145. data/lib/active_record/locking/optimistic.rb +92 -98
  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 +594 -267
  152. data/lib/active_record/model_schema.rb +292 -111
  153. data/lib/active_record/nested_attributes.rb +266 -214
  154. data/lib/active_record/no_touching.rb +8 -2
  155. data/lib/active_record/null_relation.rb +24 -37
  156. data/lib/active_record/persistence.rb +350 -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 +117 -35
  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 +160 -174
  163. data/lib/active_record/readonly_attributes.rb +5 -4
  164. data/lib/active_record/reflection.rb +447 -288
  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 +259 -244
  168. data/lib/active_record/relation/delegation.rb +67 -60
  169. data/lib/active_record/relation/finder_methods.rb +290 -253
  170. data/lib/active_record/relation/from_clause.rb +26 -0
  171. data/lib/active_record/relation/merger.rb +91 -68
  172. data/lib/active_record/relation/predicate_builder/array_handler.rb +24 -23
  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 +118 -92
  180. data/lib/active_record/relation/query_attribute.rb +45 -0
  181. data/lib/active_record/relation/query_methods.rb +446 -389
  182. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  183. data/lib/active_record/relation/spawn_methods.rb +18 -16
  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 -339
  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 -19
  193. data/lib/active_record/scoping/default.rb +102 -84
  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 +136 -95
  203. data/lib/active_record/tasks/mysql_database_tasks.rb +59 -89
  204. data/lib/active_record/tasks/postgresql_database_tasks.rb +84 -31
  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 +208 -123
  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 -41
  212. data/lib/active_record/type/date_time.rb +4 -38
  213. data/lib/active_record/type/decimal_without_scale.rb +6 -2
  214. data/lib/active_record/type/hash_lookup_type_map.rb +13 -5
  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 +30 -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 +41 -32
  231. data/lib/active_record/validations.rb +38 -35
  232. data/lib/active_record/version.rb +3 -1
  233. data/lib/active_record.rb +36 -21
  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 -6
  238. data/lib/rails/generators/active_record/migration/templates/{migration.rb → migration.rb.tt} +8 -7
  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.rb +7 -5
  243. metadata +77 -53
  244. data/lib/active_record/associations/preloader/belongs_to.rb +0 -17
  245. data/lib/active_record/associations/preloader/collection_association.rb +0 -24
  246. data/lib/active_record/associations/preloader/has_many.rb +0 -17
  247. data/lib/active_record/associations/preloader/has_many_through.rb +0 -19
  248. data/lib/active_record/associations/preloader/has_one.rb +0 -23
  249. data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
  250. data/lib/active_record/associations/preloader/singular_association.rb +0 -21
  251. data/lib/active_record/attribute.rb +0 -149
  252. data/lib/active_record/attribute_set/builder.rb +0 -86
  253. data/lib/active_record/attribute_set.rb +0 -77
  254. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -491
  255. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
  256. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
  257. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +0 -13
  258. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +0 -35
  259. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
  260. data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
  261. data/lib/active_record/serializers/xml_serializer.rb +0 -193
  262. data/lib/active_record/type/big_integer.rb +0 -13
  263. data/lib/active_record/type/binary.rb +0 -50
  264. data/lib/active_record/type/boolean.rb +0 -30
  265. data/lib/active_record/type/decimal.rb +0 -40
  266. data/lib/active_record/type/decorator.rb +0 -14
  267. data/lib/active_record/type/float.rb +0 -19
  268. data/lib/active_record/type/integer.rb +0 -55
  269. data/lib/active_record/type/mutable.rb +0 -16
  270. data/lib/active_record/type/numeric.rb +0 -36
  271. data/lib/active_record/type/string.rb +0 -36
  272. data/lib/active_record/type/time_value.rb +0 -38
  273. data/lib/active_record/type/value.rb +0 -101
  274. /data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +0 -0
@@ -1,11 +1,14 @@
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
7
9
  # is provided by its child HasManyThroughAssociation.
8
10
  class HasManyAssociation < CollectionAssociation #:nodoc:
11
+ include ForeignAssociation
9
12
 
10
13
  def handle_dependency
11
14
  case options[:dependent]
@@ -14,35 +17,27 @@ module ActiveRecord
14
17
 
15
18
  when :restrict_with_error
16
19
  unless empty?
17
- record = klass.human_attribute_name(reflection.name).downcase
18
- owner.errors.add(:base, :"restrict_dependent_destroy.many", record: record)
19
- 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)
20
23
  end
21
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
22
29
  else
23
- if options[:dependent] == :destroy
24
- # No point in executing the counter update since we're going to destroy the parent anyway
25
- load_target.each { |t| t.destroyed_by_association = reflection }
26
- destroy_all
27
- else
28
- delete_all
29
- end
30
+ delete_all
30
31
  end
31
32
  end
32
33
 
33
34
  def insert_record(record, validate = true, raise = false)
34
35
  set_owner_attributes(record)
35
- set_inverse_instance(record)
36
-
37
- if raise
38
- record.save!(:validate => validate)
39
- else
40
- record.save(:validate => validate)
41
- end
36
+ super
42
37
  end
43
38
 
44
39
  def empty?
45
- if has_cached_counter?
40
+ if reflection.has_cached_counter?
46
41
  size.zero?
47
42
  else
48
43
  super
@@ -65,70 +60,34 @@ module ActiveRecord
65
60
  # If the collection is empty the target is set to an empty array and
66
61
  # the loaded flag is set to true as well.
67
62
  def count_records
68
- count = if has_cached_counter?
69
- owner._read_attribute cached_counter_attribute_name
63
+ count = if reflection.has_cached_counter?
64
+ owner._read_attribute(reflection.counter_cache_column).to_i
70
65
  else
71
- scope.count
66
+ scope.count(:all)
72
67
  end
73
68
 
74
69
  # If there's nothing in the database and @target has no new records
75
70
  # we are certain the current target is an empty array. This is a
76
71
  # documented side-effect of the method that may avoid an extra SELECT.
77
- @target ||= [] and loaded! if count == 0
72
+ (@target ||= []) && loaded! if count == 0
78
73
 
79
74
  [association_scope.limit_value, count].compact.min
80
75
  end
81
76
 
82
- def has_cached_counter?(reflection = reflection())
83
- owner.attribute_present?(cached_counter_attribute_name(reflection))
84
- end
85
-
86
- def cached_counter_attribute_name(reflection = reflection())
87
- options[:counter_cache] || "#{reflection.name}_count"
88
- end
89
-
90
77
  def update_counter(difference, reflection = reflection())
91
- update_counter_in_database(difference, reflection)
92
- update_counter_in_memory(difference, reflection)
93
- end
94
-
95
- def update_counter_in_database(difference, reflection = reflection())
96
- if has_cached_counter?(reflection)
97
- counter = cached_counter_attribute_name(reflection)
98
- owner.class.update_counters(owner.id, counter => difference)
78
+ if reflection.has_cached_counter?
79
+ owner.increment!(reflection.counter_cache_column, difference)
99
80
  end
100
81
  end
101
82
 
102
83
  def update_counter_in_memory(difference, reflection = reflection())
103
- if has_cached_counter?(reflection)
104
- counter = cached_counter_attribute_name(reflection)
105
- owner[counter] += difference
106
- 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
107
88
  end
108
89
  end
109
90
 
110
- # This shit is nasty. We need to avoid the following situation:
111
- #
112
- # * An associated record is deleted via record.destroy
113
- # * Hence the callbacks run, and they find a belongs_to on the record with a
114
- # :counter_cache options which points back at our owner. So they update the
115
- # counter cache.
116
- # * In which case, we must make sure to *not* update the counter cache, or else
117
- # it will be decremented twice.
118
- #
119
- # Hence this method.
120
- def inverse_updates_counter_cache?(reflection = reflection())
121
- counter_name = cached_counter_attribute_name(reflection)
122
- inverse_updates_counter_named?(counter_name, reflection)
123
- end
124
-
125
- def inverse_updates_counter_named?(counter_name, reflection = reflection())
126
- reflection.klass._reflections.values.any? { |inverse_reflection|
127
- inverse_reflection.belongs_to? &&
128
- inverse_reflection.counter_cache_column == counter_name
129
- }
130
- end
131
-
132
91
  def delete_count(method, scope)
133
92
  if method == :delete_all
134
93
  scope.delete_all
@@ -138,29 +97,22 @@ module ActiveRecord
138
97
  end
139
98
 
140
99
  def delete_or_nullify_all_records(method)
141
- count = delete_count(method, self.scope)
100
+ count = delete_count(method, scope)
142
101
  update_counter(-count)
102
+ count
143
103
  end
144
104
 
145
105
  # Deletes the records according to the <tt>:dependent</tt> option.
146
106
  def delete_records(records, method)
147
107
  if method == :destroy
148
108
  records.each(&:destroy!)
149
- update_counter(-records.length) unless inverse_updates_counter_cache?
109
+ update_counter(-records.length) unless reflection.inverse_updates_counter_cache?
150
110
  else
151
111
  scope = self.scope.where(reflection.klass.primary_key => records)
152
112
  update_counter(-delete_count(method, scope))
153
113
  end
154
114
  end
155
115
 
156
- def foreign_key_present?
157
- if reflection.klass.primary_key
158
- owner.attribute_present?(reflection.association_primary_key)
159
- else
160
- false
161
- end
162
- end
163
-
164
116
  def concat_records(records, *)
165
117
  update_counter_if_success(super, records.length)
166
118
  end
@@ -179,6 +131,14 @@ module ActiveRecord
179
131
  end
180
132
  saved_successfully
181
133
  end
134
+
135
+ def difference(a, b)
136
+ a - b
137
+ end
138
+
139
+ def intersection(a, b)
140
+ a & b
141
+ end
182
142
  end
183
143
  end
184
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,14 +57,12 @@ 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)
97
- through_record
98
- end
99
- end
60
+ attributes = through_scope_attributes
61
+ attributes[source_reflection.name] = record
62
+ attributes[source_reflection.foreign_type] = options[:source_type] if options[:source_type]
100
63
 
101
- def options_for_through_record
102
- [through_scope_attributes]
64
+ through_association.build(attributes)
65
+ end
103
66
  end
104
67
 
105
68
  def through_scope_attributes
@@ -109,7 +72,10 @@ module ActiveRecord
109
72
  end
110
73
 
111
74
  def save_through_record(record)
112
- build_through_record(record).save!
75
+ association = build_through_record(record)
76
+ if association.changed?
77
+ association.save!
78
+ end
113
79
  ensure
114
80
  @through_records.delete(record.object_id)
115
81
  end
@@ -117,7 +83,7 @@ module ActiveRecord
117
83
  def build_record(attributes)
118
84
  ensure_not_nested
119
85
 
120
- record = super(attributes)
86
+ record = super
121
87
 
122
88
  inverse = source_reflection.inverse_of
123
89
  if inverse
@@ -131,6 +97,11 @@ module ActiveRecord
131
97
  record
132
98
  end
133
99
 
100
+ def remove_records(existing_records, records, method)
101
+ super
102
+ delete_through_records(records)
103
+ end
104
+
134
105
  def target_reflection_has_associated_record?
135
106
  !(through_reflection.belongs_to? && owner[through_reflection.foreign_key].blank?)
136
107
  end
@@ -138,7 +109,7 @@ module ActiveRecord
138
109
  def update_through_counter?(method)
139
110
  case method
140
111
  when :destroy
141
- !inverse_updates_counter_cache?(through_reflection)
112
+ !through_reflection.inverse_updates_counter_cache?
142
113
  when :nullify
143
114
  false
144
115
  else
@@ -155,23 +126,15 @@ module ActiveRecord
155
126
 
156
127
  scope = through_association.scope
157
128
  scope.where! construct_join_attributes(*records)
129
+ scope = scope.where(through_scope_attributes)
158
130
 
159
131
  case method
160
132
  when :destroy
161
133
  if scope.klass.primary_key
162
- count = scope.destroy_all.length
134
+ count = scope.destroy_all.count(&:destroyed?)
163
135
  else
164
- scope.each do |record|
165
- record._run_destroy_callbacks
166
- end
167
-
168
- arel = scope.arel
169
-
170
- stmt = Arel::DeleteManager.new arel.engine
171
- stmt.from scope.klass.arel_table
172
- stmt.wheres = arel.constraints
173
-
174
- count = scope.klass.connection.delete(stmt, 'SQL', scope.bind_values)
136
+ scope.each(&:_run_destroy_callbacks)
137
+ count = scope.delete_all
175
138
  end
176
139
  when :nullify
177
140
  count = scope.update_all(source_reflection.foreign_key => nil)
@@ -188,9 +151,33 @@ module ActiveRecord
188
151
 
189
152
  if through_reflection.collection? && update_through_counter?(method)
190
153
  update_counter(-count, through_reflection)
154
+ else
155
+ update_counter(-count)
191
156
  end
192
157
 
193
- update_counter(-count)
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
194
181
  end
195
182
 
196
183
  def through_records_for(record)
@@ -221,18 +208,13 @@ module ActiveRecord
221
208
 
222
209
  def find_target
223
210
  return [] unless target_reflection_has_associated_record?
224
- get_records
211
+ super
225
212
  end
226
213
 
227
214
  # NOTE - not sure that we can actually cope with inverses here
228
215
  def invertible_for?(record)
229
216
  false
230
217
  end
231
-
232
- def through_reflection_updates_counter_cache?
233
- counter_name = cached_counter_attribute_name
234
- inverse_updates_counter_named?(counter_name, through_reflection)
235
- end
236
218
  end
237
219
  end
238
220
  end
@@ -1,7 +1,10 @@
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:
7
+ include ForeignAssociation
5
8
 
6
9
  def handle_dependency
7
10
  case options[:dependent]
@@ -10,9 +13,9 @@ module ActiveRecord
10
13
 
11
14
  when :restrict_with_error
12
15
  if load_target
13
- record = klass.human_attribute_name(reflection.name).downcase
14
- owner.errors.add(:base, :"restrict_dependent_destroy.one", record: record)
15
- 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)
16
19
  end
17
20
 
18
21
  else
@@ -24,10 +27,10 @@ module ActiveRecord
24
27
  raise_on_type_mismatch!(record) if record
25
28
  load_target
26
29
 
27
- return self.target if !(target || record)
30
+ return target unless target || record
28
31
 
29
32
  assigning_another_record = target != record
30
- if assigning_another_record || record.changed?
33
+ if assigning_another_record || record.has_changes_to_save?
31
34
  save &&= owner.persisted?
32
35
 
33
36
  transaction_if(save) do
@@ -52,12 +55,14 @@ module ActiveRecord
52
55
  def delete(method = options[:dependent])
53
56
  if load_target
54
57
  case method
55
- when :delete
56
- target.delete
57
- when :destroy
58
- target.destroy
59
- when :nullify
60
- 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?
61
66
  end
62
67
  end
63
68
  end
@@ -74,18 +79,20 @@ module ActiveRecord
74
79
 
75
80
  def remove_target!(method)
76
81
  case method
77
- when :delete
78
- target.delete
79
- when :destroy
80
- target.destroy
81
- else
82
- nullify_owner_attributes(target)
83
-
84
- if target.persisted? && owner.persisted? && !target.save
85
- set_owner_attributes(target)
86
- raise RecordNotSaved, "Failed to remove the existing associated #{reflection.name}. " +
87
- "The record failed to save after its foreign key was set to nil."
88
- 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
89
96
  end
90
97
  end
91
98
 
@@ -100,6 +107,14 @@ module ActiveRecord
100
107
  yield
101
108
  end
102
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
103
118
  end
104
119
  end
105
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)