activerecord 3.2.22.5 → 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 (275) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +657 -621
  3. data/MIT-LICENSE +2 -2
  4. data/README.rdoc +41 -46
  5. data/examples/performance.rb +55 -42
  6. data/examples/simple.rb +6 -5
  7. data/lib/active_record/aggregations.rb +264 -236
  8. data/lib/active_record/association_relation.rb +40 -0
  9. data/lib/active_record/associations/alias_tracker.rb +47 -42
  10. data/lib/active_record/associations/association.rb +127 -75
  11. data/lib/active_record/associations/association_scope.rb +126 -92
  12. data/lib/active_record/associations/belongs_to_association.rb +78 -27
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +9 -4
  14. data/lib/active_record/associations/builder/association.rb +117 -32
  15. data/lib/active_record/associations/builder/belongs_to.rb +135 -60
  16. data/lib/active_record/associations/builder/collection_association.rb +61 -54
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +120 -42
  18. data/lib/active_record/associations/builder/has_many.rb +10 -64
  19. data/lib/active_record/associations/builder/has_one.rb +19 -51
  20. data/lib/active_record/associations/builder/singular_association.rb +28 -18
  21. data/lib/active_record/associations/collection_association.rb +226 -293
  22. data/lib/active_record/associations/collection_proxy.rb +1067 -69
  23. data/lib/active_record/associations/foreign_association.rb +13 -0
  24. data/lib/active_record/associations/has_many_association.rb +83 -47
  25. data/lib/active_record/associations/has_many_through_association.rb +98 -65
  26. data/lib/active_record/associations/has_one_association.rb +57 -20
  27. data/lib/active_record/associations/has_one_through_association.rb +18 -9
  28. data/lib/active_record/associations/join_dependency/join_association.rb +48 -126
  29. data/lib/active_record/associations/join_dependency/join_base.rb +11 -12
  30. data/lib/active_record/associations/join_dependency/join_part.rb +35 -42
  31. data/lib/active_record/associations/join_dependency.rb +212 -164
  32. data/lib/active_record/associations/preloader/association.rb +95 -89
  33. data/lib/active_record/associations/preloader/through_association.rb +84 -44
  34. data/lib/active_record/associations/preloader.rb +123 -111
  35. data/lib/active_record/associations/singular_association.rb +33 -24
  36. data/lib/active_record/associations/through_association.rb +60 -26
  37. data/lib/active_record/associations.rb +1759 -1506
  38. data/lib/active_record/attribute_assignment.rb +60 -193
  39. data/lib/active_record/attribute_decorators.rb +90 -0
  40. data/lib/active_record/attribute_methods/before_type_cast.rb +55 -8
  41. data/lib/active_record/attribute_methods/dirty.rb +113 -74
  42. data/lib/active_record/attribute_methods/primary_key.rb +106 -77
  43. data/lib/active_record/attribute_methods/query.rb +8 -5
  44. data/lib/active_record/attribute_methods/read.rb +63 -114
  45. data/lib/active_record/attribute_methods/serialization.rb +60 -90
  46. data/lib/active_record/attribute_methods/time_zone_conversion.rb +69 -43
  47. data/lib/active_record/attribute_methods/write.rb +43 -45
  48. data/lib/active_record/attribute_methods.rb +366 -149
  49. data/lib/active_record/attributes.rb +266 -0
  50. data/lib/active_record/autosave_association.rb +312 -225
  51. data/lib/active_record/base.rb +114 -505
  52. data/lib/active_record/callbacks.rb +145 -67
  53. data/lib/active_record/coders/json.rb +15 -0
  54. data/lib/active_record/coders/yaml_column.rb +32 -23
  55. data/lib/active_record/collection_cache_key.rb +53 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +883 -284
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +16 -2
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +350 -200
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +82 -27
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +150 -65
  61. data/lib/active_record/connection_adapters/abstract/savepoints.rb +23 -0
  62. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +146 -0
  63. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +477 -284
  64. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +95 -0
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +1100 -310
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +283 -0
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +450 -118
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +657 -446
  69. data/lib/active_record/connection_adapters/column.rb +50 -255
  70. data/lib/active_record/connection_adapters/connection_specification.rb +287 -0
  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 +59 -210
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +44 -0
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +163 -0
  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 +92 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +56 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +15 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +17 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +50 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +23 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +23 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +15 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +21 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +71 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +15 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +15 -0
  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 +41 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +15 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +65 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +97 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +18 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +111 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +23 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +28 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +30 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid.rb +34 -0
  108. data/lib/active_record/connection_adapters/postgresql/quoting.rb +168 -0
  109. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +43 -0
  110. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +65 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +206 -0
  112. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +50 -0
  113. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +774 -0
  114. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +39 -0
  115. data/lib/active_record/connection_adapters/postgresql/utils.rb +81 -0
  116. data/lib/active_record/connection_adapters/postgresql_adapter.rb +620 -1080
  117. data/lib/active_record/connection_adapters/schema_cache.rb +85 -36
  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 +545 -27
  126. data/lib/active_record/connection_adapters/statement_pool.rb +34 -13
  127. data/lib/active_record/connection_handling.rb +145 -0
  128. data/lib/active_record/core.rb +559 -0
  129. data/lib/active_record/counter_cache.rb +200 -105
  130. data/lib/active_record/define_callbacks.rb +22 -0
  131. data/lib/active_record/dynamic_matchers.rb +107 -69
  132. data/lib/active_record/enum.rb +244 -0
  133. data/lib/active_record/errors.rb +245 -60
  134. data/lib/active_record/explain.rb +35 -71
  135. data/lib/active_record/explain_registry.rb +32 -0
  136. data/lib/active_record/explain_subscriber.rb +18 -9
  137. data/lib/active_record/fixture_set/file.rb +82 -0
  138. data/lib/active_record/fixtures.rb +418 -275
  139. data/lib/active_record/gem_version.rb +17 -0
  140. data/lib/active_record/inheritance.rb +209 -100
  141. data/lib/active_record/integration.rb +116 -21
  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 +9 -1
  145. data/lib/active_record/locking/optimistic.rb +107 -94
  146. data/lib/active_record/locking/pessimistic.rb +20 -8
  147. data/lib/active_record/log_subscriber.rb +99 -34
  148. data/lib/active_record/migration/command_recorder.rb +199 -64
  149. data/lib/active_record/migration/compatibility.rb +217 -0
  150. data/lib/active_record/migration/join_table.rb +17 -0
  151. data/lib/active_record/migration.rb +893 -296
  152. data/lib/active_record/model_schema.rb +328 -175
  153. data/lib/active_record/nested_attributes.rb +338 -242
  154. data/lib/active_record/no_touching.rb +58 -0
  155. data/lib/active_record/null_relation.rb +68 -0
  156. data/lib/active_record/persistence.rb +557 -170
  157. data/lib/active_record/query_cache.rb +14 -43
  158. data/lib/active_record/querying.rb +36 -24
  159. data/lib/active_record/railtie.rb +147 -52
  160. data/lib/active_record/railties/console_sandbox.rb +5 -4
  161. data/lib/active_record/railties/controller_runtime.rb +13 -6
  162. data/lib/active_record/railties/databases.rake +206 -488
  163. data/lib/active_record/readonly_attributes.rb +4 -6
  164. data/lib/active_record/reflection.rb +734 -228
  165. data/lib/active_record/relation/batches/batch_enumerator.rb +69 -0
  166. data/lib/active_record/relation/batches.rb +249 -52
  167. data/lib/active_record/relation/calculations.rb +330 -284
  168. data/lib/active_record/relation/delegation.rb +135 -37
  169. data/lib/active_record/relation/finder_methods.rb +450 -287
  170. data/lib/active_record/relation/from_clause.rb +26 -0
  171. data/lib/active_record/relation/merger.rb +193 -0
  172. data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
  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 +19 -0
  179. data/lib/active_record/relation/predicate_builder.rb +132 -43
  180. data/lib/active_record/relation/query_attribute.rb +45 -0
  181. data/lib/active_record/relation/query_methods.rb +1037 -221
  182. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  183. data/lib/active_record/relation/spawn_methods.rb +48 -151
  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 +451 -359
  187. data/lib/active_record/result.rb +129 -20
  188. data/lib/active_record/runtime_registry.rb +24 -0
  189. data/lib/active_record/sanitization.rb +164 -136
  190. data/lib/active_record/schema.rb +31 -19
  191. data/lib/active_record/schema_dumper.rb +154 -107
  192. data/lib/active_record/schema_migration.rb +56 -0
  193. data/lib/active_record/scoping/default.rb +108 -98
  194. data/lib/active_record/scoping/named.rb +125 -112
  195. data/lib/active_record/scoping.rb +77 -123
  196. data/lib/active_record/secure_token.rb +40 -0
  197. data/lib/active_record/serialization.rb +10 -6
  198. data/lib/active_record/statement_cache.rb +121 -0
  199. data/lib/active_record/store.rb +175 -16
  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 +337 -0
  203. data/lib/active_record/tasks/mysql_database_tasks.rb +115 -0
  204. data/lib/active_record/tasks/postgresql_database_tasks.rb +143 -0
  205. data/lib/active_record/tasks/sqlite_database_tasks.rb +83 -0
  206. data/lib/active_record/timestamp.rb +80 -41
  207. data/lib/active_record/touch_later.rb +64 -0
  208. data/lib/active_record/transactions.rb +240 -119
  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 +9 -0
  212. data/lib/active_record/type/date_time.rb +9 -0
  213. data/lib/active_record/type/decimal_without_scale.rb +15 -0
  214. data/lib/active_record/type/hash_lookup_type_map.rb +25 -0
  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 +71 -0
  218. data/lib/active_record/type/text.rb +11 -0
  219. data/lib/active_record/type/time.rb +21 -0
  220. data/lib/active_record/type/type_map.rb +62 -0
  221. data/lib/active_record/type/unsigned_integer.rb +17 -0
  222. data/lib/active_record/type.rb +79 -0
  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 +35 -18
  228. data/lib/active_record/validations/length.rb +26 -0
  229. data/lib/active_record/validations/presence.rb +68 -0
  230. data/lib/active_record/validations/uniqueness.rb +133 -75
  231. data/lib/active_record/validations.rb +53 -43
  232. data/lib/active_record/version.rb +7 -7
  233. data/lib/active_record.rb +89 -57
  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 +61 -8
  237. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +24 -0
  238. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +46 -0
  239. data/lib/rails/generators/active_record/migration.rb +28 -8
  240. data/lib/rails/generators/active_record/model/model_generator.rb +23 -22
  241. data/lib/rails/generators/active_record/model/templates/model.rb.tt +13 -0
  242. data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +1 -1
  243. data/lib/rails/generators/active_record.rb +10 -16
  244. metadata +141 -62
  245. data/examples/associations.png +0 -0
  246. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -63
  247. data/lib/active_record/associations/join_helper.rb +0 -55
  248. data/lib/active_record/associations/preloader/belongs_to.rb +0 -17
  249. data/lib/active_record/associations/preloader/collection_association.rb +0 -24
  250. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  251. data/lib/active_record/associations/preloader/has_many.rb +0 -17
  252. data/lib/active_record/associations/preloader/has_many_through.rb +0 -15
  253. data/lib/active_record/associations/preloader/has_one.rb +0 -23
  254. data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
  255. data/lib/active_record/associations/preloader/singular_association.rb +0 -21
  256. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  257. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
  258. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -441
  259. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
  260. data/lib/active_record/dynamic_finder_match.rb +0 -68
  261. data/lib/active_record/dynamic_scope_match.rb +0 -23
  262. data/lib/active_record/fixtures/file.rb +0 -65
  263. data/lib/active_record/identity_map.rb +0 -162
  264. data/lib/active_record/observer.rb +0 -121
  265. data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
  266. data/lib/active_record/serializers/xml_serializer.rb +0 -203
  267. data/lib/active_record/session_store.rb +0 -360
  268. data/lib/active_record/test_case.rb +0 -73
  269. data/lib/rails/generators/active_record/migration/templates/migration.rb +0 -34
  270. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -15
  271. data/lib/rails/generators/active_record/model/templates/model.rb +0 -12
  272. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  273. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  274. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  275. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord::Associations
