activerecord 5.2.3

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 (244) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +937 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +217 -0
  5. data/examples/performance.rb +185 -0
  6. data/examples/simple.rb +15 -0
  7. data/lib/active_record.rb +188 -0
  8. data/lib/active_record/aggregations.rb +283 -0
  9. data/lib/active_record/association_relation.rb +40 -0
  10. data/lib/active_record/associations.rb +1860 -0
  11. data/lib/active_record/associations/alias_tracker.rb +81 -0
  12. data/lib/active_record/associations/association.rb +299 -0
  13. data/lib/active_record/associations/association_scope.rb +168 -0
  14. data/lib/active_record/associations/belongs_to_association.rb +130 -0
  15. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +40 -0
  16. data/lib/active_record/associations/builder/association.rb +140 -0
  17. data/lib/active_record/associations/builder/belongs_to.rb +163 -0
  18. data/lib/active_record/associations/builder/collection_association.rb +82 -0
  19. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +135 -0
  20. data/lib/active_record/associations/builder/has_many.rb +17 -0
  21. data/lib/active_record/associations/builder/has_one.rb +30 -0
  22. data/lib/active_record/associations/builder/singular_association.rb +42 -0
  23. data/lib/active_record/associations/collection_association.rb +513 -0
  24. data/lib/active_record/associations/collection_proxy.rb +1131 -0
  25. data/lib/active_record/associations/foreign_association.rb +13 -0
  26. data/lib/active_record/associations/has_many_association.rb +144 -0
  27. data/lib/active_record/associations/has_many_through_association.rb +227 -0
  28. data/lib/active_record/associations/has_one_association.rb +120 -0
  29. data/lib/active_record/associations/has_one_through_association.rb +45 -0
  30. data/lib/active_record/associations/join_dependency.rb +262 -0
  31. data/lib/active_record/associations/join_dependency/join_association.rb +60 -0
  32. data/lib/active_record/associations/join_dependency/join_base.rb +23 -0
  33. data/lib/active_record/associations/join_dependency/join_part.rb +71 -0
  34. data/lib/active_record/associations/preloader.rb +193 -0
  35. data/lib/active_record/associations/preloader/association.rb +131 -0
  36. data/lib/active_record/associations/preloader/through_association.rb +107 -0
  37. data/lib/active_record/associations/singular_association.rb +73 -0
  38. data/lib/active_record/associations/through_association.rb +121 -0
  39. data/lib/active_record/attribute_assignment.rb +88 -0
  40. data/lib/active_record/attribute_decorators.rb +90 -0
  41. data/lib/active_record/attribute_methods.rb +492 -0
  42. data/lib/active_record/attribute_methods/before_type_cast.rb +78 -0
  43. data/lib/active_record/attribute_methods/dirty.rb +150 -0
  44. data/lib/active_record/attribute_methods/primary_key.rb +143 -0
  45. data/lib/active_record/attribute_methods/query.rb +42 -0
  46. data/lib/active_record/attribute_methods/read.rb +85 -0
  47. data/lib/active_record/attribute_methods/serialization.rb +90 -0
  48. data/lib/active_record/attribute_methods/time_zone_conversion.rb +91 -0
  49. data/lib/active_record/attribute_methods/write.rb +68 -0
  50. data/lib/active_record/attributes.rb +266 -0
  51. data/lib/active_record/autosave_association.rb +498 -0
  52. data/lib/active_record/base.rb +329 -0
  53. data/lib/active_record/callbacks.rb +353 -0
  54. data/lib/active_record/coders/json.rb +15 -0
  55. data/lib/active_record/coders/yaml_column.rb +50 -0
  56. data/lib/active_record/collection_cache_key.rb +53 -0
  57. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +1068 -0
  58. data/lib/active_record/connection_adapters/abstract/database_limits.rb +72 -0
  59. data/lib/active_record/connection_adapters/abstract/database_statements.rb +540 -0
  60. data/lib/active_record/connection_adapters/abstract/query_cache.rb +145 -0
  61. data/lib/active_record/connection_adapters/abstract/quoting.rb +200 -0
  62. data/lib/active_record/connection_adapters/abstract/savepoints.rb +23 -0
  63. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +146 -0
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +685 -0
  65. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +95 -0
  66. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +1396 -0
  67. data/lib/active_record/connection_adapters/abstract/transaction.rb +283 -0
  68. data/lib/active_record/connection_adapters/abstract_adapter.rb +628 -0
  69. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +887 -0
  70. data/lib/active_record/connection_adapters/column.rb +91 -0
  71. data/lib/active_record/connection_adapters/connection_specification.rb +287 -0
  72. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +33 -0
  73. data/lib/active_record/connection_adapters/mysql/column.rb +27 -0
  74. data/lib/active_record/connection_adapters/mysql/database_statements.rb +140 -0
  75. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +72 -0
  76. data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -0
  77. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +73 -0
  78. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +87 -0
  79. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +80 -0
  80. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +148 -0
  81. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +35 -0
  82. data/lib/active_record/connection_adapters/mysql2_adapter.rb +129 -0
  83. data/lib/active_record/connection_adapters/postgresql/column.rb +44 -0
  84. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +163 -0
  85. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid.rb +34 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +92 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +56 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +15 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +17 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +50 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +23 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +23 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +15 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +21 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +71 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +15 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +15 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +45 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +41 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +15 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +65 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +97 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +18 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +111 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +23 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +28 -0
  108. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +30 -0
  109. data/lib/active_record/connection_adapters/postgresql/quoting.rb +168 -0
  110. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +43 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +65 -0
  112. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +206 -0
  113. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +50 -0
  114. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +774 -0
  115. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +39 -0
  116. data/lib/active_record/connection_adapters/postgresql/utils.rb +81 -0
  117. data/lib/active_record/connection_adapters/postgresql_adapter.rb +863 -0
  118. data/lib/active_record/connection_adapters/schema_cache.rb +118 -0
  119. data/lib/active_record/connection_adapters/sql_type_metadata.rb +34 -0
  120. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
  121. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +67 -0
  122. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +17 -0
  123. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +19 -0
  124. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +18 -0
  125. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +106 -0
  126. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +573 -0
  127. data/lib/active_record/connection_adapters/statement_pool.rb +61 -0
  128. data/lib/active_record/connection_handling.rb +145 -0
  129. data/lib/active_record/core.rb +559 -0
  130. data/lib/active_record/counter_cache.rb +218 -0
  131. data/lib/active_record/define_callbacks.rb +22 -0
  132. data/lib/active_record/dynamic_matchers.rb +122 -0
  133. data/lib/active_record/enum.rb +244 -0
  134. data/lib/active_record/errors.rb +380 -0
  135. data/lib/active_record/explain.rb +50 -0
  136. data/lib/active_record/explain_registry.rb +32 -0
  137. data/lib/active_record/explain_subscriber.rb +34 -0
  138. data/lib/active_record/fixture_set/file.rb +82 -0
  139. data/lib/active_record/fixtures.rb +1065 -0
  140. data/lib/active_record/gem_version.rb +17 -0
  141. data/lib/active_record/inheritance.rb +283 -0
  142. data/lib/active_record/integration.rb +155 -0
  143. data/lib/active_record/internal_metadata.rb +45 -0
  144. data/lib/active_record/legacy_yaml_adapter.rb +48 -0
  145. data/lib/active_record/locale/en.yml +48 -0
  146. data/lib/active_record/locking/optimistic.rb +198 -0
  147. data/lib/active_record/locking/pessimistic.rb +89 -0
  148. data/lib/active_record/log_subscriber.rb +137 -0
  149. data/lib/active_record/migration.rb +1378 -0
  150. data/lib/active_record/migration/command_recorder.rb +240 -0
  151. data/lib/active_record/migration/compatibility.rb +217 -0
  152. data/lib/active_record/migration/join_table.rb +17 -0
  153. data/lib/active_record/model_schema.rb +521 -0
  154. data/lib/active_record/nested_attributes.rb +600 -0
  155. data/lib/active_record/no_touching.rb +58 -0
  156. data/lib/active_record/null_relation.rb +68 -0
  157. data/lib/active_record/persistence.rb +763 -0
  158. data/lib/active_record/query_cache.rb +45 -0
  159. data/lib/active_record/querying.rb +70 -0
  160. data/lib/active_record/railtie.rb +226 -0
  161. data/lib/active_record/railties/console_sandbox.rb +7 -0
  162. data/lib/active_record/railties/controller_runtime.rb +56 -0
  163. data/lib/active_record/railties/databases.rake +377 -0
  164. data/lib/active_record/readonly_attributes.rb +24 -0
  165. data/lib/active_record/reflection.rb +1044 -0
  166. data/lib/active_record/relation.rb +629 -0
  167. data/lib/active_record/relation/batches.rb +287 -0
  168. data/lib/active_record/relation/batches/batch_enumerator.rb +69 -0
  169. data/lib/active_record/relation/calculations.rb +417 -0
  170. data/lib/active_record/relation/delegation.rb +147 -0
  171. data/lib/active_record/relation/finder_methods.rb +565 -0
  172. data/lib/active_record/relation/from_clause.rb +26 -0
  173. data/lib/active_record/relation/merger.rb +193 -0
  174. data/lib/active_record/relation/predicate_builder.rb +152 -0
  175. data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
  176. data/lib/active_record/relation/predicate_builder/association_query_value.rb +46 -0
  177. data/lib/active_record/relation/predicate_builder/base_handler.rb +19 -0
  178. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +20 -0
  179. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +56 -0
  180. data/lib/active_record/relation/predicate_builder/range_handler.rb +42 -0
  181. data/lib/active_record/relation/predicate_builder/relation_handler.rb +19 -0
  182. data/lib/active_record/relation/query_attribute.rb +45 -0
  183. data/lib/active_record/relation/query_methods.rb +1231 -0
  184. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  185. data/lib/active_record/relation/spawn_methods.rb +77 -0
  186. data/lib/active_record/relation/where_clause.rb +186 -0
  187. data/lib/active_record/relation/where_clause_factory.rb +34 -0
  188. data/lib/active_record/result.rb +149 -0
  189. data/lib/active_record/runtime_registry.rb +24 -0
  190. data/lib/active_record/sanitization.rb +222 -0
  191. data/lib/active_record/schema.rb +70 -0
  192. data/lib/active_record/schema_dumper.rb +255 -0
  193. data/lib/active_record/schema_migration.rb +56 -0
  194. data/lib/active_record/scoping.rb +106 -0
  195. data/lib/active_record/scoping/default.rb +152 -0
  196. data/lib/active_record/scoping/named.rb +213 -0
  197. data/lib/active_record/secure_token.rb +40 -0
  198. data/lib/active_record/serialization.rb +22 -0
  199. data/lib/active_record/statement_cache.rb +121 -0
  200. data/lib/active_record/store.rb +211 -0
  201. data/lib/active_record/suppressor.rb +61 -0
  202. data/lib/active_record/table_metadata.rb +82 -0
  203. data/lib/active_record/tasks/database_tasks.rb +337 -0
  204. data/lib/active_record/tasks/mysql_database_tasks.rb +115 -0
  205. data/lib/active_record/tasks/postgresql_database_tasks.rb +143 -0
  206. data/lib/active_record/tasks/sqlite_database_tasks.rb +83 -0
  207. data/lib/active_record/timestamp.rb +153 -0
  208. data/lib/active_record/touch_later.rb +64 -0
  209. data/lib/active_record/transactions.rb +502 -0
  210. data/lib/active_record/translation.rb +24 -0
  211. data/lib/active_record/type.rb +79 -0
  212. data/lib/active_record/type/adapter_specific_registry.rb +136 -0
  213. data/lib/active_record/type/date.rb +9 -0
  214. data/lib/active_record/type/date_time.rb +9 -0
  215. data/lib/active_record/type/decimal_without_scale.rb +15 -0
  216. data/lib/active_record/type/hash_lookup_type_map.rb +25 -0
  217. data/lib/active_record/type/internal/timezone.rb +17 -0
  218. data/lib/active_record/type/json.rb +30 -0
  219. data/lib/active_record/type/serialized.rb +71 -0
  220. data/lib/active_record/type/text.rb +11 -0
  221. data/lib/active_record/type/time.rb +21 -0
  222. data/lib/active_record/type/type_map.rb +62 -0
  223. data/lib/active_record/type/unsigned_integer.rb +17 -0
  224. data/lib/active_record/type_caster.rb +9 -0
  225. data/lib/active_record/type_caster/connection.rb +33 -0
  226. data/lib/active_record/type_caster/map.rb +23 -0
  227. data/lib/active_record/validations.rb +93 -0
  228. data/lib/active_record/validations/absence.rb +25 -0
  229. data/lib/active_record/validations/associated.rb +60 -0
  230. data/lib/active_record/validations/length.rb +26 -0
  231. data/lib/active_record/validations/presence.rb +68 -0
  232. data/lib/active_record/validations/uniqueness.rb +238 -0
  233. data/lib/active_record/version.rb +10 -0
  234. data/lib/rails/generators/active_record.rb +19 -0
  235. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +27 -0
  236. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
  237. data/lib/rails/generators/active_record/migration.rb +35 -0
  238. data/lib/rails/generators/active_record/migration/migration_generator.rb +78 -0
  239. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +24 -0
  240. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +46 -0
  241. data/lib/rails/generators/active_record/model/model_generator.rb +48 -0
  242. data/lib/rails/generators/active_record/model/templates/model.rb.tt +13 -0
  243. data/lib/rails/generators/active_record/model/templates/module.rb.tt +7 -0
  244. metadata +333 -0