4
+ module ForeignAssociation # :nodoc:
5
+ def foreign_key_present?
6
+ if reflection.klass.primary_key
7
+ owner.attribute_present?(reflection.active_record_primary_key)
8
+ else
9
+ false
10
+ end
11
+ end
12
+ end
13
+ end
@@ -1,20 +1,46 @@
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
12
+
13
+ def handle_dependency
14
+ case options[:dependent]
15
+ when :restrict_with_exception
16
+ raise ActiveRecord::DeleteRestrictionError.new(reflection.name) unless empty?
17
+
18
+ when :restrict_with_error
19
+ unless empty?
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)
23
+ end
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
29
+ else
30
+ delete_all
31
+ end
32
+ end
9
33
 
10
34
  def insert_record(record, validate = true, raise = false)
11
35
  set_owner_attributes(record)
12
- set_inverse_instance(record)
36
+ super
37
+ end
13
38
 
14
- if raise
15
- record.save!(:validate => validate)
39
+ def empty?
40
+ if reflection.has_cached_counter?
41
+ size.zero?
16
42
  else
17
- record.save(:validate => validate)
43
+ super
18
44
  end
19
45
  end
20
46
 
@@ -34,74 +60,84 @@ module ActiveRecord
34
60
  # If the collection is empty the target is set to an empty array and
35
61
  # the loaded flag is set to true as well.
36
62
  def count_records
37
- count = if has_cached_counter?
38
- owner.send(:read_attribute, cached_counter_attribute_name)
39
- elsif options[:counter_sql] || options[:finder_sql]
40
- reflection.klass.count_by_sql(custom_counter_sql)
63
+ count = if reflection.has_cached_counter?
64
+ owner._read_attribute(reflection.counter_cache_column).to_i
41
65
  else
42
- scoped.count
66
+ scope.count(:all)
43
67
  end
44
68
 
45
69
  # If there's nothing in the database and @target has no new records
46
70
  # we are certain the current target is an empty array. This is a
47
71
  # documented side-effect of the method that may avoid an extra SELECT.
48
- @target ||= [] and loaded! if count == 0
72
+ (@target ||= []) && loaded! if count == 0
49
73
 
50
- [options[:limit], count].compact.min
74
+ [association_scope.limit_value, count].compact.min
51
75
  end
52
76
 
53
- def has_cached_counter?(reflection = self.reflection)
54
- owner.attribute_present?(cached_counter_attribute_name(reflection))
77
+ def update_counter(difference, reflection = reflection())
78
+ if reflection.has_cached_counter?
79
+ owner.increment!(reflection.counter_cache_column, difference)
80
+ end
55
81
  end