@@ -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
@@ -0,0 +1,144 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Associations
5
+ # = Active Record Has Many Association
6
+ # This is the proxy that handles a has many association.
7
+ #
8
+ # If the association has a <tt>:through</tt> option further specialization
9
+ # is provided by its child HasManyThroughAssociation.
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
33
+
34
+ def insert_record(record, validate = true, raise = false)
35
+ set_owner_attributes(record)
36
+ super
37
+ end
38
+
39
+ def empty?
40
+ if reflection.has_cached_counter?
41
+ size.zero?
42
+ else
43
+ super
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ # Returns the number of records in this collection.
50
+ #
51
+ # If the association has a counter cache it gets that value. Otherwise
52
+ # it will attempt to do a count via SQL, bounded to <tt>:limit</tt> if
53
+ # there's one. Some configuration options like :group make it impossible
54
+ # to do an SQL count, in those cases the array count will be used.
55
+ #
56
+ # That does not depend on whether the collection has already been loaded
57
+ # or not. The +size+ method is the one that takes the loaded flag into
58
+ # account and delegates to +count_records+ if needed.
59
+ #
60
+ # If the collection is empty the target is set to an empty array and
61
+ # the loaded flag is set to true as well.
62
+ def count_records
63
+ count = if reflection.has_cached_counter?
64
+ owner._read_attribute(reflection.counter_cache_column).to_i
65
+ else
66
+ scope.count(:all)
67
+ end
68
+
69
+ # If there's nothing in the database and @target has no new records
70
+ # we are certain the current target is an empty array. This is a
71
+ # documented side-effect of the method that may avoid an extra SELECT.
72
+ (@target ||= []) && loaded! if count == 0
73
+
74
+ [association_scope.limit_value, count].compact.min
75
+ end
76
+
77
+ def update_counter(difference, reflection = reflection())
78
+ if reflection.has_cached_counter?
79
+ owner.increment!(reflection.counter_cache_column, difference)
80
+ end
81
+ end
82
+
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
89
+ end
90
+
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)
96
+ end
97
+ end
98
+
99
+ def delete_or_nullify_all_records(method)
100
+ count = delete_count(method, scope)
101
+ update_counter(-count)
102
+ count
103
+ end
104
+
105
+ # Deletes the records according to the <tt>:dependent</tt> option.
106
+ def delete_records(records, method)
107
+ if method == :destroy
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
123
+ else
124
+ update_counter_if_success(super, 1)
125
+ end
126
+ end
127
+
128
+ def update_counter_if_success(saved_successfully, difference)
129
+ if saved_successfully
130
+ update_counter_in_memory(difference)
131
+ end
132
+ saved_successfully
133
+ end
134
+
135
+ def difference(a, b)
136
+ a - b
137
+ end
138
+
139
+ def intersection(a, b)
140
+ a & b
141
+ end
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,227 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Associations
5
+ # = Active Record Has Many Through Association
6
+ class HasManyThroughAssociation < HasManyAssociation #:nodoc:
7
+ include ThroughAssociation
8
+
9
+ def initialize(owner, reflection)
10
+ super
11
+ @through_records = {}
12
+ end
13
+
14
+ def concat(*records)
15
+ unless owner.new_record?
16
+ records.flatten.each do |record|
17
+ raise_on_type_mismatch!(record)
18
+ end
19
+ end
20
+
21
+ super
22
+ end
23
+
24
+ def concat_records(records)
25
+ ensure_not_nested
26
+
27
+ records = super(records, true)
28
+
29
+ if owner.new_record? && records
30
+ records.flatten.each do |record|
31
+ build_through_record(record)
32
+ end
33
+ end
34
+
35
+ records
36
+ end
37
+
38
+ def insert_record(record, validate = true, raise = false)
39
+ ensure_not_nested
40
+
41
+ if record.new_record? || record.has_changes_to_save?
42
+ return unless super
43
+ end
44
+
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.
53
+ #
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.
56
+ def build_through_record(record)
57
+ @through_records[record.object_id] ||= begin
58
+ ensure_mutable
59
+
60
+ through_record = through_association.build(*options_for_through_record)
61
+ through_record.send("#{source_reflection.name}=", record)
62
+
63
+ if options[:source_type]
64
+ through_record.send("#{source_reflection.foreign_type}=", options[:source_type])
65
+ end
66
+
67
+ through_record
68
+ end
69
+ end
70
+
71
+ def options_for_through_record
72
+ [through_scope_attributes]
73
+ end
74
+
75
+ def through_scope_attributes
76
+ scope.where_values_hash(through_association.reflection.name.to_s).
77
+ except!(through_association.reflection.foreign_key,
78
+ through_association.reflection.klass.inheritance_column)
79
+ end
80
+
81
+ def save_through_record(record)
82
+ association = build_through_record(record)
83
+ if association.changed?
84
+ association.save!
85
+ end
86
+ ensure
87
+ @through_records.delete(record.object_id)
88
+ end
89
+
90
+ def build_record(attributes)
91
+ ensure_not_nested
92
+
93
+ record = super
94
+
95
+ inverse = source_reflection.inverse_of
96
+ if inverse
97
+ if inverse.collection?
98
+ record.send(inverse.name) << build_through_record(record)
99
+ elsif inverse.has_one?
100
+ record.send("#{inverse.name}=", build_through_record(record))
101
+ end
102
+ end
103
+
104
+ record
105
+ end
106
+
107
+ def remove_records(existing_records, records, method)
108
+ super
109
+ delete_through_records(records)
110
+ end
111
+
112
+ def target_reflection_has_associated_record?
113
+ !(through_reflection.belongs_to? && owner[through_reflection.foreign_key].blank?)
114
+ end
115
+
116
+ def update_through_counter?(method)
117
+ case method
118
+ when :destroy
119
+ !through_reflection.inverse_updates_counter_cache?
120
+ when :nullify
121
+ false
122
+ else
123
+ true
124
+ end
125
+ end
126
+
127
+ def delete_or_nullify_all_records(method)
128
+ delete_records(load_target, method)
129
+ end
130
+
131
+ def delete_records(records, method)
132
+ ensure_not_nested
133
+
134
+ scope = through_association.scope
135
+ scope.where! construct_join_attributes(*records)
136
+ scope = scope.where(through_scope_attributes)
137
+
138
+ case method
139
+ when :destroy
140
+ if scope.klass.primary_key
141
+ count = scope.destroy_all.count(&:destroyed?)
142
+ else
143
+ scope.each(&:_run_destroy_callbacks)
144
+ count = scope.delete_all
145
+ end
146
+ when :nullify
147
+ count = scope.update_all(source_reflection.foreign_key => nil)
148
+ else
149
+ count = scope.delete_all
150
+ end
151
+
152
+ delete_through_records(records)
153
+
154
+ if source_reflection.options[:counter_cache] && method != :destroy
155
+ counter = source_reflection.counter_cache_column
156
+ klass.decrement_counter counter, records.map(&:id)
157
+ end
158
+
159
+ if through_reflection.collection? && update_through_counter?(method)
160
+ update_counter(-count, through_reflection)
161
+ else
162
+ update_counter(-count)
163
+ end
164
+
165
+ count
166
+ end
167
+
168
+ def difference(a, b)
169
+ distribution = distribution(b)
170
+
171
+ a.reject { |record| mark_occurrence(distribution, record) }
172
+ end
173
+
174
+ def intersection(a, b)
175
+ distribution = distribution(b)
176
+
177
+ a.select { |record| mark_occurrence(distribution, record) }
178
+ end
179
+
180
+ def mark_occurrence(distribution, record)
181
+ distribution[record] > 0 && distribution[record] -= 1
182
+ end
183
+
184
+ def distribution(array)
185
+ array.each_with_object(Hash.new(0)) do |record, distribution|
186
+ distribution[record] += 1
187
+ end
188
+ end
189
+
190
+ def through_records_for(record)
191
+ attributes = construct_join_attributes(record)
192
+ candidates = Array.wrap(through_association.target)
193
+ candidates.find_all do |c|
194
+ attributes.all? do |key, value|
195
+ c.public_send(key) == value
196
+ end
197
+ end
198
+ end
199
+
200
+ def delete_through_records(records)
201
+ records.each do |record|
202
+ through_records = through_records_for(record)
203
+
204
+ if through_reflection.collection?
205
+ through_records.each { |r| through_association.target.delete(r) }
206
+ else
207
+ if through_records.include?(through_association.target)
208
+ through_association.target = nil
209
+ end
210
+ end
211
+
212
+ @through_records.delete(record.object_id)
213
+ end
214
+ end
215
+
216
+ def find_target
217
+ return [] unless target_reflection_has_associated_record?
218
+ super
219
+ end
220
+
221
+ # NOTE - not sure that we can actually cope with inverses here
222
+ def invertible_for?(record)
223
+ false
224
+ end
225
+ end
226
+ end
227
+ end
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Associations
5
+ # = Active Record Has One Association
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
+
26
+ def replace(record, save = true)
27
+ raise_on_type_mismatch!(record) if record
28
+ load_target
29
+
30
+ return target unless target || record
31
+
32
+ assigning_another_record = target != record
33
+ if assigning_another_record || record.has_changes_to_save?
34
+ save &&= owner.persisted?
35
+
36
+ transaction_if(save) do
37
+ remove_target!(options[:dependent]) if target && !target.destroyed? && assigning_another_record
38
+
39
+ if record
40
+ set_owner_attributes(record)
41
+ set_inverse_instance(record)
42
+
43
+ if save && !record.save
44
+ nullify_owner_attributes(record)
45
+ set_owner_attributes(target) if target
46
+ raise RecordNotSaved, "Failed to save the new associated #{reflection.name}."
47
+ end
48
+ end
49
+ end
50
+ end
51
+
52
+ self.target = record
53
+ end
54
+
55
+ def delete(method = options[:dependent])
56
+ if load_target
57
+ case method
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?
66
+ end
67
+ end
68
+ end
69
+
70
+ private
71
+
72
+ # The reason that the save param for replace is false, if for create (not just build),
73
+ # is because the setting of the foreign keys is actually handled by the scoping when
74
+ # the record is instantiated, and so they are set straight away and do not need to be
75
+ # updated within replace.
76
+ def set_new_record(record)
77
+ replace(record, false)
78
+ end
79
+
80
+ def remove_target!(method)
81
+ case method
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
96
+ end
97
+ end
98
+
99
+ def nullify_owner_attributes(record)
100
+ record[reflection.foreign_key] = nil
101
+ end
102
+
103
+ def transaction_if(value)
104
+ if value
105
+ reflection.klass.transaction { yield }
106
+ else
107
+ yield
108
+ end
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
118
+ end
119
+ end
120
+ end