56
82
 
57
- def cached_counter_attribute_name(reflection = self.reflection)
58
- "#{reflection.name}_count"
83
+ def update_counter_in_memory(difference, reflection = reflection())
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
88
+ end
59
89
  end
60
90
 
61
- def update_counter(difference, reflection = self.reflection)
62
- if has_cached_counter?(reflection)
63
- counter = cached_counter_attribute_name(reflection)
64
- owner.class.update_counters(owner.id, counter => difference)
65
- owner[counter] += difference
66
- owner.changed_attributes.delete(counter) # eww
91
+ def delete_count(method, scope)
92
+ if method == :delete_all
93
+ scope.delete_all
94
+ else
95
+ scope.update_all(reflection.foreign_key => nil)
67
96
  end
68
97
  end
69
98
 
70
- # This shit is nasty. We need to avoid the following situation:
71
- #
72
- # * An associated record is deleted via record.destroy
73
- # * Hence the callbacks run, and they find a belongs_to on the record with a
74
- # :counter_cache options which points back at our owner. So they update the
75
- # counter cache.
76
- # * In which case, we must make sure to *not* update the counter cache, or else
77
- # it will be decremented twice.
78
- #
79
- # Hence this method.
80
- def inverse_updates_counter_cache?(reflection = self.reflection)
81
- counter_name = cached_counter_attribute_name(reflection)
82
- reflection.klass.reflect_on_all_associations(:belongs_to).any? { |inverse_reflection|
83
- inverse_reflection.counter_cache_column == counter_name
84
- }
99
+ def delete_or_nullify_all_records(method)
100
+ count = delete_count(method, scope)
101
+ update_counter(-count)
102
+ count
85
103
  end
86
104
 
87
105
  # Deletes the records according to the <tt>:dependent</tt> option.
88
106
  def delete_records(records, method)
89
107
  if method == :destroy
90
- records.each { |r| r.destroy }
91
- update_counter(-records.length) unless inverse_updates_counter_cache?
108
+ records.each(&:destroy!)
109
+ update_counter(-records.length) unless reflection.inverse_updates_counter_cache?
110
+ else
111
+ scope = self.scope.where(reflection.klass.primary_key => records)
112
+ update_counter(-delete_count(method, scope))
113
+ end
114
+ end
115
+
116
+ def concat_records(records, *)
117
+ update_counter_if_success(super, records.length)
118
+ end
119
+
120
+ def _create_record(attributes, *)
121
+ if attributes.is_a?(Array)
122
+ super
92
123
  else
93
- scope = self.scoped.where(reflection.klass.primary_key => records)
124
+ update_counter_if_success(super, 1)
125
+ end
126
+ end
94
127
 
95
- if method == :delete_all
96
- update_counter(-scope.delete_all)
97
- else
98
- update_counter(-scope.update_all(reflection.foreign_key => nil))
99
- end
128
+ def update_counter_if_success(saved_successfully, difference)
129
+ if saved_successfully
130
+ update_counter_in_memory(difference)
100
131
  end
132
+ saved_successfully
133
+ end
134
+
135
+ def difference(a, b)
136
+ a - b
101
137
  end
102
138
 
103
- def foreign_key_present?
104
- owner.attribute_present?(reflection.association_primary_key)
139
+ def intersection(a, b)
140
+ a & b
105
141
  end
106
142
  end
107
143
  end
@@ -1,102 +1,95 @@
1
- require 'active_support/core_ext/object/blank'
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 if the collection hasn't been
17
- # loaded and calling collection.size if it has. If it's more likely than not that the collection does
18
- # have a size larger than zero, and you need to fetch that collection afterwards, it'll take one fewer
19
- # SELECT query if you use #length.
20
- def size
21
- if has_cached_counter?
22
- owner.send(:read_attribute, cached_counter_attribute_name)
23
- elsif loaded?
24
- target.size
25
- else
26
- count
27
- end
11
+ @through_records = {}
28
12
  end
29
13
 
30
14
  def concat(*records)
31
15
  unless owner.new_record?
32
16
  records.flatten.each do |record|
33
- raise_on_type_mismatch(record)
34
- record.save! if record.new_record?
17
+ raise_on_type_mismatch!(record)
35
18
  end
36
19
  end
37
20
 
38
21
  super
39
22
  end
40
23
 
41
- def insert_record(record, validate = true, raise = false)
24
+ def concat_records(records)
42
25
  ensure_not_nested
43
26
 
44
- if record.new_record?
45
- if raise
46
- record.save!(:validate => validate)
47
- else
48
- return unless record.save(:validate => validate)
27
+ records = super(records, true)
28
+
29
+ if owner.new_record? && records
30
+ records.flatten.each do |record|
31
+ build_through_record(record)
49
32
  end
50
33
  end
51
34
 
52
- save_through_record(record)
53
- update_counter(1)
54
- record
35
+ records
55
36
  end
56
37
 
57
- # ActiveRecord::Relation#delete_all needs to support joins before we can use a
58
- # SQL-only implementation.
59
- alias delete_all_on_destroy delete_all
60
-
61
- private
38
+ def insert_record(record, validate = true, raise = false)
39
+ ensure_not_nested
62
40
 
63
- def through_association
64
- @through_association ||= owner.association(through_reflection.name)
41
+ if record.new_record? || record.has_changes_to_save?
42
+ return unless super
65
43
  end
66
44
 
67
- # We temporarily cache through record that has been build, because if we build a
68
- # through record in build_record and then subsequently call insert_record, then we
69
- # want to use the exact same object.
45
+ save_through_record(record)
46
+
47
+ record
48
+ end
49
+
50
+ private
51
+ # The through record (built with build_record) is temporarily cached
52
+ # so that it may be reused if insert_record is subsequently called.
70
53
  #
71
- # However, after insert_record has been called, we clear the cache entry because
72
- # we want it to be possible to have multiple instances of the same record in an
73
- # association
54
+ # However, after insert_record has been called, the cache is cleared in
55
+ # order to allow multiple instances of the same record in an association.
74
56
  def build_through_record(record)
75
57
  @through_records[record.object_id] ||= begin
76
58
  ensure_mutable
77
59
 
78
- through_record = through_association.build
79
- through_record.send("#{source_reflection.name}=", record)
80
- through_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]
63
+
64
+ through_association.build(attributes)
81
65
  end
82
66
  end
83
67
 
68
+ def through_scope_attributes
69
+ scope.where_values_hash(through_association.reflection.name.to_s).
70
+ except!(through_association.reflection.foreign_key,
71
+ through_association.reflection.klass.inheritance_column)
72
+ end
73
+
84
74
  def save_through_record(record)
85
- build_through_record(record).save!
75
+ association = build_through_record(record)
76
+ if association.changed?
77
+ association.save!
78
+ end
86
79
  ensure
87
80
  @through_records.delete(record.object_id)
88
81
  end
89
82
 
90
- def build_record(attributes, options = {})
83
+ def build_record(attributes)
91
84
  ensure_not_nested
92
85
 
93
- record = super(attributes, options)
86
+ record = super
94
87
 
95
88
  inverse = source_reflection.inverse_of
96
89
  if inverse
97
- if inverse.macro == :has_many
90
+ if inverse.collection?
98
91
  record.send(inverse.name) << build_through_record(record)
99
- elsif inverse.macro == :has_one
92
+ elsif inverse.has_one?
100
93
  record.send("#{inverse.name}=", build_through_record(record))
101
94
  end
102
95
  end
@@ -104,18 +97,19 @@ module ActiveRecord
104
97
  record
105
98
  end
106
99
 
100
+ def remove_records(existing_records, records, method)
101
+ super
102
+ delete_through_records(records)
103
+ end
104
+
107
105
  def target_reflection_has_associated_record?
108
- if through_reflection.macro == :belongs_to && owner[through_reflection.foreign_key].blank?
109
- false
110
- else
111
- true
112
- end
106
+ !(through_reflection.belongs_to? && owner[through_reflection.foreign_key].blank?)
113
107
  end
114
108
 
115
109
  def update_through_counter?(method)
116
110
  case method
117
111
  when :destroy
118
- !inverse_updates_counter_cache?(through_reflection)
112
+ !through_reflection.inverse_updates_counter_cache?
119
113
  when :nullify
120
114
  false
121
115
  else
@@ -123,14 +117,25 @@ module ActiveRecord
123
117
  end
124
118
  end
125
119
 
120
+ def delete_or_nullify_all_records(method)
121
+ delete_records(load_target, method)
122
+ end
123
+
126
124
  def delete_records(records, method)
127
125
  ensure_not_nested
128
126
 
129
- scope = through_association.scoped.where(construct_join_attributes(*records))
127
+ scope = through_association.scope
128
+ scope.where! construct_join_attributes(*records)
129
+ scope = scope.where(through_scope_attributes)
130
130
 
131
131
  case method
132
132
  when :destroy
133
- count = scope.destroy_all.length
133
+ if scope.klass.primary_key
134
+ count = scope.destroy_all.count(&:destroyed?)
135
+ else
136
+ scope.each(&:_run_destroy_callbacks)
137
+ count = scope.delete_all
138
+ end
134
139
  when :nullify
135
140
  count = scope.update_all(source_reflection.foreign_key => nil)
136
141
  else
@@ -139,29 +144,57 @@ module ActiveRecord
139
144
 
140
145
  delete_through_records(records)
141
146
 
142
- if source_reflection.options[:counter_cache]
147
+ if source_reflection.options[:counter_cache] && method != :destroy
143
148
  counter = source_reflection.counter_cache_column
144
149
  klass.decrement_counter counter, records.map(&:id)
145
150
  end
146
151
 
147
- if through_reflection.macro == :has_many && update_through_counter?(method)
152
+ if through_reflection.collection? && update_through_counter?(method)
148
153
  update_counter(-count, through_reflection)
154
+ else
155
+ update_counter(-count)
149
156
  end
150
157
 
151
- 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
152
181
  end
153
182
 
154
183
  def through_records_for(record)
155
184
  attributes = construct_join_attributes(record)
156
185
  candidates = Array.wrap(through_association.target)
157
- candidates.find_all { |c| c.attributes.slice(*attributes.keys) == attributes }
186
+ candidates.find_all do |c|
187
+ attributes.all? do |key, value|
188
+ c.public_send(key) == value
189
+ end
190
+ end
158
191
  end
159
192
 
160
193
  def delete_through_records(records)
161
194
  records.each do |record|
162
195
  through_records = through_records_for(record)
163
196
 
164
- if through_reflection.macro == :has_many
197
+ if through_reflection.collection?
165
198
  through_records.each { |r| through_association.target.delete(r) }
166
199
  else
167
200
  if through_records.include?(through_association.target)
@@ -175,7 +208,7 @@ module ActiveRecord
175
208
 
176
209
  def find_target
177
210
  return [] unless target_reflection_has_associated_record?
178
- scoped.all
211
+ super
179
212
  end
180
213
 
181
214
  # NOTE - not sure that we can actually cope with inverses here
@@ -1,24 +1,46 @@
1
- require 'active_support/core_ext/object/inclusion'
1
+ # frozen_string_literal: true
2
2
 
3
3
  module ActiveRecord
4
- # = Active Record Belongs To Has One Association
5
4
  module Associations
5
+ # = Active Record Has One Association
6
6
  class HasOneAssociation < SingularAssociation #:nodoc:
7
+ include ForeignAssociation
8
+
9
+ def handle_dependency
10
+ case options[:dependent]
11
+ when :restrict_with_exception
12
+ raise ActiveRecord::DeleteRestrictionError.new(reflection.name) if load_target
13
+
14
+ when :restrict_with_error
15
+ if load_target
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)
19
+ end
20
+
21
+ else
22
+ delete
23
+ end
24
+ end
25
+
7
26
  def replace(record, save = true)
8
- raise_on_type_mismatch(record) if record
27
+ raise_on_type_mismatch!(record) if record
9
28
  load_target
10
29
 
11
- # If target and record are nil, or target is equal to record,
12
- # we don't need to have transaction.
13
- if (target || record) && target != record
30
+ return target unless target || record
31
+
32
+ assigning_another_record = target != record
33
+ if assigning_another_record || record.has_changes_to_save?
34
+ save &&= owner.persisted?
35
+
14
36
  transaction_if(save) do
15
- remove_target!(options[:dependent]) if target && !target.destroyed?
16
-
37
+ remove_target!(options[:dependent]) if target && !target.destroyed? && assigning_another_record
38
+
17
39
  if record
18
40
  set_owner_attributes(record)
19
41
  set_inverse_instance(record)
20
-
21
- if owner.persisted? && save && !record.save
42
+
43
+ if save && !record.save
22
44
  nullify_owner_attributes(record)
23
45
  set_owner_attributes(target) if target
24
46
  raise RecordNotSaved, "Failed to save the new associated #{reflection.name}."
@@ -33,12 +55,14 @@ module ActiveRecord
33
55
  def delete(method = options[:dependent])
34
56
  if load_target
35
57
  case method
36
- when :delete
37
- target.delete
38
- when :destroy
39
- target.destroy
40
- when :nullify
41
- target.update_attribute(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?
42
66
  end
43
67
  end
44
68
  end
@@ -54,15 +78,20 @@ module ActiveRecord
54
78
  end
55
79
 
56
80
  def remove_target!(method)
57
- if method.in?([:delete, :destroy])
58
- target.send(method)
81
+ case method
82
+ when :delete
83
+ target.delete
84
+ when :destroy
85
+ target.destroyed_by_association = reflection
86
+ target.destroy
59
87
  else
60
88
  nullify_owner_attributes(target)
89
+ remove_inverse_instance(target)
61
90
 
62
91
  if target.persisted? && owner.persisted? && !target.save
63
92
  set_owner_attributes(target)
64
- raise RecordNotSaved, "Failed to remove the existing associated #{reflection.name}. " +
65
- "The record failed to save when after its foreign key was set to nil."
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."
66
95
  end
67
96
  end
68
97
  end
@@ -78,6 +107,14 @@ module ActiveRecord
78
107
  yield
79
108
  end
80
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
81
118
  end
82
119
  end
83
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(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)